wine/tools/make_unicode
2024-05-04 09:56:23 +02:00

5868 lines
229 KiB
Perl
Executable file

#!/usr/bin/perl -w
#
# Generate code page .c files from ftp.unicode.org descriptions
#
# Copyright 2000 Alexandre Julliard
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#
use strict;
use XML::LibXML;
use Digest::SHA;
use Encode;
use Time::Local qw(timegm_modern);
my $UNIVERSION = "15.1.0";
my $CLDRVERSION = "45";
my $ISO639VERSION = "20230123";
my $TZVERSION = "2024a";
my %data_files =
(
ucd => { url => "https://www.unicode.org/Public/$UNIVERSION/ucd/UCD.zip", name => "UCD-$UNIVERSION.zip",
sha => "cb1c663d053926500cd501229736045752713a066bd75802098598b7a7056177" },
unihan => { url => "https://www.unicode.org/Public/$UNIVERSION/ucd/Unihan.zip", name => "Unihan-$UNIVERSION.zip",
sha => "a0226610e324bcf784ac380e11f4cbf533ee1e6b3d028b0991bf8c0dc3f85853" },
idna => { url => "https://www.unicode.org/Public/idna/$UNIVERSION/IdnaMappingTable.txt", name => "IdnaMappingTable-$UNIVERSION.txt",
sha => "402cbd285f1f952fcd0834b63541d54f69d3d8f1b8f8599bf71a1a14935f82c4" },
cldr => { url => "https://github.com/unicode-org/cldr/archive/refs/tags/release-$CLDRVERSION.zip",
sha => "29f4dafa918017e74cb84edf19fb707894cbce1cf7da1dd89fff24c107a66ecb" },
cldr33 => { url => "https://www.unicode.org/Public/cldr/33/cldr-common-33.0.zip",
sha => "fa3490082c086d21257153609642f54fcf788fcfda4966fe67f3f6daca0d58b9" },
sorting => { url => "https://download.microsoft.com/download/C/F/7/CF713A5E-9FBC-4FD6-9246-275F65C0E498/Windows 10 Sorting Weight Table.txt",
sha => "81fcfa1e5ed3e3a94d329959ff7d97d522ddf9d653d2c4d6ddcccc5cd4df663f" },
codepages => { url => "https://download.microsoft.com/download/C/F/7/CF713A5E-9FBC-4FD6-9246-275F65C0E498/Windows Supported Code Page Data Files.zip",
sha => "5074e6dd253056ba61fc6c870c9a955467855129c6ad3a51761c386b301b125a" },
iso639 => { url => "https://iso639-3.sil.org/sites/iso639-3/files/downloads/iso-639-3_Code_Tables_$ISO639VERSION.zip",
sha => "884faa6cc5ac5181ed7969eed75355c1bc665447614cf4c06c62e87b38fe6a97" },
ksx1001 => { url => "https://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/KSC/KSX1001.TXT",
sha => "d8d2a35206ac0ea2865f5d801c9d6717f735bf46f263a658a64a960abe59e371" },
jis0208 => { url => "https://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT",
sha => "1c571870457f19c97720631fa83ee491549a96ba1436da1296786a67d8632e87" },
jis0212 => { url => "https://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0212.TXT",
sha => "477820bb3055bbcc90880d788cd95607d221dc94457bae249231adecf13c12e6" },
tzdata => { url => "https://data.iana.org/time-zones/releases/tzdata$TZVERSION.tar.gz",
sha => "0d0434459acbd2059a7a8da1f3304a84a86591f6ed69c6248fffa502b6edffe3" },
);
# Default char for undefined mappings
my $DEF_CHAR = ord '?';
# Last valid Unicode character
my $MAX_CHAR = 0x10ffff;
my $nlskey = "-SYSTEM\\-CurrentControlSet\\-Control\\-Nls";
my $zonekey = "-Software\\-Microsoft\\-Windows NT\\-CurrentVersion\\Time Zones";
my @allfiles =
(
"CodpageFiles/037.txt",
"CodpageFiles/437.txt",
"CodpageFiles/500.txt",
"CodpageFiles/708.txt",
"CodpageFiles/720.txt",
"CodpageFiles/737.txt",
"CodpageFiles/775.txt",
"CodpageFiles/850.txt",
"CodpageFiles/852.txt",
"CodpageFiles/855.txt",
"CodpageFiles/857.txt",
"CodpageFiles/860.txt",
"CodpageFiles/861.txt",
"CodpageFiles/862.txt",
"CodpageFiles/863.txt",
"CodpageFiles/864.txt",
"CodpageFiles/865.txt",
"CodpageFiles/866.txt",
"CodpageFiles/869.txt",
"CodpageFiles/874.txt",
"CodpageFiles/875.txt",
"CodpageFiles/932.txt",
"CodpageFiles/936.txt",
"CodpageFiles/949.txt",
"CodpageFiles/950.txt",
"CodpageFiles/1026.txt",
"CodpageFiles/1250.txt",
"CodpageFiles/1251.txt",
"CodpageFiles/1252.txt",
"CodpageFiles/1253.txt",
"CodpageFiles/1254.txt",
"CodpageFiles/1255.txt",
"CodpageFiles/1256.txt",
"CodpageFiles/1257.txt",
"CodpageFiles/1258.txt",
"CodpageFiles/1361.txt",
"CodpageFiles/10000.txt",
"CodpageFiles/10001.txt",
"CodpageFiles/10002.txt",
"CodpageFiles/10003.txt",
"CodpageFiles/10004.txt",
"CodpageFiles/10005.txt",
"CodpageFiles/10006.txt",
"CodpageFiles/10007.txt",
"CodpageFiles/10008.txt",
"CodpageFiles/10010.txt",
"CodpageFiles/10017.txt",
"CodpageFiles/10021.txt",
"CodpageFiles/10029.txt",
"CodpageFiles/10079.txt",
"CodpageFiles/10081.txt",
"CodpageFiles/10082.txt",
"CodpageFiles/20127.txt",
"CodpageFiles/20866.txt",
"CodpageFiles/21866.txt",
"CodpageFiles/28591.txt",
"CodpageFiles/28592.txt",
"CodpageFiles/28593.txt",
"CodpageFiles/28594.txt",
"CodpageFiles/28595.txt",
"CodpageFiles/28596.txt",
"CodpageFiles/28597.txt",
"CodpageFiles/28598.txt",
"CodpageFiles/28599.txt",
"CodpageFiles/28603.txt",
"CodpageFiles/28605.txt",
);
my @timezone_files = qw(africa antarctica asia australasia europe northamerica southamerica etcetera backward);
my %ctype =
(
# CT_CTYPE1
"upper" => 0x0001,
"lower" => 0x0002,
"digit" => 0x0004,
"space" => 0x0008,
"punct" => 0x0010,
"cntrl" => 0x0020,
"blank" => 0x0040,
"xdigit" => 0x0080,
"alpha" => 0x0100 | 0x80000000,
"defin" => 0x0200,
# CT_CTYPE3 in high 16 bits
"nonspacing" => 0x00010000,
"diacritic" => 0x00020000,
"vowelmark" => 0x00040000,
"symbol" => 0x00080000,
"katakana" => 0x00100000,
"hiragana" => 0x00200000,
"halfwidth" => 0x00400000,
"fullwidth" => 0x00800000,
"ideograph" => 0x01000000,
"kashida" => 0x02000000,
"lexical" => 0x04000000,
"highsurrogate" => 0x08000000,
"lowsurrogate" => 0x10000000,
);
my %bracket_types =
(
"o" => 0x0000,
"c" => 0x0001,
);
my %indic_types =
(
"Other" => 0x0000,
"Bindu" => 0x0001,
"Visarga" => 0x0002,
"Avagraha" => 0x0003,
"Nukta" => 0x0004,
"Virama" => 0x0005,
"Vowel_Independent" => 0x0006,
"Vowel_Dependent" => 0x0007,
"Vowel" => 0x0008,
"Consonant_Placeholder" => 0x0009,
"Consonant" => 0x000a,
"Consonant_Dead" => 0x000b,
"Consonant_Succeeding_Repha" => 0x000c,
"Consonant_Subjoined" => 0x000d,
"Consonant_Medial" => 0x000e,
"Consonant_Final" => 0x000f,
"Consonant_Head_Letter" => 0x0010,
"Modifying_Letter" => 0x0011,
"Tone_Letter" => 0x0012,
"Tone_Mark" => 0x0013,
"Register_Shifter" => 0x0014,
"Consonant_Preceding_Repha" => 0x0015,
"Pure_Killer" => 0x0016,
"Invisible_Stacker" => 0x0017,
"Gemination_Mark" => 0x0018,
"Cantillation_Mark" => 0x0019,
"Non_Joiner" => 0x001a,
"Joiner" => 0x001b,
"Number_Joiner" => 0x001c,
"Number" => 0x001d,
"Brahmi_Joining_Number" => 0x001e,
"Consonant_With_Stacker" => 0x001f,
"Consonant_Prefixed" => 0x0020,
"Syllable_Modifier" => 0x0021,
"Consonant_Killer" => 0x0022,
"Consonant_Initial_Postfixed" => 0x0023,
);
my %matra_types =
(
"Right" => 0x01,
"Left" => 0x02,
"Visual_Order_Left" => 0x03,
"Left_And_Right" => 0x04,
"Top" => 0x05,
"Bottom" => 0x06,
"Top_And_Bottom" => 0x07,
"Top_And_Right" => 0x08,
"Top_And_Left" => 0x09,
"Top_And_Left_And_Right" => 0x0a,
"Bottom_And_Right" => 0x0b,
"Top_And_Bottom_And_Right" => 0x0c,
"Overstruck" => 0x0d,
"Invisible" => 0x0e,
"Bottom_And_Left" => 0x0f,
"Top_And_Bottom_And_Left" => 0x10,
);
my %break_types =
(
"BK" => 0x0001,
"CR" => 0x0002,
"LF" => 0x0003,
"CM" => 0x0004,
"SG" => 0x0005,
"GL" => 0x0006,
"CB" => 0x0007,
"SP" => 0x0008,
"ZW" => 0x0009,
"NL" => 0x000a,
"WJ" => 0x000b,
"JL" => 0x000c,
"JV" => 0x000d,
"JT" => 0x000e,
"H2" => 0x000f,
"H3" => 0x0010,
"XX" => 0x0011,
"OP" => 0x0012,
"CL" => 0x0013,
"CP" => 0x0014,
"QU" => 0x0015,
"NS" => 0x0016,
"EX" => 0x0017,
"SY" => 0x0018,
"IS" => 0x0019,
"PR" => 0x001a,
"PO" => 0x001b,
"NU" => 0x001c,
"AL" => 0x001d,
"ID" => 0x001e,
"IN" => 0x001f,
"HY" => 0x0020,
"BB" => 0x0021,
"BA" => 0x0022,
"SA" => 0x0023,
"AI" => 0x0024,
"B2" => 0x0025,
"HL" => 0x0026,
"CJ" => 0x0027,
"RI" => 0x0028,
"EB" => 0x0029,
"EM" => 0x002a,
"ZWJ" => 0x002b,
"AK" => 0x002c,
"AP" => 0x002d,
"AS" => 0x002e,
"VF" => 0x002f,
"VI" => 0x0030,
);
my %vertical_types =
(
"R" => 0x0000,
"U" => 0x0001,
"Tr" => 0x0002,
"Tu" => 0x0003,
);
my %categories =
(
"Lu" => $ctype{"defin"}|$ctype{"alpha"}|$ctype{"upper"}, # Letter, Uppercase
"Ll" => $ctype{"defin"}|$ctype{"alpha"}|$ctype{"lower"}, # Letter, Lowercase
"Lt" => $ctype{"defin"}|$ctype{"alpha"}|$ctype{"upper"}|$ctype{"lower"}, # Letter, Titlecase
"Mn" => $ctype{"defin"}|$ctype{"nonspacing"}, # Mark, Non-Spacing
"Mc" => $ctype{"defin"}, # Mark, Spacing Combining
"Me" => $ctype{"defin"}, # Mark, Enclosing
"Nd" => $ctype{"defin"}|$ctype{"digit"}, # Number, Decimal Digit
"Nl" => $ctype{"defin"}|$ctype{"alpha"}, # Number, Letter
"No" => $ctype{"defin"}, # Number, Other
"Zs" => $ctype{"defin"}|$ctype{"space"}, # Separator, Space
"Zl" => $ctype{"defin"}|$ctype{"space"}, # Separator, Line
"Zp" => $ctype{"defin"}|$ctype{"space"}, # Separator, Paragraph
"Cc" => $ctype{"defin"}|$ctype{"cntrl"}, # Other, Control
"Cf" => $ctype{"defin"}|$ctype{"cntrl"}, # Other, Format
"Cs" => $ctype{"defin"}, # Other, Surrogate
"Co" => $ctype{"defin"}, # Other, Private Use
"Cn" => $ctype{"defin"}, # Other, Not Assigned
"Lm" => $ctype{"defin"}|$ctype{"alpha"}, # Letter, Modifier
"Lo" => $ctype{"defin"}|$ctype{"alpha"}, # Letter, Other
"Pc" => $ctype{"defin"}|$ctype{"punct"}, # Punctuation, Connector
"Pd" => $ctype{"defin"}|$ctype{"punct"}, # Punctuation, Dash
"Ps" => $ctype{"defin"}|$ctype{"punct"}, # Punctuation, Open
"Pe" => $ctype{"defin"}|$ctype{"punct"}, # Punctuation, Close
"Pi" => $ctype{"defin"}|$ctype{"punct"}, # Punctuation, Initial quote
"Pf" => $ctype{"defin"}|$ctype{"punct"}, # Punctuation, Final quote
"Po" => $ctype{"defin"}|$ctype{"punct"}, # Punctuation, Other
"Sm" => $ctype{"defin"}|$ctype{"symbol"}, # Symbol, Math
"Sc" => $ctype{"defin"}|$ctype{"symbol"}, # Symbol, Currency
"Sk" => $ctype{"defin"}|$ctype{"symbol"}, # Symbol, Modifier
"So" => $ctype{"defin"}|$ctype{"symbol"} # Symbol, Other
);
# a few characters need additional categories that cannot be determined automatically
my %special_categories =
(
"xdigit" => [ ord('0')..ord('9'),ord('A')..ord('F'),ord('a')..ord('f'),
0xff10..0xff19, 0xff21..0xff26, 0xff41..0xff46 ],
"space" => [ 0x09..0x0d, 0x85 ],
"blank" => [ 0x09, 0x20, 0xa0, 0x3000, 0xfeff ],
"cntrl" => [ 0x070f, 0x200c, 0x200d,
0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d, 0x202e,
0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
0xfff9, 0xfffa, 0xfffb ],
"punct" => [ 0x24, 0x2b, 0x3c..0x3e, 0x5e, 0x60, 0x7c, 0x7e, 0xa2..0xbe,
0xd7, 0xf7 ],
"digit" => [ 0xb2, 0xb3, 0xb9 ],
"lower" => [ 0xaa, 0xba, 0x2071, 0x207f ],
"nonspacing" => [ 0xc0..0xc5, 0xc7..0xcf, 0xd1..0xd6, 0xd8..0xdd, 0xe0..0xe5, 0xe7..0xef,
0xf1..0xf6, 0xf8..0xfd, 0xff, 0x6de, 0x1929..0x192b, 0x302e..0x302f ],
"diacritic" => [ 0x5e, 0x60, 0xb7, 0xd8, 0xf8 ],
"symbol" => [ 0x09..0x0d, 0x20..0x23, 0x25, 0x26, 0x28..0x2a, 0x2c, 0x2e..0x2f, 0x3a..0x40,
0x5b..0x60, 0x7b..0x7e, 0xa0..0xa9, 0xab..0xb1, 0xb4..0xb8, 0xbb, 0xbf,
0x02b9..0x02ba, 0x02c6..0x02cf ],
"halfwidth" => [ 0x20..0x7e, 0xa2..0xa3, 0xa5..0xa6, 0xac, 0xaf, 0x20a9 ],
"fullwidth" => [ 0x2018..0x2019, 0x201c..0x201d, 0x3000..0x3002, 0x300c..0x300d, 0x309b..0x309c,
0x30a1..0x30ab, 0x30ad, 0x30ad, 0x30af, 0x30b1, 0x30b3, 0x30b5, 0x30b7, 0x30b9,
0x30bb, 0x30bd, 0x30bf, 0x30c1, 0x30c3, 0x30c4, 0x30c6, 0x30c8, 0x30ca..0x30cf,
0x30d2, 0x30d5, 0x30d8, 0x30db, 0x30de..0x30ed, 0x30ef, 0x30f2..0x30f3, 0x30fb,
0x3131..0x3164 ],
"ideograph" => [ 0x3006..0x3007 ],
"lexical" => [ 0x22, 0x24, 0x27, 0x2d, 0x2f, 0x3d, 0x40, 0x5c, 0x5e..0x60, 0x7e,
0xa8, 0xaa, 0xad, 0xaf, 0xb4, 0xb8, 0xba,
0x02b0..0x02b8, 0x02bc, 0x02c7, 0x02ca..0x02cb, 0x02cf, 0x02d8..0x02dd, 0x02e0..0x02e3,
0x037a, 0x0384..0x0385, 0x0387, 0x0559..0x055a, 0x0640, 0x1fbd..0x1fc1,
0x1fcd..0x1fcf, 0x1fdd..0x1fdf, 0x1fed..0x1fef, 0x1ffd..0x1ffe, 0x2010..0x2015,
0x2032..0x2034, 0x2038, 0x2043..0x2044, 0x207b..0x207c, 0x207f, 0x208b..0x208c,
0x2212, 0x2215..0x2216, 0x2500, 0x2504..0x2505, 0x2508..0x2509, 0x254c..0x254d,
0x3003, 0x301c, 0x3030..0x3035, 0x309b..0x309e, 0x30fd..0x30fe, 0xfe31..0xfe32,
0xfe58, 0xfe63, 0xfe66, 0xfe68..0xfe69, 0xfe6b, 0xff04, 0xff07, 0xff0d, 0xff0f,
0xff1d, 0xff20, 0xff3c, 0xff3e, 0xff40, 0xff5e ],
"kashida" => [ 0x0640 ],
);
my %directions =
(
"L" => 1, # Left-to-Right
"R" => 2, # Right-to-Left
"AL" => 12, # Right-to-Left Arabic
"EN" => 3, # European Number
"ES" => 4, # European Number Separator
"ET" => 5, # European Number Terminator
"AN" => 6, # Arabic Number
"CS" => 7, # Common Number Separator
"NSM" => 13, # Non-Spacing Mark
"BN" => 14, # Boundary Neutral
"B" => 8, # Paragraph Separator
"S" => 9, # Segment Separator
"WS" => 10, # Whitespace
"ON" => 11, # Other Neutrals
"LRE" => 15, # Left-to-Right Embedding
"LRO" => 15, # Left-to-Right Override
"RLE" => 15, # Right-to-Left Embedding
"RLO" => 15, # Right-to-Left Override
"PDF" => 15, # Pop Directional Format
"LRI" => 15, # Left-to-Right Isolate
"RLI" => 15, # Right-to-Left Isolate
"FSI" => 15, # First Strong Isolate
"PDI" => 15 # Pop Directional Isolate
);
my %c2_types =
(
"L" => 1, # C2_LEFTTORIGHT
"R" => 2, # C2_RIGHTTOLEFT
"AL" => 2, # C2_RIGHTTOLEFT
"EN" => 3, # C2_EUROPENUMBER
"ES" => 4, # C2_EUROPESEPARATOR
"ET" => 5, # C2_EUROPETERMINATOR
"AN" => 6, # C2_ARABICNUMBER
"CS" => 7, # C2_COMMONSEPARATOR
"NSM" => 11, # C2_OTHERNEUTRAL
"BN" => 0, # C2_NOTAPPLICABLE
"B" => 8, # C2_BLOCKSEPARATOR
"S" => 9, # C2_SEGMENTSEPARATOR
"WS" => 10, # C2_WHITESPACE
"ON" => 11, # C2_OTHERNEUTRAL
"LRE" => 11, # C2_OTHERNEUTRAL
"LRO" => 11, # C2_OTHERNEUTRAL
"RLE" => 11, # C2_OTHERNEUTRAL
"RLO" => 11, # C2_OTHERNEUTRAL
"PDF" => 11, # C2_OTHERNEUTRAL
"LRI" => 11, # C2_OTHERNEUTRAL
"RLI" => 11, # C2_OTHERNEUTRAL
"FSI" => 11, # C2_OTHERNEUTRAL
"PDI" => 11 # C2_OTHERNEUTRAL
);
my %bidi_types =
(
"ON" => 0, # Other Neutrals
"L" => 1, # Left-to-Right
"R" => 2, # Right-to-Left
"AN" => 3, # Arabic Number
"EN" => 4, # European Number
"AL" => 5, # Right-to-Left Arabic
"NSM" => 6, # Non-Spacing Mark
"CS" => 7, # Common Number Separator
"ES" => 8, # European Number Separator
"ET" => 9, # European Number Terminator
"BN" => 10, # Boundary Neutral
"S" => 11, # Segment Separator
"WS" => 12, # Whitespace
"B" => 13, # Paragraph Separator
"RLO" => 14, # Right-to-Left Override
"RLE" => 15, # Right-to-Left Embedding
"LRO" => 16, # Left-to-Right Override
"LRE" => 17, # Left-to-Right Embedding
"PDF" => 18, # Pop Directional Format
"LRI" => 19, # Left-to-Right Isolate
"RLI" => 20, # Right-to-Left Isolate
"FSI" => 21, # First Strong Isolate
"PDI" => 22 # Pop Directional Isolate
);
my %joining_types =
(
"U" => 0, # Non_Joining
"L" => 1, # Left_Joining
"R" => 2, # Right_Joining
"D" => 3, # Dual_Joining
"C" => 3, # Join_Causing
"ALAPH" => 4, # Syriac ALAPH
"DALATH RISH" => 5, # Syriac DALATH RISH group
"T" => 6, # Transparent
);
my @locales =
(
{ name => "", lcid => 0x0000007f, file => "root", territory => "IV", sabbrevlangname => "IVL", sopentypelang =>"dflt" },
{ name => "aa", sopentypelang => "AFR" },
{ name => "aa-DJ" },
{ name => "aa-ER" },
{ name => "aa-ET" },
{ name => "af", lcid => 0x00000036, oemcp => 850, sabbrevlangname => "AFK", sopentypelang => "AFK" },
{ name => "af-NA" },
{ name => "af-ZA", lcid => 0x00000436 },
{ name => "agq" },
{ name => "agq-CM" },
{ name => "ak", sopentypelang => "TWI" },
{ name => "ak-GH" },
{ name => "am", lcid => 0x0000005e, sabbrevlangname => "AMH" },
{ name => "am-ET", lcid => 0x0000045e },
{ name => "ar", lcid => 0x00000001, territory => "SA", oemcp => 720, group => 13 },
{ name => "ar-001" },
{ name => "ar-AE", lcid => 0x00003801, sabbrevlangname => "ARU" },
{ name => "ar-BH", lcid => 0x00003c01, sabbrevlangname => "ARH" },
{ name => "ar-DJ" },
{ name => "ar-DZ", lcid => 0x00001401, sabbrevlangname => "ARG", nativedigits => "0123456789" },
{ name => "ar-EG", lcid => 0x00000c01, sabbrevlangname => "ARE" },
{ name => "ar-EH" },
{ name => "ar-ER" },
{ name => "ar-IL" },
{ name => "ar-IQ", lcid => 0x00000801, sabbrevlangname => "ARI" },
{ name => "ar-JO", lcid => 0x00002c01, sabbrevlangname => "ARJ" },
{ name => "ar-KM" },
{ name => "ar-KW", lcid => 0x00003401, sabbrevlangname => "ARK" },
{ name => "ar-LB", lcid => 0x00003001, sabbrevlangname => "ARB" },
{ name => "ar-LY", lcid => 0x00001001, sabbrevlangname => "ARL", nativedigits => "0123456789" },
{ name => "ar-MA", lcid => 0x00001801, sabbrevlangname => "ARM", nativedigits => "0123456789" },
{ name => "ar-MR" },
{ name => "ar-OM", lcid => 0x00002001, sabbrevlangname => "ARO" },
{ name => "ar-PS" },
{ name => "ar-QA", lcid => 0x00004001, sabbrevlangname => "ARQ" },
{ name => "ar-SA", lcid => 0x00000401, sabbrevlangname => "ARA" },
{ name => "ar-SD" },
{ name => "ar-SO" },
{ name => "ar-SS" },
{ name => "ar-SY", lcid => 0x00002801, sabbrevlangname => "ARS" },
{ name => "ar-TD" },
{ name => "ar-TN", lcid => 0x00001c01, sabbrevlangname => "ART", nativedigits => "0123456789" },
{ name => "ar-YE", lcid => 0x00002401, sabbrevlangname => "ARY" },
{ name => "arn", lcid => 0x0000007a, oemcp => 850, ebcdiccp => 20284, slist => ",", sabbrevlangname => "MPD", sopentypelang => "MAP" },
{ name => "arn-CL", lcid => 0x0000047a },
{ name => "arn-Latn", alias => "arn" },
{ name => "arn-Latn-CL", alias => "arn-CL" },
{ name => "as", lcid => 0x0000004d, slist => ",", group => 15 },
{ name => "as-IN", lcid => 0x0000044d },
{ name => "asa" },
{ name => "asa-TZ" },
{ name => "ast" },
{ name => "ast-ES" },
{ name => "az", lcid => 0x0000002c, oemcp => 857, ebcdiccp => 20905, group => 2 },
{ name => "az-Cyrl", lcid => 0x0000742c, oemcp => 866, ebcdiccp => 20880, group => 5, sabbrevlangname => "AZC" },
{ name => "az-Cyrl-AZ", lcid => 0x0000082c },
{ name => "az-Latn", lcid => 0x0000782c },
{ name => "az-Latn-AZ", lcid => 0x0000042c },
{ name => "ba", lcid => 0x0000006d, oemcp => 866, group => 5, sabbrevlangname => "BAS", sopentypelang => "BSH" },
{ name => "ba-Cyrl", alias => "ba" },
{ name => "ba-Cyrl-RU", alias => "ba-RU" },
{ name => "ba-RU", lcid => 0x0000046d },
{ name => "bas" },
{ name => "bas-CM" },
{ name => "be", lcid => 0x00000023, oemcp => 866, ebcdiccp => 500, group => 5 },
{ name => "be-BY", lcid => 0x00000423 },
{ name => "bem" },
{ name => "bem-ZM" },
{ name => "bew" },
{ name => "bew-ID" },
{ name => "bez" },
{ name => "bez-TZ" },
{ name => "bg", lcid => 0x00000002, oemcp => 866, ebcdiccp => 21025, group => 5, sabbrevlangname => "BGR", sopentypelang => "BGR" },
{ name => "bg-BG", lcid => 0x00000402 },
{ name => "bin", lcid => 0x00000066, oemcp => 850, dir => "exemplars", sabbrevlangname => "ZZZ", sopentypelang => "EDO" },
{ name => "bin-NG", lcid => 0x00000466, file => "bin", dir => "exemplars" },
{ name => "blo" },
{ name => "blo-BJ" },
{ name => "bm", sopentypelang => "BMB" },
{ name => "bm-Latn", file => "bm" },
{ name => "bm-Latn-ML", file => "bm_ML" },
{ name => "bm-ML", alias => "bm-Latn-ML" },
{ name => "bn", lcid => 0x00000045, slist => ",", group => 15, sabbrevlangname => "BNB" },
{ name => "bn-BD", lcid => 0x00000845 },
{ name => "bn-IN", lcid => 0x00000445, sabbrevlangname => "BNG" },
{ name => "bo", lcid => 0x00000051, slist => ",", group => 15, sabbrevlangname => "BOB", sopentypelang => "TIB" },
{ name => "bo-CN", lcid => 0x00000451 },
{ name => "bo-IN", slist => "," },
{ name => "bo-Tibt", alias => "bo" },
{ name => "bo-Tibt-CN", alias => "bo-CN" },
{ name => "bo-Tibt-IN", alias => "bo-IN" },
{ name => "br", lcid => 0x0000007e, oemcp => 850, ebcdiccp => 20297 },
{ name => "br-FR", lcid => 0x0000047e },
{ name => "br-Latn", alias => "br" },
{ name => "br-Latn-FR", alias => "br-FR" },
{ name => "brx" },
{ name => "brx-IN" },
{ name => "bs", lcid => 0x0000781a, oemcp => 852, maccp => 10082, ebcdiccp => 870, group => 2, sabbrevlangname => "BSB" },
{ name => "bs-Cyrl", lcid => 0x0000641a, oemcp => 855, group => 5, sabbrevlangname => "BSC" },
{ name => "bs-Cyrl-BA", lcid => 0x0000201a },
{ name => "bs-Latn", lcid => 0x0000681a },
{ name => "bs-Latn-BA", lcid => 0x0000141a },
{ name => "byn", sopentypelang => "BIL" },
{ name => "byn-ER" },
{ name => "ca", lcid => 0x00000003, oemcp => 850 },
{ name => "ca-AD", maccp => 65001 },
{ name => "ca-ES", lcid => 0x00000403 },
{ name => "ca-ES-valencia", lcid => 0x00000803, file => "ca_ES_VALENCIA", sabbrevlangname => "VAL" },
{ name => "ca-FR", maccp => 65001 },
{ name => "ca-IT", maccp => 65001 },
{ name => "ccp" },
{ name => "ccp-BD", alias => "ccp-Cakm-BD" },
{ name => "ccp-Cakm", file => "ccp" },
{ name => "ccp-Cakm-BD", file => "ccp_BD" },
{ name => "ccp-Cakm-IN", file => "ccp_IN" },
{ name => "ccp-IN", alias => "ccp-Cakm-IN" },
{ name => "ce" },
{ name => "ce-RU" },
{ name => "ceb" },
{ name => "ceb-Latn", file => "ceb" },
{ name => "ceb-Latn-PH", file => "ceb_PH" },
{ name => "ceb-PH", alias => "ceb-Latn-PH" },
{ name => "cgg" },
{ name => "cgg-UG" },
{ name => "chr", lcid => 0x0000005c, slist => ",", sabbrevlangname => "CRE" },
{ name => "chr-Cher", lcid => 0x00007c5c, file => "chr" },
{ name => "chr-Cher-US", lcid => 0x0000045c, file => "chr_US" },
{ name => "chr-US", alias => "chr-Cher-US" },
{ name => "ckb", alias => "ku" },
{ name => "ckb-IQ", alias => "ku-Arab-IQ" },
{ name => "ckb-IR", alias => "ku-Arab-IR" },
{ name => "co", lcid => 0x00000083, oemcp => 850, ebcdiccp => 20297 },
{ name => "co-FR", lcid => 0x00000483 },
{ name => "co-Latn", alias => "co" },
{ name => "co-Latn-FR", alias => "co-FR" },
{ name => "cs", lcid => 0x00000005, oemcp => 852, group => 2, sabbrevlangname => "CSY", sopentypelang => "CSY" },
{ name => "cs-CZ", lcid => 0x00000405 },
{ name => "csw" },
{ name => "csw-CA" },
{ name => "cu", sopentypelang => "CSL" },
{ name => "cu-RU" },
{ name => "cy", lcid => 0x00000052, oemcp => 850, ebcdiccp => 20285, sabbrevlangname => "CYM", sopentypelang => "WEL" },
{ name => "cy-GB", lcid => 0x00000452 },
{ name => "da", lcid => 0x00000006, oemcp => 850, ebcdiccp => 20277 },
{ name => "da-DK", lcid => 0x00000406 },
{ name => "da-GL", maccp => 65001 },
{ name => "dav" },
{ name => "dav-KE" },
{ name => "de", lcid => 0x00000007, oemcp => 850, ebcdiccp => 20273 },
{ name => "de-AT", lcid => 0x00000c07, sabbrevlangname => "DEA" },
{ name => "de-BE" },
{ name => "de-CH", lcid => 0x00000807, sabbrevlangname => "DES" },
{ name => "de-DE", lcid => 0x00000407 },
{ name => "de-DE_phoneb", lcid => 0x00010407, alias => "de-DE" },
{ name => "de-DE-u-co-phonebk", alias => "de-DE_phoneb" },
{ name => "de-IT", oemcp => 65001 },
{ name => "de-LI", lcid => 0x00001407, sabbrevlangname => "DEC" },
{ name => "de-LU", lcid => 0x00001007, sabbrevlangname => "DEL" },
{ name => "dje", sopentypelang => "DJR" },
{ name => "dje-NE" },
{ name => "doi", sopentypelang => "DGR" },
{ name => "doi-IN", alias => "doi-Deva-IN" },
{ name => "doi-Deva", file => "doi" },
{ name => "doi-Deva-IN", file => "doi_IN" },
{ name => "dsb", lcid => 0x00007c2e, sparent => "hsb", oemcp => 850, ebcdiccp => 870, sabbrevlangname => "DSB", sopentypelang => "LSB" },
{ name => "dsb-DE", lcid => 0x0000082e },
{ name => "dua" },
{ name => "dua-CM" },
{ name => "dv", lcid => 0x00000065, slist => "\x{060c}", group => 13, nativedigits => "0123456789" },
{ name => "dv-MV", lcid => 0x00000465 },
{ name => "dyo" },
{ name => "dyo-SN" },
{ name => "dz", sopentypelang => "DZN" },
{ name => "dz-BT", lcid => 0x00000c51, sabbrevlangname => "ZZZ" },
{ name => "ebu" },
{ name => "ebu-KE" },
{ name => "ee" },
{ name => "ee-GH" },
{ name => "ee-TG" },
{ name => "el", lcid => 0x00000008, oemcp => 737, group => 4 },
{ name => "el-CY" },
{ name => "el-GR", lcid => 0x00000408 },
{ name => "en", lcid => 0x00000009, oemcp => 437, slist => ",", sabbrevlangname => "ENU" },
{ name => "en-001", oemcp => 850 },
{ name => "en-029", lcid => 0x00002409, file => "en", oemcp => 850, sabbrevlangname => "ENB" },
{ name => "en-150", oemcp => 65001 },
{ name => "en-AE", lcid => 0x00004c09, oemcp => 65001, sabbrevlangname => "ZZZ" },
{ name => "en-AG", oemcp => 850 },
{ name => "en-AI", oemcp => 850 },
{ name => "en-AS", oemcp => 850 },
{ name => "en-AT", oemcp => 65001 },
{ name => "en-AU", lcid => 0x00000c09, oemcp => 850, sabbrevlangname => "ENA" },
{ name => "en-BB", oemcp => 850 },
{ name => "en-BE", oemcp => 850 },
{ name => "en-BI", oemcp => 65001 },
{ name => "en-BM", oemcp => 850 },
{ name => "en-BS", oemcp => 850 },
{ name => "en-BW", oemcp => 850 },
{ name => "en-BZ", lcid => 0x00002809, oemcp => 850, sabbrevlangname => "ENL" },
{ name => "en-CA", lcid => 0x00001009, oemcp => 850, ebcdiccp => 37, sabbrevlangname => "ENC" },
{ name => "en-CC", oemcp => 850 },
{ name => "en-CH", oemcp => 65001 },
{ name => "en-CK", oemcp => 850 },
{ name => "en-CM", oemcp => 850 },
{ name => "en-CX", oemcp => 850 },
{ name => "en-CY", oemcp => 65001 },
{ name => "en-DE", oemcp => 65001 },
{ name => "en-DG", oemcp => 850 },
{ name => "en-DK", oemcp => 65001 },
{ name => "en-DM", oemcp => 850 },
{ name => "en-ER", oemcp => 850 },
{ name => "en-FI", oemcp => 65001 },
{ name => "en-FJ", oemcp => 850 },
{ name => "en-FK", oemcp => 850 },
{ name => "en-FM", oemcp => 850 },
{ name => "en-GB", lcid => 0x00000809, oemcp => 850, ebcdiccp => 20285, sabbrevlangname => "ENG" },
{ name => "en-GD", oemcp => 850 },
{ name => "en-GG", oemcp => 850 },
{ name => "en-GH", oemcp => 850 },
{ name => "en-GI", oemcp => 850 },
{ name => "en-GM", oemcp => 850 },
{ name => "en-GU", oemcp => 850 },
{ name => "en-GY", oemcp => 850 },
{ name => "en-HK", lcid => 0x00003c09, oemcp => 850, sabbrevlangname => "ENH" },
{ name => "en-ID", lcid => 0x00003809, oemcp => 850, sabbrevlangname => "ZZZ" },
{ name => "en-IE", lcid => 0x00001809, oemcp => 850, sabbrevlangname => "ENI" },
{ name => "en-IL", oemcp => 65001 },
{ name => "en-IM", oemcp => 850 },
{ name => "en-IN", lcid => 0x00004009, sabbrevlangname => "ENN" },
{ name => "en-IO", oemcp => 850 },
{ name => "en-JE", oemcp => 850 },
{ name => "en-JM", lcid => 0x00002009, oemcp => 850, sabbrevlangname => "ENJ" },
{ name => "en-KE", oemcp => 850 },
{ name => "en-KI", oemcp => 850 },
{ name => "en-KN", oemcp => 850 },
{ name => "en-KY", oemcp => 850 },
{ name => "en-LC", oemcp => 850 },
{ name => "en-LR", oemcp => 850 },
{ name => "en-LS", oemcp => 850 },
{ name => "en-MG", oemcp => 850 },
{ name => "en-MH", oemcp => 850 },
{ name => "en-MO", oemcp => 850 },
{ name => "en-MP", oemcp => 850 },
{ name => "en-MS", oemcp => 850 },
{ name => "en-MT", oemcp => 850 },
{ name => "en-MU", oemcp => 850 },
{ name => "en-MW", oemcp => 850 },
{ name => "en-MY", lcid => 0x00004409, sabbrevlangname => "ENM" },
{ name => "en-NA", oemcp => 850 },
{ name => "en-NF", oemcp => 850 },
{ name => "en-NG", oemcp => 850 },
{ name => "en-NL", oemcp => 65001 },
{ name => "en-NR", oemcp => 850 },
{ name => "en-NU", oemcp => 850 },
{ name => "en-NZ", lcid => 0x00001409, oemcp => 850, sabbrevlangname => "ENZ" },
{ name => "en-PG", oemcp => 850 },
{ name => "en-PH", lcid => 0x00003409, ebcdiccp => 500, sabbrevlangname => "ENP" },
{ name => "en-PK", oemcp => 850 },
{ name => "en-PN", oemcp => 850 },
{ name => "en-PR", oemcp => 850 },
{ name => "en-PW", oemcp => 850 },
{ name => "en-RW", oemcp => 850 },
{ name => "en-SB", oemcp => 850 },
{ name => "en-SC", oemcp => 850 },
{ name => "en-SD", oemcp => 850 },
{ name => "en-SE", oemcp => 65001 },
{ name => "en-SG", lcid => 0x00004809, sabbrevlangname => "ENE" },
{ name => "en-SH", oemcp => 850 },
{ name => "en-SI", oemcp => 65001 },
{ name => "en-SL", oemcp => 850 },
{ name => "en-SS", oemcp => 850 },
{ name => "en-SX", oemcp => 850 },
{ name => "en-SZ", oemcp => 850 },
{ name => "en-TC", oemcp => 850 },
{ name => "en-TK", oemcp => 850 },
{ name => "en-TO", oemcp => 850 },
{ name => "en-TT", lcid => 0x00002c09, oemcp => 850, sabbrevlangname => "ENT" },
{ name => "en-TV", oemcp => 850 },
{ name => "en-TZ", oemcp => 850 },
{ name => "en-UG", oemcp => 850 },
{ name => "en-UM", oemcp => 850 },
{ name => "en-US", lcid => 0x00000409 },
{ name => "en-VC", oemcp => 850 },
{ name => "en-VG", oemcp => 850 },
{ name => "en-VI", oemcp => 850 },
{ name => "en-VU", oemcp => 850 },
{ name => "en-WS", oemcp => 850 },
{ name => "en-ZA", lcid => 0x00001c09, ebcdiccp => 500, sabbrevlangname => "ENS" },
{ name => "en-ZM", oemcp => 850 },
{ name => "en-ZW", lcid => 0x00003009, ebcdiccp => 500, sabbrevlangname => "ENW" },
{ name => "eo", sopentypelang => "NTO" },
{ name => "eo-001" },
{ name => "es", lcid => 0x0000000a, oemcp => 850, ebcdiccp => 20284, sabbrevlangname => "ESP", sopentypelang => "ESP" },
{ name => "es-419", lcid => 0x0000580a, sabbrevlangname => "ESJ" },
{ name => "es-AR", lcid => 0x00002c0a, sabbrevlangname => "ESS" },
{ name => "es-BO", lcid => 0x0000400a, sabbrevlangname => "ESB" },
{ name => "es-BR", oemcp => 65001 },
{ name => "es-BZ", oemcp => 65001 },
{ name => "es-CL", lcid => 0x0000340a, sabbrevlangname => "ESL" },
{ name => "es-CO", lcid => 0x0000240a, sabbrevlangname => "ESO" },
{ name => "es-CR", lcid => 0x0000140a, sabbrevlangname => "ESC" },
{ name => "es-CU", lcid => 0x00005c0a, sabbrevlangname => "ESK" },
{ name => "es-DO", lcid => 0x00001c0a, sabbrevlangname => "ESD" },
{ name => "es-EA" },
{ name => "es-EC", lcid => 0x0000300a, sabbrevlangname => "ESF" },
{ name => "es-ES", lcid => 0x00000c0a, sabbrevlangname => "ESN" },
{ name => "es-ES_tradnl", lcid => 0x0000040a, file => "es_ES" },
{ name => "es-ES-u-co-trad", alias => "es-ES_tradnl" },
{ name => "es-GQ" },
{ name => "es-GT", lcid => 0x0000100a, sabbrevlangname => "ESG" },
{ name => "es-HN", lcid => 0x0000480a, sabbrevlangname => "ESH" },
{ name => "es-IC" },
{ name => "es-MX", lcid => 0x0000080a, sabbrevlangname => "ESM" },
{ name => "es-NI", lcid => 0x00004c0a, sabbrevlangname => "ESI" },
{ name => "es-PA", lcid => 0x0000180a, sabbrevlangname => "ESA" },
{ name => "es-PE", lcid => 0x0000280a, sabbrevlangname => "ESR" },
{ name => "es-PH" },
{ name => "es-PR", lcid => 0x0000500a, sabbrevlangname => "ESU" },
{ name => "es-PY", lcid => 0x00003c0a, sabbrevlangname => "ESZ" },
{ name => "es-SV", lcid => 0x0000440a, sabbrevlangname => "ESE" },
{ name => "es-US", lcid => 0x0000540a, sabbrevlangname => "EST" },
{ name => "es-UY", lcid => 0x0000380a, sabbrevlangname => "ESY" },
{ name => "es-VE", lcid => 0x0000200a, sabbrevlangname => "ESV" },
{ name => "et", lcid => 0x00000025, oemcp => 775, group => 3, sabbrevlangname => "ETI", sopentypelang => "ETI" },
{ name => "et-EE", lcid => 0x00000425 },
{ name => "eu", lcid => 0x0000002d, oemcp => 850, maccp => 65001, sabbrevlangname => "EUQ", sopentypelang => "EUQ" },
{ name => "eu-ES", lcid => 0x0000042d },
{ name => "ewo" },
{ name => "ewo-CM" },
{ name => "fa", lcid => 0x00000029, inegnumber => 3, oemcp => 720, slist => "\x{061b}", group => 13, sabbrevlangname => "FAR", sopentypelang => "FAR" },
{ name => "fa-AF", alias => "prs-AF" },
{ name => "fa-IR", lcid => 0x00000429 },
{ name => "ff", lcid => 0x00000067, oemcp => 850, ebcdiccp => 20297 },
{ name => "ff-CM", alias => "ff-Latn-CM" },
{ name => "ff-GN", alias => "ff-Latn-GN" },
{ name => "ff-MR", alias => "ff-Latn-MR" },
{ name => "ff-NG", alias => "ff-Latn-NG" },
{ name => "ff-SN", alias => "ff-Latn-SN" },
{ name => "ff-Adlm", oemcp => 65001 },
{ name => "ff-Adlm-BF" },
{ name => "ff-Adlm-CM" },
{ name => "ff-Adlm-GH" },
{ name => "ff-Adlm-GM" },
{ name => "ff-Adlm-GN" },
{ name => "ff-Adlm-GW" },
{ name => "ff-Adlm-LR" },
{ name => "ff-Adlm-MR" },
{ name => "ff-Adlm-NE" },
{ name => "ff-Adlm-NG" },
{ name => "ff-Adlm-SL" },
{ name => "ff-Adlm-SN" },
{ name => "ff-Latn", lcid => 0x00007c67 },
{ name => "ff-Latn-BF", oemcp => 65001 },
{ name => "ff-Latn-CM" },
{ name => "ff-Latn-GH", oemcp => 65001 },
{ name => "ff-Latn-GM", oemcp => 65001 },
{ name => "ff-Latn-GN" },
{ name => "ff-Latn-GW", oemcp => 65001 },
{ name => "ff-Latn-LR", oemcp => 65001 },
{ name => "ff-Latn-MR" },
{ name => "ff-Latn-NE", oemcp => 65001 },
{ name => "ff-Latn-NG", lcid => 0x00000467, sabbrevlangname => "ZZZ" },
{ name => "ff-Latn-SL", oemcp => 65001 },
{ name => "ff-Latn-SN", lcid => 0x00000867 },
{ name => "fi", lcid => 0x0000000b, oemcp => 850, ebcdiccp => 20278 },
{ name => "fi-FI", lcid => 0x0000040b },
{ name => "fil", lcid => 0x00000064, oemcp => 437, ebcdiccp => 500, sabbrevlangname => "FPO", sopentypelang => "PIL" },
{ name => "fil-PH", lcid => 0x00000464 },
{ name => "fil-Latn", alias => "fil" },
{ name => "fil-Latn-PH", alias => "fil-PH" },
{ name => "fo", lcid => 0x00000038, oemcp => 850, maccp => 10079, ebcdiccp => 20277, sabbrevlangname => "FOS", sopentypelang => "FOS" },
{ name => "fo-DK", oemcp => 65001, maccp => 65001 },
{ name => "fo-FO", lcid => 0x00000438 },
{ name => "fr", lcid => 0x0000000c, oemcp => 850, ebcdiccp => 20297 },
{ name => "fr-029", lcid => 0x00001c0c, file => "fr", sabbrevlangname => "ZZZ" },
{ name => "fr-BE", lcid => 0x0000080c, sabbrevlangname => "FRB" },
{ name => "fr-BF" },
{ name => "fr-BI" },
{ name => "fr-BJ" },
{ name => "fr-BL" },
{ name => "fr-CA", lcid => 0x00000c0c, sabbrevlangname => "FRC" },
{ name => "fr-CD", lcid => 0x0000240c, sabbrevlangname => "FRD" },
{ name => "fr-CF" },
{ name => "fr-CG" },
{ name => "fr-CH", lcid => 0x0000100c, sabbrevlangname => "FRS" },
{ name => "fr-CI", lcid => 0x0000300c, sabbrevlangname => "FRI" },
{ name => "fr-CM", lcid => 0x00002c0c, sabbrevlangname => "FRE" },
{ name => "fr-DJ" },
{ name => "fr-DZ" },
{ name => "fr-FR", lcid => 0x0000040c },
{ name => "fr-GA" },
{ name => "fr-GF" },
{ name => "fr-GN" },
{ name => "fr-GP" },
{ name => "fr-GQ" },
{ name => "fr-HT", lcid => 0x00003c0c, sabbrevlangname => "FRH" },
{ name => "fr-KM" },
{ name => "fr-LU", lcid => 0x0000140c, sabbrevlangname => "FRL" },
{ name => "fr-MA", lcid => 0x0000380c, sabbrevlangname => "FRO" },
{ name => "fr-MC", lcid => 0x0000180c, sabbrevlangname => "FRM" },
{ name => "fr-MF" },
{ name => "fr-MG" },
{ name => "fr-ML", lcid => 0x0000340c, sabbrevlangname => "FRF" },
{ name => "fr-MQ" },
{ name => "fr-MR" },
{ name => "fr-MU" },
{ name => "fr-NC" },
{ name => "fr-NE" },
{ name => "fr-PF" },
{ name => "fr-PM" },
{ name => "fr-RE", lcid => 0x0000200c, sabbrevlangname => "FRR" },
{ name => "fr-RW" },
{ name => "fr-SC" },
{ name => "fr-SN", lcid => 0x0000280c, sabbrevlangname => "FRN" },
{ name => "fr-SY" },
{ name => "fr-TD" },
{ name => "fr-TG" },
{ name => "fr-TN" },
{ name => "fr-VU" },
{ name => "fr-WF" },
{ name => "fr-YT" },
{ name => "fur", sopentypelang => "FRL" },
{ name => "fur-IT" },
{ name => "fuv-NG", alias => "ff-Latn-NG" },
{ name => "fy", lcid => 0x00000062, oemcp => 850, sabbrevlangname => "FYN", sopentypelang => "FRI" },
{ name => "fy-NL", lcid => 0x00000462 },
{ name => "ga", lcid => 0x0000003c, oemcp => 850, sabbrevlangname => "IRE", sopentypelang => "IRI" },
{ name => "ga-GB", oemcp => 65001 },
{ name => "ga-IE", lcid => 0x0000083c },
{ name => "gd", lcid => 0x00000091, oemcp => 850, ebcdiccp => 20285, sopentypelang => "GAE" },
{ name => "gd-GB", lcid => 0x00000491 },
{ name => "gd-Latn", alias => "gd" },
{ name => "gl", lcid => 0x00000056, oemcp => 850, sabbrevlangname => "GLC", sopentypelang => "GAL" },
{ name => "gl-ES", lcid => 0x00000456 },
{ name => "gn", lcid => 0x00000074, oemcp => 850, ebcdiccp => 20284, slist => ",", sopentypelang => "GUA" },
{ name => "gn-PY", lcid => 0x00000474 },
{ name => "gsw", lcid => 0x00000084, oemcp => 850, ebcdiccp => 20297, sabbrevlangname => "ZZZ", sopentypelang => "ALS" },
{ name => "gsw-CH" },
{ name => "gsw-FR", lcid => 0x00000484, sabbrevlangname => "GSW" },
{ name => "gsw-LI" },
{ name => "gu", lcid => 0x00000047, slist => ",", group => 15 },
{ name => "gu-IN", lcid => 0x00000447 },
{ name => "guz" },
{ name => "guz-KE" },
{ name => "gv", sopentypelang => "MNX" },
{ name => "gv-GB", file => "gv" },
{ name => "gv-IM" },
{ name => "ha", lcid => 0x00000068, oemcp => 437 },
{ name => "ha-GH", alias => "ha-Latn-GH" },
{ name => "ha-Latn", lcid => 0x00007c68, file => "ha" },
{ name => "ha-Latn-GH", file => "ha_GH", ebcdiccp => 500 },
{ name => "ha-Latn-NE", file => "ha_NE", ebcdiccp => 500 },
{ name => "ha-Latn-NG", lcid => 0x00000468, file => "ha_NG" },
{ name => "ha-NE", alias => "ha-Latn-NE" },
{ name => "ha-NG", alias => "ha-Latn-NG" },
{ name => "haw", lcid => 0x00000075, oemcp => 437 },
{ name => "haw-Latn", alias => "haw" },
{ name => "haw-Latn-US", alias => "haw-US" },
{ name => "haw-US", lcid => 0x00000475 },
{ name => "he", lcid => 0x0000000d, oemcp => 862, slist => ",", group => 12, sopentypelang => "IWR" },
{ name => "he-IL", lcid => 0x0000040d },
{ name => "hi", lcid => 0x00000039, slist => ",", group => 15 },
{ name => "hi-IN", lcid => 0x00000439 },
{ name => "hr", lcid => 0x0000001a, inegnumber => 2, oemcp => 852, maccp => 10082, group => 2 },
{ name => "hr-BA", lcid => 0x0000101a, ebcdiccp => 870, inegnumber => 1, sabbrevlangname => "HRB" },
{ name => "hr-HR", lcid => 0x0000041a },
{ name => "hsb", lcid => 0x0000002e, oemcp => 850, ebcdiccp => 870, sopentypelang => "USB" },
{ name => "hsb-DE", lcid => 0x0000042e },
{ name => "hu", lcid => 0x0000000e, oemcp => 852, group => 2 },
{ name => "hu-HU", lcid => 0x0000040e },
{ name => "hu-HU_technl", lcid => 0x0001040e, alias => "hu-HU" },
{ name => "hy", lcid => 0x0000002b, slist => ",", group => 17 },
{ name => "hy-AM", lcid => 0x0000042b },
{ name => "ia" },
{ name => "ia-001" },
## name => "ibb", lcid => 0x00000069 },
## name => "ibb-NG", lcid => 0x00000469 },
{ name => "id", lcid => 0x00000021, oemcp => 850 },
{ name => "id-ID", lcid => 0x00000421 },
{ name => "ie" },
{ name => "ie-EE" },
{ name => "ig", lcid => 0x00000070, oemcp => 437 },
{ name => "ig-Latn", alias => "ig" },
{ name => "ig-Latn-NG", alias => "ig-NG" },
{ name => "ig-NG", lcid => 0x00000470 },
{ name => "ii", lcid => 0x00000078, group => 9, sopentypelang => "YIM" },
{ name => "ii-CN", lcid => 0x00000478 },
{ name => "ii-Yiii", alias => "ii" },
{ name => "ii-Yiii-CN", alias => "ii-CN" },
{ name => "is", lcid => 0x0000000f, oemcp => 850, maccp => 10079, ebcdiccp => 20871 },
{ name => "is-IS", lcid => 0x0000040f },
{ name => "it", lcid => 0x00000010, oemcp => 850, ebcdiccp => 20280 },
{ name => "it-CH", lcid => 0x00000810, ebcdiccp => 500, sabbrevlangname => "ITS" },
{ name => "it-IT", lcid => 0x00000410 },
{ name => "it-SM" },
{ name => "it-VA", oemcp => 65001 },
{ name => "iu", lcid => 0x0000005d, oemcp => 437, slist => ",", sortlocale => "iu-Latn-CA", sabbrevlangname => "IUK", sopentypelang => "INU" },
{ name => "iu-Cans", lcid => 0x0000785d, file => "iu", oemcp => 65001, sabbrevlangname => "IUS" },
{ name => "iu-Cans-CA", lcid => 0x0000045d, file => "iu_CA" },
{ name => "iu-Latn", lcid => 0x00007c5d },
{ name => "iu-Latn-CA", lcid => 0x0000085d },
{ name => "ja", lcid => 0x00000011, ireadinglayout => 2, oemcp => 932, slist => ",", sscripts => "Hani Hira Jpan Kana", group => 7, sopentypelang => "JAN" },
{ name => "ja-JP", lcid => 0x00000411 },
{ name => "ja-JP_radstr", lcid => 0x00040411, alias => "ja-JP" },
{ name => "ja-JP-u-co-unihan", alias => "ja-JP_radstr" },
{ name => "jgo" },
{ name => "jgo-CM" },
{ name => "jmc" },
{ name => "jmc-TZ" },
{ name => "jv", oemcp => 850, nativedigits => "0123456789" },
{ name => "jv-ID", alias => "jv-Latn-ID" },
## name => "jv-Java" },
## name => "jv-Java-ID" },
{ name => "jv-Latn", file => "jv" },
{ name => "jv-Latn-ID", file => "jv_ID" },
{ name => "ka", lcid => 0x00000037, group => 16 },
{ name => "ka-GE", lcid => 0x00000437 },
{ name => "ka-GE_modern", lcid => 0x00010437, alias => "ka-GE" },
{ name => "kab", sopentypelang => "KAB0" },
{ name => "kab-DZ" },
{ name => "kam", sopentypelang => "KMB" },
{ name => "kam-KE" },
{ name => "kde" },
{ name => "kde-TZ" },
{ name => "kea" },
{ name => "kea-CV" },
{ name => "kgp" },
{ name => "kgp-BR" },
{ name => "khq" },
{ name => "khq-ML" },
{ name => "ki" },
{ name => "ki-KE" },
{ name => "kk", lcid => 0x0000003f, group => 5, sabbrevlangname => "KKZ" },
{ name => "kk-Cyrl", alias => "kk" },
{ name => "kk-Cyrl-KZ", alias => "kk-KZ" },
{ name => "kk-KZ", lcid => 0x0000043f },
{ name => "kkj" },
{ name => "kkj-CM" },
{ name => "kl", lcid => 0x0000006f, oemcp => 850, ebcdiccp => 20277, sopentypelang => "GRN" },
{ name => "kl-GL", lcid => 0x0000046f },
{ name => "kln", sopentypelang => "KAL" },
{ name => "kln-KE" },
{ name => "km", lcid => 0x00000053, inegnumber => 2, slist => ",", group => 15 },
{ name => "km-KH", lcid => 0x00000453 },
{ name => "kn", lcid => 0x0000004b, slist => ",", group => 15, sabbrevlangname => "KDI" },
{ name => "kn-IN", lcid => 0x0000044b },
{ name => "ko", lcid => 0x00000012, ireadinglayout => 2, slist => ",", oemcp => 949, ebcdiccp => 20833, sscripts => "Hang Hani Kore", group => 8 },
{ name => "ko-KP", oemcp => 65001 },
{ name => "ko-KR", lcid => 0x00000412 },
{ name => "kok", lcid => 0x00000057, slist => ",", group => 15, sabbrevlangname => "KNK" },
{ name => "kok-IN", lcid => 0x00000457 },
{ name => "kr", lcid => 0x00000071, sortlocale => "kr-Latn-NG", oemcp => 850, dir => "exemplars", sabbrevlangname => "ZZZ", sopentypelang => "KNR" },
{ name => "kr-Latn", file => "kr", dir => "exemplars" },
{ name => "kr-Latn-NG", lcid => 0x00000471, file => "kr", dir => "exemplars" },
{ name => "kr-NG", alias => "kr-Latn-NG" },
{ name => "ks", lcid => 0x00000060, group => 15, sabbrevlangname => "ZZZ", sopentypelang => "KSH" },
{ name => "ks-Arab", lcid => 0x00000460 },
{ name => "ks-Arab-IN" },
{ name => "ks-Deva", slist => "," },
{ name => "ks-Deva-IN", lcid => 0x00000860 },
{ name => "ks-IN", alias => "ks-Arab-IN" },
{ name => "ksb" },
{ name => "ksb-TZ" },
{ name => "ksf" },
{ name => "ksf-CM" },
{ name => "ksh", sopentypelang => "KSH0" },
{ name => "ksh-DE" },
{ name => "ku", lcid => 0x00000092, file => "ckb", slist => "\x{061b}", sortlocale => "ku-Arab-IQ", oemcp => 720 },
{ name => "ku-Arab", lcid => 0x00007c92, file => "ckb", group => 13 },
{ name => "ku-Arab-IQ", lcid => 0x00000492, file => "ckb_IQ" },
{ name => "ku-Arab-IR", file => "ckb_IR", oemcp => 65001 },
{ name => "kw" },
{ name => "kw-GB" },
{ name => "ky", lcid => 0x00000040, oemcp => 866, group => 5, sabbrevlangname => "KYR" },
{ name => "ky-Cyrl", alias => "ky" },
{ name => "ky-Cyrl-KG", alias => "ky-KG" },
{ name => "ky-KG", lcid => 0x00000440 },
{ name => "la", lcid => 0x00000076, oemcp => 437, slist => ",", sabbrevlangname => "ZZZ" },
{ name => "la-VA", lcid => 0x00000476 },
{ name => "la-001", alias => "la-VA" },
{ name => "lag" },
{ name => "lag-TZ" },
{ name => "lb", lcid => 0x0000006e, oemcp => 850, ebcdiccp => 20297, sabbrevlangname => "LBX" },
{ name => "lb-LU", lcid => 0x0000046e },
{ name => "lg" },
{ name => "lg-UG" },
{ name => "lkt" },
{ name => "lkt-US" },
{ name => "ln" },
{ name => "ln-AO" },
{ name => "ln-CD" },
{ name => "ln-CF" },
{ name => "ln-CG" },
{ name => "lo", lcid => 0x00000054, group => 15 },
{ name => "lo-LA", lcid => 0x00000454 },
{ name => "lrc" },
{ name => "lrc-IQ" },
{ name => "lrc-IR" },
{ name => "lt", lcid => 0x00000027, oemcp => 775, group => 3, sabbrevlangname => "LTH", sopentypelang => "LTH" },
{ name => "lt-LT", lcid => 0x00000427 },
{ name => "lu" },
{ name => "lu-CD" },
{ name => "luo" },
{ name => "luo-KE" },
{ name => "luy", sopentypelang => "LUH" },
{ name => "luy-KE" },
{ name => "lv", lcid => 0x00000026, oemcp => 775, group => 3, sabbrevlangname => "LVI", sopentypelang => "LVI" },
{ name => "lv-LV", lcid => 0x00000426 },
{ name => "mai" },
{ name => "mai-IN" },
{ name => "mas" },
{ name => "mas-KE" },
{ name => "mas-TZ" },
{ name => "mer" },
{ name => "mer-KE" },
{ name => "mfe" },
{ name => "mfe-MU" },
{ name => "mg" },
{ name => "mg-MG" },
{ name => "mgh" },
{ name => "mgh-MZ" },
{ name => "mgo" },
{ name => "mgo-CM" },
{ name => "mi", lcid => 0x00000081, slist => "," },
{ name => "mi-Latn", alias => "mi" },
{ name => "mi-Latn-NZ", alias => "mi-NZ" },
{ name => "mi-NZ", lcid => 0x00000481 },
{ name => "mic" },
{ name => "mic-CA" },
{ name => "mk", lcid => 0x0000002f, oemcp => 866, ebcdiccp => 500, group => 5, sabbrevlangname => "MKI" },
{ name => "mk-MK", lcid => 0x0000042f },
{ name => "ml", lcid => 0x0000004c, group => 15, sabbrevlangname => "MYM", sopentypelang => "MLR" },
{ name => "ml-IN", lcid => 0x0000044c },
{ name => "mn", lcid => 0x00000050, oemcp => 866, sopentypelang => "MNG" },
{ name => "mn-Cyrl", lcid => 0x00007850, file => "mn", sabbrevlangname => "MNN" },
{ name => "mn-Cyrl-MN", alias => "mn-MN" },
{ name => "mn-MN", lcid => 0x00000450, sparent => "mn-Cyrl", group => 5 },
{ name => "mn-Mong", lcid => 0x00007c50, oemcp => 65001, slist => ",", group => 15, sabbrevlangname => "MNG", nativedigits => "0123456789" },
{ name => "mn-Mong-CN", lcid => 0x00000850 },
{ name => "mn-Mong-MN", lcid => 0x00000c50, sabbrevlangname => "MNM" },
{ name => "mni", lcid => 0x00000058, slist => ",", sabbrevlangname => "ZZZ" },
{ name => "mni-IN", lcid => 0x00000458, file => "mni_Beng_IN" },
{ name => "mni-Beng" },
{ name => "mni-Beng-IN", alias => "mni-IN" },
{ name => "moh", lcid => 0x0000007c, oemcp => 850, ebcdiccp => 37, slist => ",", sabbrevlangname => "MWK" },
{ name => "moh-CA", lcid => 0x0000047c },
{ name => "moh-Latn", alias => "moh" },
{ name => "moh-Latn-CA", alias => "moh-CA" },
{ name => "mr", lcid => 0x0000004e, slist => ",", group => 15 },
{ name => "mr-IN", lcid => 0x0000044e },
{ name => "ms", lcid => 0x0000003e, oemcp => 850, sabbrevlangname => "MSL", sopentypelang => "MLY" },
{ name => "ms-BN", lcid => 0x0000083e, sabbrevlangname => "MSB" },
{ name => "ms-ID" },
{ name => "ms-Latn", alias => "ms" },
{ name => "ms-Latn-BN", alias => "ms-BN" },
{ name => "ms-Latn-MY", alias => "ms-MY" },
{ name => "ms-Latn-SG", alias => "ms-SG" },
{ name => "ms-MY", lcid => 0x0000043e },
{ name => "ms-SG" },
{ name => "mt", lcid => 0x0000003a, sopentypelang => "MTS" },
{ name => "mt-MT", lcid => 0x0000043a },
{ name => "mua" },
{ name => "mua-CM" },
{ name => "my", lcid => 0x00000055, sopentypelang => "BRM" },
{ name => "my-MM", lcid => 0x00000455 },
{ name => "mzn" },
{ name => "mzn-IR" },
{ name => "naq" },
{ name => "naq-NA" },
{ name => "nb", lcid => 0x00007c14, oemcp => 850, ebcdiccp => 20277, sabbrevlangname => "NOR", sopentypelang => "NOR" },
{ name => "nb-NO", lcid => 0x00000414 },
{ name => "nb-SJ" },
{ name => "nd", sopentypelang => "NDB" },
{ name => "nd-ZW" },
{ name => "nds" },
{ name => "nds-DE" },
{ name => "nds-NL" },
{ name => "ne", lcid => 0x00000061, slist => "," },
{ name => "ne-IN", lcid => 0x00000861, sabbrevlangname => "NEI" },
{ name => "ne-NP", lcid => 0x00000461, group => 15 },
{ name => "nl", lcid => 0x00000013, oemcp => 850 },
{ name => "nl-AW" },
{ name => "nl-BE", lcid => 0x00000813, sabbrevlangname => "NLB" },
{ name => "nl-BQ" },
{ name => "nl-CW" },
{ name => "nl-NL", lcid => 0x00000413 },
{ name => "nl-SR" },
{ name => "nl-SX" },
{ name => "nmg" },
{ name => "nmg-CM" },
{ name => "nn", lcid => 0x00007814, oemcp => 850, ebcdiccp => 20277, sabbrevlangname => "NON", sopentypelang => "NYN" },
{ name => "nn-NO", lcid => 0x00000814 },
{ name => "nnh" },
{ name => "nnh-CM" },
{ name => "no", lcid => 0x00000014, oemcp => 850, ebcdiccp => 20277, sortlocale => "nb-NO" },
{ name => "nqo", idigits => 3, inegnumber => 3, slist => "\x{060c}", sopentypelang => "NKO" },
{ name => "nqo-GN" },
{ name => "nr", sopentypelang => "NDB" },
{ name => "nr-ZA" },
{ name => "nso", lcid => 0x0000006c, oemcp => 850, sopentypelang => "SOT" },
{ name => "nso-ZA", lcid => 0x0000046c },
{ name => "nus" },
{ name => "nus-SD", alias => "nus-SS" },
{ name => "nus-SS" },
{ name => "nyn", sopentypelang => "NKL" },
{ name => "nyn-UG" },
{ name => "oc", lcid => 0x00000082, oemcp => 850, ebcdiccp => 20297 },
{ name => "oc-FR", lcid => 0x00000482 },
{ name => "oc-Latn", alias => "oc" },
{ name => "oc-Latn-FR", alias => "oc-FR" },
{ name => "om", lcid => 0x00000072, sopentypelang => "ORO" },
{ name => "om-ET", lcid => 0x00000472 },
{ name => "om-KE" },
{ name => "or", lcid => 0x00000048, slist => ",", group => 15 },
{ name => "or-IN", lcid => 0x00000448 },
{ name => "os" },
{ name => "os-GE" },
{ name => "os-RU" },
{ name => "pa", lcid => 0x00000046, slist => "," },
{ name => "pa-Arab", lcid => 0x00007c46, slist => ";", inegnumber => 2, oemcp => 720, group => 13, sabbrevlangname => "PAP" },
{ name => "pa-Arab-PK", lcid => 0x00000846 },
{ name => "pa-Guru" },
{ name => "pa-Guru-IN", alias => "pa-IN" },
{ name => "pa-IN", lcid => 0x00000446, sparent => "pa-Guru", file => "pa_Guru_IN", group => 15 },
{ name => "pap", lcid => 0x00000079, oemcp => 850, sopentypelang => "PAP0" },
## name => "pap-029", lcid => 0x00000479 },
{ name => "pcm" },
{ name => "pcm-NG", alias => "pcm-Latn-NG" },
{ name => "pcm-Latn", file => "pcm" },
{ name => "pcm-Latn-NG", file => "pcm_NG" },
{ name => "pl", lcid => 0x00000015, oemcp => 852, ebcdiccp => 20880, group => 2, sabbrevlangname => "PLK", sopentypelang => "PLK" },
{ name => "pl-PL", lcid => 0x00000415 },
{ name => "prg" },
{ name => "prg-001", file => "prg" },
{ name => "prg-PL" },
{ name => "prs", lcid => 0x0000008c, file => "fa", inegnumber => 3, oemcp => 720, group => 13, sopentypelang => "DRI" },
{ name => "prs-AF", lcid => 0x0000048c, file => "fa_AF" },
{ name => "prs-Arab", alias => "prs" },
{ name => "prs-Arab-AF", alias => "prs-AF" },
{ name => "ps", lcid => 0x00000063, group => 13, sabbrevlangname => "PAS", sopentypelang => "PAS" },
{ name => "ps-AF", lcid => 0x00000463 },
{ name => "ps-PK" },
{ name => "pt", lcid => 0x00000016, oemcp => 850, sabbrevlangname => "PTB", sopentypelang => "PTG" },
{ name => "pt-AO" },
{ name => "pt-BR", lcid => 0x00000416 },
{ name => "pt-CH", oemcp => 65001 },
{ name => "pt-CV" },
{ name => "pt-GQ", oemcp => 65001 },
{ name => "pt-GW" },
{ name => "pt-LU", oemcp => 65001 },
{ name => "pt-MO" },
{ name => "pt-MZ" },
{ name => "pt-PT", lcid => 0x00000816, sabbrevlangname => "PTG" },
{ name => "pt-ST" },
{ name => "pt-TL" },
## name => qps-Latn-x-sh", lcid => 0x80000901 },
## name => qps-ploc", lcid => 0x80000501 },
## name => qps-ploca", lcid => 0x800005fe },
## name => qps-plocm", lcid => 0x800009ff },
{ name => "qu", alias => "quz" },
{ name => "qu-BO", alias => "quz-BO" },
{ name => "qu-EC", alias => "quz-EC" },
{ name => "qu-PE", alias => "quz-PE" },
{ name => "quc", lcid => 0x00000086, oemcp => 850, ebcdiccp => 20284, slist => "," },
{ name => "quc-Latn", lcid => 0x00007c86, file => "quc" },
{ name => "quc-Latn-GT", lcid => 0x00000486, file => "quc_GT" },
{ name => "qut", alias => "quc" },
{ name => "qut-GT", alias => "quc-Latn-GT" },
{ name => "quz", lcid => 0x0000006b, file => "qu", territory => "BO", oemcp => 850, ebcdiccp => 20284, slist => "," },
{ name => "quz-BO", lcid => 0x0000046b, file => "qu_BO" },
{ name => "quz-EC", lcid => 0x0000086b, file => "qu_EC" },
{ name => "quz-Latn", alias => "quz" },
{ name => "quz-Latn-BO", alias => "quz-BO" },
{ name => "quz-Latn-EC", alias => "quz-EC" },
{ name => "quz-Latn-PE", alias => "quz-PE" },
{ name => "quz-PE", lcid => 0x00000c6b, file => "qu_PE" },
{ name => "rm", lcid => 0x00000017, oemcp => 850, ebcdiccp => 20273, sabbrevlangname => "RMC", sopentypelang => "RMS" },
{ name => "rm-CH", lcid => 0x00000417 },
{ name => "rn" },
{ name => "rn-BI" },
{ name => "ro", lcid => 0x00000018, oemcp => 852, ebcdiccp => 20880, sabbrevlangname => "ROM", sopentypelang => "ROM" },
{ name => "ro-MD", lcid => 0x00000818, maccp => 65001, sabbrevlangname => "ROD" },
{ name => "ro-RO", lcid => 0x00000418, group => 2 },
{ name => "rof" },
{ name => "rof-TZ" },
{ name => "ru", lcid => 0x00000019, oemcp => 866 },
{ name => "ru-BY", maccp => 65001 },
{ name => "ru-KG", maccp => 65001 },
{ name => "ru-KZ", maccp => 65001 },
{ name => "ru-MD", lcid => 0x00000819, maccp => 65001, sabbrevlangname => "RUM" },
{ name => "ru-RU", lcid => 0x00000419, group => 5 },
{ name => "ru-UA", maccp => 65001 },
{ name => "rw", lcid => 0x00000087, oemcp => 437, sopentypelang => "RUA" },
{ name => "rw-RW", lcid => 0x00000487 },
{ name => "rwk" },
{ name => "rwk-TZ" },
{ name => "sa", lcid => 0x0000004f, slist => ",", group => 15 },
{ name => "sa-Deva", alias => "sa" },
{ name => "sa-Deva-IN", alias => "sa-IN" },
{ name => "sa-IN", lcid => 0x0000044f },
{ name => "sah", lcid => 0x00000085, oemcp => 866, group => 5, sopentypelang => "YAK" },
{ name => "sah-Cyrl", alias => "sah" },
{ name => "sah-Cyrl-RU", alias => "sah-RU" },
{ name => "sah-RU", lcid => 0x00000485 },
{ name => "saq" },
{ name => "saq-KE" },
{ name => "sat" },
{ name => "sat-Olck" },
{ name => "sat-Olck-IN" },
{ name => "sbp" },
{ name => "sbp-TZ" },
{ name => "sc" },
{ name => "sc-IT" },
{ name => "sd", lcid => 0x00000059, inegnumber => 3, oemcp => 720, sabbrevlangname => "SIP" },
{ name => "sd-Arab", lcid => 0x00007c59, group => 13 },
{ name => "sd-Arab-PK", lcid => 0x00000859 },
{ name => "sd-Deva", inegnumber => 1, slist => ",", oemcp => 65001, group => 15 },
{ name => "sd-Deva-IN", lcid => 0x00000459, sabbrevlangname => "ZZZ" },
{ name => "sd-PK", alias => "sd-Arab-PK" },
{ name => "se", lcid => 0x0000003b, oemcp => 850, ebcdiccp => 20277, sopentypelang => "NSM" },
{ name => "se-FI", lcid => 0x00000c3b, ebcdiccp => 20278, sabbrevlangname => "SMG" },
{ name => "se-NO", lcid => 0x0000043b },
{ name => "se-SE", lcid => 0x0000083b, ebcdiccp => 20278, sabbrevlangname => "SMF" },
{ name => "se-Latn", alias => "se" },
{ name => "se-Latn-FI", alias => "se-FI" },
{ name => "se-Latn-NO", alias => "se-NO" },
{ name => "se-Latn-SE", alias => "se-SE" },
{ name => "seh" },
{ name => "seh-MZ" },
{ name => "ses" },
{ name => "ses-ML" },
{ name => "sg", sopentypelang => "SGO" },
{ name => "sg-CF" },
{ name => "shi" },
{ name => "shi-Latn" },
{ name => "shi-Latn-MA" },
{ name => "shi-Tfng" },
{ name => "shi-Tfng-MA" },
{ name => "si", lcid => 0x0000005b, group => 15, sopentypelang => "SNH" },
{ name => "si-LK", lcid => 0x0000045b },
{ name => "sk", lcid => 0x0000001b, oemcp => 852, ebcdiccp => 20880, group => 2, sabbrevlangname => "SKY", sopentypelang => "SKY" },
{ name => "sk-SK", lcid => 0x0000041b },
{ name => "skr" },
{ name => "skr-PK" },
{ name => "sl", lcid => 0x00000024, oemcp => 852, ebcdiccp => 20880, group => 2 },
{ name => "sl-SI", lcid => 0x00000424 },
{ name => "sma", lcid => 0x0000783b, sparent => "se", ebcdiccp => 20278, sabbrevlangname => "SMB", sopentypelang => "SSM" },
{ name => "sma-Latn", alias => "sma" },
{ name => "sma-Latn-NO", alias => "sma-NO" },
{ name => "sma-Latn-SE", alias => "sma-SE" },
{ name => "sma-NO", lcid => 0x0000183b, ebcdiccp => 20277, sabbrevlangname => "SMA" },
{ name => "sma-SE", lcid => 0x00001c3b },
{ name => "smj", lcid => 0x00007c3b, sparent => "se", ebcdiccp => 20278, sabbrevlangname => "SMK", sopentypelang => "LSM" },
{ name => "smj-Latn", alias => "smj" },
{ name => "smj-Latn-NO", alias => "smj-NO" },
{ name => "smj-Latn-SE", alias => "smj-SE" },
{ name => "smj-NO", lcid => 0x0000103b, ebcdiccp => 20277, sabbrevlangname => "SMJ" },
{ name => "smj-SE", lcid => 0x0000143b },
{ name => "smn", lcid => 0x0000703b, sparent => "se", ebcdiccp => 20278, sopentypelang => "ISM" },
{ name => "smn-FI", lcid => 0x0000243b },
{ name => "smn-Latn", alias => "smn" },
{ name => "smn-Latn-FI", alias => "smn-FI" },
{ name => "sms", lcid => 0x0000743b, sparent => "se", ebcdiccp => 20278, sopentypelang => "SKS" },
{ name => "sms-FI", lcid => 0x0000203b },
{ name => "sms-Latn", alias => "sms" },
{ name => "sms-Latn-FI", alias => "sms-FI" },
{ name => "sn", sopentypelang => "SNA0" },
{ name => "sn-Latn", file => "sn" },
{ name => "sn-Latn-ZW", file => "sn_ZW" },
{ name => "sn-ZW", alias => "sn-Latn-ZW" },
{ name => "so", lcid => 0x00000077, sopentypelang => "SML" },
{ name => "so-DJ" },
{ name => "so-ET" },
{ name => "so-KE" },
{ name => "so-SO", lcid => 0x00000477 },
{ name => "sq", lcid => 0x0000001c, oemcp => 852, ebcdiccp => 20880, group => 2 },
{ name => "sq-AL", lcid => 0x0000041c },
{ name => "sq-MK" },
{ name => "sq-XK" },
{ name => "sr", lcid => 0x00007c1a, sortlocale => "sr-Latn-RS", oemcp => 852, group => 2, sabbrevlangname => "SRB", sopentypelang => "SRB" },
{ name => "sr-Cyrl", lcid => 0x00006c1a, oemcp => 855, ebcdiccp => 21025, group => 5, sabbrevlangname => "SRO" },
{ name => "sr-Cyrl-BA", lcid => 0x00001c1a, sabbrevlangname => "SRN" },
{ name => "sr-Cyrl-ME", lcid => 0x0000301a, sabbrevlangname => "SRQ" },
{ name => "sr-Cyrl-RS", lcid => 0x0000281a },
{ name => "sr-Cyrl-XK" },
{ name => "sr-Latn", lcid => 0x0000701a, sabbrevlangname => "SRM" },
{ name => "sr-Latn-BA", lcid => 0x0000181a, maccp => 10082, ebcdiccp => 870, sabbrevlangname => "SRS" },
{ name => "sr-Latn-ME", lcid => 0x00002c1a, sabbrevlangname => "SRP" },
{ name => "sr-Latn-RS", lcid => 0x0000241a, sabbrevlangname => "SRM" },
{ name => "sr-Latn-XK" },
## name => "sr-Cyrl-CS", lcid => 0x00000c1a },
## name => "sr-Latn-CS", lcid => 0x0000081a },
{ name => "ss", sopentypelang => "SWZ" },
{ name => "ss-SZ" },
{ name => "ss-ZA" },
{ name => "ssy" },
{ name => "ssy-ER" },
{ name => "st", lcid => 0x00000030 },
{ name => "st-LS" },
{ name => "st-ZA", lcid => 0x00000430 },
{ name => "su" },
{ name => "su-Latn" },
{ name => "su-Latn-ID" },
{ name => "sv", lcid => 0x0000001d, oemcp => 850, ebcdiccp => 20278, sabbrevlangname => "SVE", sopentypelang => "SVE" },
{ name => "sv-AX" },
{ name => "sv-FI", lcid => 0x0000081d, sabbrevlangname => "SVF" },
{ name => "sv-SE", lcid => 0x0000041d, sabbrevlangname => "SVE" },
{ name => "sw", lcid => 0x00000041, territory => "KE", oemcp => 437, ebcdiccp => 500, sabbrevlangname => "SWK", sopentypelang => "SWK" },
{ name => "sw-CD" },
{ name => "sw-KE", lcid => 0x00000441 },
{ name => "sw-TZ" },
{ name => "sw-UG" },
{ name => "swc-CD", alias => "sw-CD" },
{ name => "syr", lcid => 0x0000005a, slist => ",", group => 13 },
{ name => "syr-SY", lcid => 0x0000045a },
{ name => "syr-Syrc", alias => "syr" },
{ name => "syr-Syrc-SY", alias => "syr-SY" },
{ name => "ta", lcid => 0x00000049, slist => ",", group => 15, sabbrevlangname => "TAI" },
{ name => "ta-IN", lcid => 0x00000449 },
{ name => "ta-LK", lcid => 0x00000849, sabbrevlangname => "TAM" },
{ name => "ta-MY" },
{ name => "ta-SG" },
{ name => "te", lcid => 0x0000004a, group => 15 },
{ name => "te-IN", lcid => 0x0000044a },
{ name => "teo" },
{ name => "teo-KE" },
{ name => "teo-UG" },
{ name => "tg", lcid => 0x00000028, oemcp => 866, group => 5, sabbrevlangname => "TAJ", sopentypelang => "TAJ" },
{ name => "tg-Cyrl", lcid => 0x00007c28, file => "tg" },
{ name => "tg-Cyrl-TJ", lcid => 0x00000428, file => "tg_TJ" },
{ name => "tg-TJ", alias => "tg-Cyrl-TJ" },
{ name => "th", lcid => 0x0000001e, oemcp => 874, ebcdiccp => 20838, slist => ",", group => 11 },
{ name => "th-TH", lcid => 0x0000041e },
{ name => "ti", lcid => 0x00000073, territory => "ER", sopentypelang => "TGY" },
{ name => "ti-ER", lcid => 0x00000873 },
{ name => "ti-ET", lcid => 0x00000473, sabbrevlangname => "TIE" },
{ name => "tig", sopentypelang => "TGR" },
{ name => "tig-ER" },
{ name => "tig-Ethi-ER", alias => "tig-ER" },
{ name => "tk", lcid => 0x00000042, oemcp => 852, ebcdiccp => 20880, group => 2, sopentypelang => "TKM" },
{ name => "tk-Latn", alias => "tk" },
{ name => "tk-Latn-TM", alias => "tk-TM" },
{ name => "tk-TM", lcid => 0x00000442 },
{ name => "tn", lcid => 0x00000032, oemcp => 850, sopentypelang => "TNA" },
{ name => "tn-BW", lcid => 0x00000832, sabbrevlangname => "TSB" },
{ name => "tn-ZA", lcid => 0x00000432 },
{ name => "to", sopentypelang => "TGN" },
{ name => "to-TO" },
{ name => "tr", lcid => 0x0000001f, oemcp => 857, ebcdiccp => 20905, group => 6, sabbrevlangname => "TRK", sopentypelang => "TRK" },
{ name => "tr-CY" },
{ name => "tr-TR", lcid => 0x0000041f },
{ name => "ts", lcid => 0x00000031, sopentypelang => "TSG" },
{ name => "ts-ZA", lcid => 0x00000431 },
{ name => "tt", lcid => 0x00000044, oemcp => 866, group => 5, sabbrevlangname => "TTT" },
{ name => "tt-Cyrl", alias => "tt" },
{ name => "tt-Cyrl-RU", alias => "tt-RU" },
{ name => "tt-RU", lcid => 0x00000444 },
{ name => "twq" },
{ name => "twq-NE" },
{ name => "tyv" },
{ name => "tyv-RU" },
{ name => "tzm", lcid => 0x0000005f, sortlocale => "tzm-Latn-DZ", oemcp => 850, ebcdiccp => 20297, sabbrevlangname => "TZA" },
{ name => "tzm-Latn", lcid => 0x00007c5f, territory => "DZ", file => "tzm" },
{ name => "tzm-Latn-MA", file => "tzm_MA", oemcp => 65001 },
{ name => "tzm-Latn-DZ", lcid => 0x0000085f, file => "tzm" },
{ name => "tzm-MA", alias => "tzm-Latn-MA" },
{ name => "tzm-DZ", alias => "tzm-Latn-DZ" },
## name => "tzm-Arab", group => 13 },
## name => "tzm-Arab-MA", lcid => 0x0000045f },
## name => "tzm-Tfng", lcid => 0x0000785f },
## name => "tzm-Tfng-MA", lcid => 0x0000105f },
{ name => "ug", lcid => 0x00000080, oemcp => 720, slist => ",", group => 13, sopentypelang => "UYG", nativedigits => "0123456789" },
{ name => "ug-Arab", alias => "ug" },
{ name => "ug-Arab-CN", alias => "ug-CN" },
{ name => "ug-CN", lcid => 0x00000480 },
{ name => "uk", lcid => 0x00000022, oemcp => 866, maccp => 10017, ebcdiccp => 500, group => 5 },
{ name => "uk-UA", lcid => 0x00000422 },
{ name => "ur", lcid => 0x00000020, oemcp => 720 },
{ name => "ur-IN", lcid => 0x00000820, maccp => 65001, sabbrevlangname => "URI" },
{ name => "ur-PK", lcid => 0x00000420, group => 13 },
{ name => "uz", lcid => 0x00000043, oemcp => 857, maccp => 10029, group => 2 },
{ name => "uz-Arab", oemcp => 65001, maccp => 65001 },
{ name => "uz-Arab-AF" },
{ name => "uz-Cyrl", lcid => 0x00007843, oemcp => 866, maccp => 10007, group => 5, sabbrevlangname => "UZC" },
{ name => "uz-Cyrl-UZ", lcid => 0x00000843 },
{ name => "uz-Latn", lcid => 0x00007c43 },
{ name => "uz-Latn-UZ", lcid => 0x00000443 },
{ name => "vai" },
{ name => "vai-Latn" },
{ name => "vai-Latn-LR" },
{ name => "vai-Vaii" },
{ name => "vai-Vaii-LR" },
{ name => "ve", lcid => 0x00000033, sabbrevlangname => "ZZZ" },
{ name => "ve-ZA", lcid => 0x00000433 },
{ name => "vi", lcid => 0x0000002a, oemcp => 1258, slist => ",", group => 14, sabbrevlangname => "VIT", sopentypelang => "VIT" },
{ name => "vi-VN", lcid => 0x0000042a },
{ name => "vmw" },
{ name => "vmw-MZ" },
{ name => "vo" },
{ name => "vo-001" },
{ name => "vun" },
{ name => "vun-TZ" },
{ name => "wa", oemcp => 850 },
{ name => "wa-BE" },
{ name => "wae" },
{ name => "wae-CH" },
{ name => "wal" },
{ name => "wal-ET" },
{ name => "wo", lcid => 0x00000088, oemcp => 850, ebcdiccp => 20297, sopentypelang => "WLF" },
{ name => "wo-Latn", alias => "wo" },
{ name => "wo-Latn-SN", alias => "wo-SN" },
{ name => "wo-SN", lcid => 0x00000488 },
{ name => "x-IV_mathan", lcid => 0x0001007f, alias => "" },
{ name => "xh", lcid => 0x00000034, oemcp => 850, sopentypelang => "XHS" },
{ name => "xh-ZA", lcid => 0x00000434 },
{ name => "xnr" },
{ name => "xnr-IN" },
{ name => "xog" },
{ name => "xog-UG" },
{ name => "yav" },
{ name => "yav-CM" },
{ name => "yi", lcid => 0x0000003d, sabbrevlangname => "ZZZ", sopentypelang => "JII" },
{ name => "yi-001", lcid => 0x0000043d, file => "yi" },
{ name => "yi-UA" },
{ name => "yo", lcid => 0x0000006a, oemcp => 437, sopentypelang => "YBA" },
{ name => "yo-BJ", ebcdiccp => 500 },
{ name => "yo-Latn", alias => "yo" },
{ name => "yo-Latn-NG", alias => "yo-NG" },
{ name => "yo-NG", lcid => 0x0000046a },
{ name => "yrl" },
{ name => "yrl-BR" },
{ name => "yrl-CO" },
{ name => "yrl-VE" },
{ name => "yue" },
{ name => "yue-Hans" },
{ name => "yue-Hans-CN" },
{ name => "yue-Hant" },
{ name => "yue-Hant-HK" },
{ name => "zgh" },
{ name => "zgh-MA", alias => "zgh-Tfng-MA" },
{ name => "zgh-Tfng", file => "zgh" },
{ name => "zgh-Tfng-MA", file => "zgh_MA" },
{ name => "za" },
{ name => "za-CN" },
{ name => "zh", lcid => 0x00007804, ireadinglayout => 2, oemcp => 936, slist => ",", sscripts => "Hani Hans", sabbrevlangname => "CHS", sopentypelang => "ZHS", nativedigits => "0123456789" },
{ name => "zh-CN", lcid => 0x00000804, file => "zh_Hans_CN", sparent => "zh-Hans" },
{ name => "zh-CN_phoneb", lcid => 0x00050804, alias => "zh-CN" },
{ name => "zh-CN_stroke", lcid => 0x00020804, alias => "zh-CN" },
{ name => "zh-Hans", lcid => 0x00000004, group => 10 },
{ name => "zh-Hans-CN", alias => "zh-CN" },
{ name => "zh-Hans-CN-u-co-phonebk", alias => "zh-CN_phoneb" },
{ name => "zh-Hans-CN-u-co-stroke", alias => "zh-CN_stroke" },
{ name => "zh-Hans-HK", slist => ";", nativedigits => "" },
{ name => "zh-Hans-MO", slist => ";", nativedigits => "" },
{ name => "zh-Hans-SG", alias => "zh-SG" },
{ name => "zh-Hans-SG-u-co-phonebk", alias => "zh-SG_phoneb" },
{ name => "zh-Hans-SG-u-co-stroke", alias => "zh-SG_stroke" },
{ name => "zh-Hant", lcid => 0x00007c04, sortlocale => "zh-HK", ireadinglayout => 2, oemcp => 950, slist => ",", sscripts => "Hani Hant", group => 9, sabbrevlangname => "CHT", sopentypelang => "ZHH" },
{ name => "zh-Hant-HK", alias => "zh-HK" },
{ name => "zh-Hant-HK-u-co-unihan", alias => "zh-HK_radstr" },
{ name => "zh-Hant-MO", alias => "zh-MO" },
{ name => "zh-Hant-MO-u-co-stroke", alias => "zh-MO_stroke" },
{ name => "zh-Hant-MO-u-co-unihan", alias => "zh-MO_radstr" },
{ name => "zh-Hant-TW", alias => "zh-TW" },
{ name => "zh-Hant-TW-u-co-phonetic", alias => "zh-TW_pronun" },
{ name => "zh-Hant-TW-u-co-unihan", alias => "zh-TW_radstr" },
{ name => "zh-HK", lcid => 0x00000c04, file => "zh_Hant_HK", sparent => "zh-Hant", sabbrevlangname => "ZHH" },
{ name => "zh-HK_radstr", lcid => 0x00040c04, alias => "zh-HK" },
{ name => "zh-MO", lcid => 0x00001404, file => "zh_Hant_MO", sparent => "zh-Hant", sabbrevlangname => "ZHM", sopentypelang => "ZHT" },
{ name => "zh-MO_radstr", lcid => 0x00041404, alias => "zh-MO" },
{ name => "zh-MO_stroke", lcid => 0x00021404, alias => "zh-MO" },
{ name => "zh-SG", lcid => 0x00001004, file => "zh_Hans_SG", sparent => "zh-Hans", sabbrevlangname => "ZHI" },
{ name => "zh-SG_phoneb", lcid => 0x00051004, alias => "zh-SG" },
{ name => "zh-SG_stroke", lcid => 0x00021004, alias => "zh-SG" },
{ name => "zh-TW", lcid => 0x00000404, file => "zh_Hant_TW", sparent => "zh-Hant", sopentypelang => "ZHT" },
{ name => "zh-TW_pronun", lcid => 0x00030404, alias => "zh-TW" },
{ name => "zh-TW_radstr", lcid => 0x00040404, alias => "zh-TW" },
{ name => "zu", lcid => 0x00000035, oemcp => 850 },
{ name => "zu-ZA", lcid => 0x00000435 },
);
my @calendars =
(
{ id => 1, name => "Gregorian", itwodigityearmax => 2049 },
{ id => 2, type => "gregorian", locale => "en-US", itwodigityearmax => 2049 },
{ id => 3, type => "japanese", locale => "ja-JP", eras => [ 232..236 ] },
{ id => 4, type => "roc", locale => "zh-TW", eras => [ 1 ] },
{ id => 5, type => "dangi", locale => "ko-KR", eras => [ 0 ] },
{ id => 6, type => "islamic", locale => "ar-SA", itwodigityearmax => 1451 },
{ id => 7, type => "buddhist", locale => "th-TH", eras => [ 0 ] },
{ id => 8, type => "hebrew", locale => "he-IL", itwodigityearmax => 5810 },
{ id => 9, type => "gregorian", locale => "fr-FR", itwodigityearmax => 2049 },
{ id => 10, type => "gregorian", locale => "ar-SA", itwodigityearmax => 2049 },
{ id => 11, type => "gregorian", locale => "ar-SA", itwodigityearmax => 2049 },
{ id => 12, type => "gregorian", locale => "ar-SA", itwodigityearmax => 2049 },
{ id => 13, name => "Julian", locale => "en-US", itwodigityearmax => 2049 },
{ id => 14, name => "Japanese Lunisolar" },
{ id => 15, name => "Chinese Lunisolar" },
{ id => 16, name => "Saka" },
{ id => 17, name => "Lunar ETO Chinese" },
{ id => 18, name => "Lunar ETO Korean" },
{ id => 19, name => "Lunar ETO Rokuyou" },
{ id => 20, name => "Korean Lunisolar" },
{ id => 21, name => "Taiwan Lunisolar" },
{ id => 22, type => "persian", locale => "prs-AF", itwodigityearmax => 1429 },
{ id => 23, type => "islamic-umalqura", locale => "ar-SA", itwodigityearmax => 1451 },
);
my @geoids =
(
{ id => 2, name => "AG" }, # Antigua and Barbuda
{ id => 3, name => "AF" }, # Afghanistan
{ id => 4, name => "DZ" }, # Algeria
{ id => 5, name => "AZ" }, # Azerbaijan
{ id => 6, name => "AL" }, # Albania
{ id => 7, name => "AM" }, # Armenia
{ id => 8, name => "AD" }, # Andorra
{ id => 9, name => "AO" }, # Angola
{ id => 10, name => "AS" }, # American Samoa
{ id => 11, name => "AR" }, # Argentina
{ id => 12, name => "AU" }, # Australia
{ id => 14, name => "AT" }, # Austria
{ id => 17, name => "BH" }, # Bahrain
{ id => 18, name => "BB" }, # Barbados
{ id => 19, name => "BW" }, # Botswana
{ id => 20, name => "BM" }, # Bermuda
{ id => 21, name => "BE" }, # Belgium
{ id => 22, name => "BS" }, # Bahamas, The
{ id => 23, name => "BD" }, # Bangladesh
{ id => 24, name => "BZ" }, # Belize
{ id => 25, name => "BA" }, # Bosnia and Herzegovina
{ id => 26, name => "BO" }, # Bolivia
{ id => 27, name => "MM" }, # Myanmar
{ id => 28, name => "BJ" }, # Benin
{ id => 29, name => "BY" }, # Belarus
{ id => 30, name => "SB" }, # Solomon Islands
{ id => 32, name => "BR" }, # Brazil
{ id => 34, name => "BT" }, # Bhutan
{ id => 35, name => "BG" }, # Bulgaria
{ id => 37, name => "BN" }, # Brunei
{ id => 38, name => "BI" }, # Burundi
{ id => 39, name => "CA" }, # Canada
{ id => 40, name => "KH" }, # Cambodia
{ id => 41, name => "TD" }, # Chad
{ id => 42, name => "LK" }, # Sri Lanka
{ id => 43, name => "CG" }, # Congo
{ id => 44, name => "CD" }, # Congo (DRC)
{ id => 45, name => "CN" }, # China
{ id => 46, name => "CL" }, # Chile
{ id => 49, name => "CM" }, # Cameroon
{ id => 50, name => "KM" }, # Comoros
{ id => 51, name => "CO" }, # Colombia
{ id => 54, name => "CR" }, # Costa Rica
{ id => 55, name => "CF" }, # Central African Republic
{ id => 56, name => "CU" }, # Cuba
{ id => 57, name => "CV" }, # Cape Verde
{ id => 59, name => "CY" }, # Cyprus
{ id => 61, name => "DK" }, # Denmark
{ id => 62, name => "DJ" }, # Djibouti
{ id => 63, name => "DM" }, # Dominica
{ id => 65, name => "DO" }, # Dominican Republic
{ id => 66, name => "EC" }, # Ecuador
{ id => 67, name => "EG" }, # Egypt
{ id => 68, name => "IE" }, # Ireland
{ id => 69, name => "GQ" }, # Equatorial Guinea
{ id => 70, name => "EE" }, # Estonia
{ id => 71, name => "ER" }, # Eritrea
{ id => 72, name => "SV" }, # El Salvador
{ id => 73, name => "ET" }, # Ethiopia
{ id => 75, name => "CZ" }, # Czech Republic
{ id => 77, name => "FI" }, # Finland
{ id => 78, name => "FJ" }, # Fiji Islands
{ id => 80, name => "FM" }, # Micronesia
{ id => 81, name => "FO" }, # Faroe Islands
{ id => 84, name => "FR" }, # France
{ id => 86, name => "GM" }, # Gambia, The
{ id => 87, name => "GA" }, # Gabon
{ id => 88, name => "GE" }, # Georgia
{ id => 89, name => "GH" }, # Ghana
{ id => 90, name => "GI" }, # Gibraltar
{ id => 91, name => "GD" }, # Grenada
{ id => 93, name => "GL" }, # Greenland
{ id => 94, name => "DE" }, # Germany
{ id => 98, name => "GR" }, # Greece
{ id => 99, name => "GT" }, # Guatemala
{ id => 100, name => "GN" }, # Guinea
{ id => 101, name => "GY" }, # Guyana
{ id => 103, name => "HT" }, # Haiti
{ id => 104, name => "HK" }, # Hong Kong S.A.R.
{ id => 106, name => "HN" }, # Honduras
{ id => 108, name => "HR" }, # Croatia
{ id => 109, name => "HU" }, # Hungary
{ id => 110, name => "IS" }, # Iceland
{ id => 111, name => "ID" }, # Indonesia
{ id => 113, name => "IN" }, # India
{ id => 114, name => "IO" }, # British Indian Ocean Territory
{ id => 116, name => "IR" }, # Iran
{ id => 117, name => "IL" }, # Israel
{ id => 118, name => "IT" }, # Italy
{ id => 119, name => "CI" }, # Côte d'Ivoire
{ id => 121, name => "IQ" }, # Iraq
{ id => 122, name => "JP" }, # Japan
{ id => 124, name => "JM" }, # Jamaica
{ id => 125, name => "SJ" }, # Jan Mayen
{ id => 126, name => "JO" }, # Jordan
{ id => 127, parent => "UM" }, # Johnston Atoll
{ id => 129, name => "KE" }, # Kenya
{ id => 130, name => "KG" }, # Kyrgyzstan
{ id => 131, name => "KP" }, # North Korea
{ id => 133, name => "KI" }, # Kiribati
{ id => 134, name => "KR" }, # Korea
{ id => 136, name => "KW" }, # Kuwait
{ id => 137, name => "KZ" }, # Kazakhstan
{ id => 138, name => "LA" }, # Laos
{ id => 139, name => "LB" }, # Lebanon
{ id => 140, name => "LV" }, # Latvia
{ id => 141, name => "LT" }, # Lithuania
{ id => 142, name => "LR" }, # Liberia
{ id => 143, name => "SK" }, # Slovakia
{ id => 145, name => "LI" }, # Liechtenstein
{ id => 146, name => "LS" }, # Lesotho
{ id => 147, name => "LU" }, # Luxembourg
{ id => 148, name => "LY" }, # Libya
{ id => 149, name => "MG" }, # Madagascar
{ id => 151, name => "MO" }, # Macao S.A.R.
{ id => 152, name => "MD" }, # Moldova
{ id => 154, name => "MN" }, # Mongolia
{ id => 156, name => "MW" }, # Malawi
{ id => 157, name => "ML" }, # Mali
{ id => 158, name => "MC" }, # Monaco
{ id => 159, name => "MA" }, # Morocco
{ id => 160, name => "MU" }, # Mauritius
{ id => 162, name => "MR" }, # Mauritania
{ id => 163, name => "MT" }, # Malta
{ id => 164, name => "OM" }, # Oman
{ id => 165, name => "MV" }, # Maldives
{ id => 166, name => "MX" }, # Mexico
{ id => 167, name => "MY" }, # Malaysia
{ id => 168, name => "MZ" }, # Mozambique
{ id => 173, name => "NE" }, # Niger
{ id => 174, name => "VU" }, # Vanuatu
{ id => 175, name => "NG" }, # Nigeria
{ id => 176, name => "NL" }, # Netherlands
{ id => 177, name => "NO" }, # Norway
{ id => 178, name => "NP" }, # Nepal
{ id => 180, name => "NR" }, # Nauru
{ id => 181, name => "SR" }, # Suriname
{ id => 182, name => "NI" }, # Nicaragua
{ id => 183, name => "NZ" }, # New Zealand
{ id => 184, name => "PS" }, # Palestinian Authority
{ id => 185, name => "PY" }, # Paraguay
{ id => 187, name => "PE" }, # Peru
{ id => 190, name => "PK" }, # Pakistan
{ id => 191, name => "PL" }, # Poland
{ id => 192, name => "PA" }, # Panama
{ id => 193, name => "PT" }, # Portugal
{ id => 194, name => "PG" }, # Papua New Guinea
{ id => 195, name => "PW" }, # Palau
{ id => 196, name => "GW" }, # Guinea-Bissau
{ id => 197, name => "QA" }, # Qatar
{ id => 198, name => "RE" }, # Reunion
{ id => 199, name => "MH" }, # Marshall Islands
{ id => 200, name => "RO" }, # Romania
{ id => 201, name => "PH" }, # Philippines
{ id => 202, name => "PR" }, # Puerto Rico
{ id => 203, name => "RU" }, # Russia
{ id => 204, name => "RW" }, # Rwanda
{ id => 205, name => "SA" }, # Saudi Arabia
{ id => 206, name => "PM" }, # St. Pierre and Miquelon
{ id => 207, name => "KN" }, # St. Kitts and Nevis
{ id => 208, name => "SC" }, # Seychelles
{ id => 209, name => "ZA" }, # South Africa
{ id => 210, name => "SN" }, # Senegal
{ id => 212, name => "SI" }, # Slovenia
{ id => 213, name => "SL" }, # Sierra Leone
{ id => 214, name => "SM" }, # San Marino
{ id => 215, name => "SG" }, # Singapore
{ id => 216, name => "SO" }, # Somalia
{ id => 217, name => "ES" }, # Spain
{ id => 218, name => "LC" }, # St. Lucia
{ id => 219, name => "SD" }, # Sudan
{ id => 220, name => "SJ" }, # Svalbard
{ id => 221, name => "SE" }, # Sweden
{ id => 222, name => "SY" }, # Syria
{ id => 223, name => "CH" }, # Switzerland
{ id => 224, name => "AE" }, # United Arab Emirates
{ id => 225, name => "TT" }, # Trinidad and Tobago
{ id => 227, name => "TH" }, # Thailand
{ id => 228, name => "TJ" }, # Tajikistan
{ id => 231, name => "TO" }, # Tonga
{ id => 232, name => "TG" }, # Togo
{ id => 233, name => "ST" }, # São Tomé and Príncipe
{ id => 234, name => "TN" }, # Tunisia
{ id => 235, name => "TR" }, # Turkey
{ id => 236, name => "TV" }, # Tuvalu
{ id => 237, name => "TW" }, # Taiwan
{ id => 238, name => "TM" }, # Turkmenistan
{ id => 239, name => "TZ" }, # Tanzania
{ id => 240, name => "UG" }, # Uganda
{ id => 241, name => "UA" }, # Ukraine
{ id => 242, name => "GB" }, # United Kingdom
{ id => 244, name => "US" }, # United States
{ id => 245, name => "BF" }, # Burkina Faso
{ id => 246, name => "UY" }, # Uruguay
{ id => 247, name => "UZ" }, # Uzbekistan
{ id => 248, name => "VC" }, # St. Vincent and the Grenadines
{ id => 249, name => "VE" }, # Bolivarian Republic of Venezuela
{ id => 251, name => "VN" }, # Vietnam
{ id => 252, name => "VI" }, # Virgin Islands
{ id => 253, name => "VA" }, # Vatican City
{ id => 254, name => "NA" }, # Namibia
{ id => 257, name => "EH" }, # Western Sahara (disputed)
{ id => 258, parent => "UM" }, # Wake Island
{ id => 259, name => "WS" }, # Samoa
{ id => 260, name => "SZ" }, # Swaziland
{ id => 261, name => "YE" }, # Yemen
{ id => 263, name => "ZM" }, # Zambia
{ id => 264, name => "ZW" }, # Zimbabwe
{ id => 269, name => "CS" }, # Serbia and Montenegro (Former)
{ id => 270, name => "ME" }, # Montenegro
{ id => 271, name => "RS" }, # Serbia
{ id => 273, name => "CW" }, # Curaçao
{ id => 276, name => "SS" }, # South Sudan
{ id => 300, name => "AI" }, # Anguilla
{ id => 301, name => "AQ" }, # Antarctica
{ id => 302, name => "AW" }, # Aruba
{ id => 303, parent => "SH" }, # Ascension Island
{ id => 304, parent => "053" }, # Ashmore and Cartier Islands
{ id => 305, parent => "UM" }, # Baker Island
{ id => 306, name => "BV" }, # Bouvet Island
{ id => 307, name => "KY" }, # Cayman Islands
{ id => 308, name => "830", parent => "155" }, # Channel Islands
{ id => 309, name => "CX" }, # Christmas Island
{ id => 310, parent => "009" }, # Clipperton Island
{ id => 311, name => "CC" }, # Cocos (Keeling) Islands
{ id => 312, name => "CK" }, # Cook Islands
{ id => 313, parent => "053" }, # Coral Sea Islands
{ id => 314, parent => "IO" }, # Diego Garcia
{ id => 315, name => "FK" }, # Falkland Islands (Islas Malvinas)
{ id => 317, name => "GF" }, # French Guiana
{ id => 318, name => "PF" }, # French Polynesia
{ id => 319, name => "TF" }, # French Southern and Antarctic Lands
{ id => 321, name => "GP" }, # Guadeloupe
{ id => 322, name => "GU" }, # Guam
{ id => 323 }, # Guantanamo Bay
{ id => 324, name => "GG" }, # Guernsey
{ id => 325, name => "HM" }, # Heard Island and McDonald Islands
{ id => 326, parent => "UM" }, # Howland Island
{ id => 327, parent => "UM" }, # Jarvis Island
{ id => 328, name => "JE" }, # Jersey
{ id => 329, parent => "UM" }, # Kingman Reef
{ id => 330, name => "MQ" }, # Martinique
{ id => 331, name => "YT" }, # Mayotte
{ id => 332, name => "MS" }, # Montserrat
{ id => 333, name => "AN", region => 1 }, # Netherlands Antilles (Former)
{ id => 334, name => "NC" }, # New Caledonia
{ id => 335, name => "NU" }, # Niue
{ id => 336, name => "NF" }, # Norfolk Island
{ id => 337, name => "MP" }, # Northern Mariana Islands
{ id => 338, parent => "UM" }, # Palmyra Atoll
{ id => 339, name => "PN" }, # Pitcairn Islands
{ id => 340, parent => "MP" }, # Rota Island
{ id => 341, parent => "MP" }, # Saipan
{ id => 342, name => "GS" }, # South Georgia and the South Sandwich Islands
{ id => 343, name => "SH" }, # St. Helena
{ id => 346, parent => "MP" }, # Tinian Island
{ id => 347, name => "TK" }, # Tokelau
{ id => 348, parent => "SH" }, # Tristan da Cunha
{ id => 349, name => "TC" }, # Turks and Caicos Islands
{ id => 351, name => "VG" }, # Virgin Islands, British
{ id => 352, name => "WF" }, # Wallis and Futuna
{ id => 742, name => "002" }, # Africa
{ id => 2129, name => "142" }, # Asia
{ id => 10541, name => "150" }, # Europe
{ id => 15126, name => "IM" }, # Man, Isle of
{ id => 19618, name => "MK" }, # Macedonia, Former Yugoslav Republic of
{ id => 20900, name => "054" }, # Melanesia
{ id => 21206, name => "057" }, # Micronesia
{ id => 21242, parent => "UM" }, # Midway Islands
{ id => 23581, name => "021" }, # Northern America
{ id => 26286, name => "061" }, # Polynesia
{ id => 27082, name => "013" }, # Central America
{ id => 27114, name => "009" }, # Oceania
{ id => 30967, name => "SX" }, # Sint Maarten (Dutch part)
{ id => 31396, name => "005" }, # South America
{ id => 31706, name => "MF" }, # Saint Martin (French part)
{ id => 39070, name => "001" }, # World
{ id => 42483, name => "011" }, # Western Africa
{ id => 42484, name => "017" }, # Middle Africa
{ id => 42487, name => "015" }, # Northern Africa
{ id => 47590, name => "143" }, # Central Asia
{ id => 47599, name => "035" }, # South-Eastern Asia
{ id => 47600, name => "030" }, # Eastern Asia
{ id => 47603, name => "014" }, # Eastern Africa
{ id => 47609, name => "151" }, # Eastern Europe
{ id => 47610, name => "039" }, # Southern Europe
{ id => 47611, name => "145" }, # Middle East
{ id => 47614, name => "034" }, # Southern Asia
{ id => 7299303, name => "TL" }, # Democratic Republic of Timor-Leste
{ id => 9914689, name => "XK" }, # Kosovo
{ id => 10026358, name => "019" }, # Americas
{ id => 10028789, name => "AX" }, # Åland Islands
{ id => 10039880, name => "029", sintlsymbol => "XCD" }, # Caribbean
{ id => 10039882, name => "154" }, # Northern Europe
{ id => 10039883, name => "018" }, # Southern Africa
{ id => 10210824, name => "155" }, # Western Europe
{ id => 10210825, name => "053" }, # Australia and New Zealand
{ id => 161832015, name => "BL" }, # Saint Barthélemy
{ id => 161832256, name => "UM" }, # U.S. Minor Outlying Islands
{ id => 161832257, name => "419", parent => "019" }, # Latin America and the Caribbean
{ id => 161832258, name => "BQ" }, # Bonaire, Sint Eustatius and Saba
);
my @cp2uni = ();
my @glyph2uni = ();
my @lead_bytes = ();
my @uni2cp = ();
my @tolower_table = ();
my @toupper_table = ();
my @digitmap_table = ();
my @halfwidth_table = ();
my @fullwidth_table = ();
my @cjk_compat_table = ();
my @chinese_traditional_table = ();
my @chinese_simplified_table = ();
my @category_table = ();
my @initial_joining_table = ();
my @direction_table = ();
my @decomp_table = ();
my @combining_class_table = ();
my @decomp_compat_table = ();
my @comp_exclusions = ();
my @idna_decomp_table = ();
my @idna_disallowed = ();
my %registry_keys;
my $default_char;
my $default_wchar;
my %joining_forms =
(
"isolated" => [],
"final" => [],
"initial" => [],
"medial" => []
);
my $current_data_file;
sub to_utf16(@)
{
my @ret;
foreach my $ch (@_)
{
if ($ch < 0x10000)
{
push @ret, $ch;
}
else
{
my $val = $ch - 0x10000;
push @ret, 0xd800 | ($val >> 10), 0xdc00 | ($val & 0x3ff);
}
}
return @ret;
}
################################################################
# fetch a unicode.org file and open it
sub open_data_file($@)
{
my ($id, $name) = @_;
my $data = $data_files{$id};
my $cache = ($ENV{XDG_CACHE_HOME} || "$ENV{HOME}/.cache") . "/wine";
local *FILE;
my $url = $data->{url};
my $filename = "$cache/" . ($data->{name} || ($url =~ s/.*\/([^\/]+)$/$1/r));
unless (-f $filename)
{
print "Fetching $url...\n";
system "mkdir", "-p", $cache;
!system "wget", "-q", "-O", $filename, $url or die "cannot fetch $url";
}
my $sha = Digest::SHA->new( "sha256" )->addfile( $filename )->hexdigest;
die "invalid checksum $sha for $filename" unless $sha eq $data->{sha};
if ($filename =~ /\.zip$/)
{
open FILE, "-|", "unzip", "-p", $filename, $name or die "cannot extract $name from $filename";
}
elsif ($filename =~ /\.tar\.gz$/)
{
open FILE, "-|", "tar", "-x", "-f", $filename, "-O", $name or die "cannot extract $name from $filename";
}
else
{
open FILE, "<$filename" or die "cannot open $filename";
}
$current_data_file = $name ? "$url:$name" : $url;
return *FILE;
}
################################################################
# load a unicode.org file as XML data
sub load_xml_data_file($@)
{
my ($id, $name) = @_;
my $FILE = open_data_file( $id, $name );
my $xml = XML::LibXML->load_xml( IO => $FILE );
close FILE;
return $xml;
}
################################################################
# recursively get the decomposition for a character
sub get_decomposition($$);
sub get_decomposition($$)
{
my ($char, $table) = @_;
my @ret;
return $char unless defined $table->[$char];
foreach my $ch (@{$table->[$char]})
{
push @ret, get_decomposition( $ch, $table );
}
return @ret;
}
################################################################
# get the composition that results in a given character
sub get_composition($$)
{
my ($ch, $compat) = @_;
return () unless defined $decomp_table[$ch]; # no decomposition
my @ret = @{$decomp_table[$ch]};
return () if @ret < 2; # singleton decomposition
return () if $comp_exclusions[$ch]; # composition exclusion
return () if $combining_class_table[$ch]; # non-starter
return () if $combining_class_table[$ret[0]]; # first char is non-starter
return () if $compat == 1 && !defined $decomp_table[$ret[0]] &&
defined $decomp_compat_table[$ret[0]]; # first char has compat decomposition
return () if $compat == 2 && !defined $decomp_table[$ret[0]] &&
defined $idna_decomp_table[$ret[0]]; # first char has IDNA decomposition
return () if $compat == 2 && defined $idna_decomp_table[$ret[0]] &&
defined $idna_decomp_table[$idna_decomp_table[$ret[0]]->[0]]; # first char's decomposition has IDNA decomposition
return () if $compat == 2 && defined $idna_decomp_table[$ret[1]]; # second char has IDNA decomposition
return @ret;
}
################################################################
# recursively build decompositions
sub build_decompositions(@)
{
my @src = @_;
my @dst;
for (my $i = 0; $i < @src; $i++)
{
next unless defined $src[$i];
my @decomp = to_utf16( get_decomposition( $i, \@src ));
$dst[$i] = \@decomp;
}
return @dst;
}
################################################################
# compose Hangul sequences
sub compose_hangul(@)
{
my $SBASE = 0xac00;
my $LBASE = 0x1100;
my $VBASE = 0x1161;
my $TBASE = 0x11a7;
my $LCOUNT = 19;
my $VCOUNT = 21;
my $TCOUNT = 28;
my $NCOUNT = $VCOUNT * $TCOUNT;
my $SCOUNT = $LCOUNT * $NCOUNT;
my @seq = @_;
my @ret;
my $i;
for ($i = 0; $i < @seq; $i++)
{
my $ch = $seq[$i];
if ($ch >= $LBASE && $ch < $LBASE + $LCOUNT && $i < @seq - 1 &&
$seq[$i+1] >= $VBASE && $seq[$i+1] < $VBASE + $VCOUNT)
{
$ch = $SBASE + (($seq[$i] - $LBASE) * $VCOUNT + ($seq[$i+1] - $VBASE)) * $TCOUNT;
$i++;
}
if ($ch >= $SBASE && $ch < $SBASE + $SCOUNT && !(($ch - $SBASE) % $TCOUNT) && $i < @seq - 1 &&
$seq[$i+1] > $TBASE && $seq[$i+1] < $TBASE + $TCOUNT)
{
$ch += $seq[$i+1] - $TBASE;
$i++;
}
push @ret, $ch;
}
return @ret;
}
################################################################
# remove linguistic-only mappings from the case table
sub remove_linguistic_mappings($$)
{
my ($upper, $lower) = @_;
# remove case mappings that don't round-trip
for (my $i = 0; $i < @{$upper}; $i++)
{
next unless defined ${$upper}[$i];
my $ch = ${$upper}[$i];
${$upper}[$i] = undef unless defined ${$lower}[$ch] && ${$lower}[$ch] == $i;
}
for (my $i = 0; $i < @{$lower}; $i++)
{
next unless defined ${$lower}[$i];
my $ch = ${$lower}[$i];
${$lower}[$i] = undef unless defined ${$upper}[$ch] && ${$upper}[$ch] == $i;
}
}
################################################################
# read in the Unicode database files
sub load_data()
{
my $start;
# now build mappings from the decomposition field of the Unicode database
my $UNICODE_DATA = open_data_file( "ucd", "UnicodeData.txt" );
while (<$UNICODE_DATA>)
{
# Decode the fields ...
my ($code, $name, $cat, $comb, $bidi,
$decomp, $dec, $dig, $num, $mirror,
$oldname, $comment, $upper, $lower, $title) = split /;/;
my $src = hex $code;
die "unknown category $cat" unless defined $categories{$cat};
die "unknown directionality $bidi" unless defined $directions{$bidi};
$category_table[$src] = $categories{$cat};
$direction_table[$src] = $bidi;
if ($cat eq "Mn" || $cat eq "Me" || $cat eq "Cf")
{
$initial_joining_table[$src] = $joining_types{"T"};
}
else
{
$initial_joining_table[$src] = $joining_types{"U"};
}
if ($lower ne "")
{
$tolower_table[$src] = hex $lower;
}
if ($upper ne "")
{
$toupper_table[$src] = hex $upper;
}
if ($dec ne "")
{
$category_table[$src] |= $ctype{"digit"};
}
if ($dig ne "")
{
$digitmap_table[$src] = ord $dig;
}
$combining_class_table[$src] = ($cat ne "Co") ? $comb : 0x100; # Private Use
$category_table[$src] |= $ctype{"nonspacing"} if $bidi eq "NSM";
$category_table[$src] |= $ctype{"diacritic"} if $name =~ /^(COMBINING)|(MODIFIER LETTER)\W/;
$category_table[$src] |= $ctype{"vowelmark"} if $name =~ /\sVOWEL/ || $oldname =~ /\sVOWEL/;
$category_table[$src] |= $ctype{"halfwidth"} if $name =~ /^HALFWIDTH\s/;
$category_table[$src] |= $ctype{"fullwidth"} if $name =~ /^FULLWIDTH\s/;
$category_table[$src] |= $ctype{"hiragana"} if $name =~ /(HIRAGANA)|(\WKANA\W)/;
$category_table[$src] |= $ctype{"katakana"} if $name =~ /(KATAKANA)|(\WKANA\W)/;
$category_table[$src] |= $ctype{"ideograph"} if $name =~ /^<CJK Ideograph/;
$category_table[$src] |= $ctype{"ideograph"} if $name =~ /^CJK COMPATIBILITY IDEOGRAPH/;
$category_table[$src] |= $ctype{"ideograph"} if $name =~ /^HANGZHOU/;
$category_table[$src] |= $ctype{"highsurrogate"} if $name =~ /High Surrogate/;
$category_table[$src] |= $ctype{"lowsurrogate"} if $name =~ /Low Surrogate/;
# copy the category and direction for everything between First/Last pairs
if ($name =~ /, First>/) { $start = $src; }
if ($name =~ /, Last>/)
{
while ($start < $src)
{
$category_table[$start] = $category_table[$src];
$direction_table[$start] = $direction_table[$src];
$combining_class_table[$start] = $combining_class_table[$src];
$start++;
}
}
next if $decomp eq ""; # no decomposition, skip it
if ($decomp =~ /^<([a-zA-Z]+)>\s+([0-9a-fA-F]+)/)
{
my @seq = map { hex $_; } (split /\s+/, (split /\s+/, $decomp, 2)[1]);
$decomp_compat_table[$src] = \@seq;
}
if ($decomp =~ /^<([a-zA-Z]+)>\s+([0-9a-fA-F]+)$/)
{
# decomposition of the form "<foo> 1234" -> use char if type is known
my $dst = hex $2;
if ($1 eq "narrow")
{
$halfwidth_table[$dst] = $src;
$fullwidth_table[$src] = $dst;
}
elsif ($1 eq "wide")
{
next if $dst == 0x5c; # don't remap backslash
$fullwidth_table[$dst] = $src;
$halfwidth_table[$src] = $dst;
}
elsif ($1 eq "font" || $1 eq "square" || $1 eq "circle")
{
$fullwidth_table[$src] = $dst if $src >= 0x10000;
}
elsif ($1 eq "isolated" || $1 eq "final" || $1 eq "initial" || $1 eq "medial")
{
${joining_forms{$1}}[$dst] = $src;
}
}
elsif ($decomp =~ /^<compat>\s+0020\s+([0-9a-fA-F]+)/)
{
# decomposition "<compat> 0020 1234" -> combining accent
}
elsif ($decomp =~ /^([0-9a-fA-F]+)/)
{
# store decomposition
if ($decomp =~ /^([0-9a-fA-F]+)\s+([0-9a-fA-F]+)$/)
{
$decomp_table[$src] = $decomp_compat_table[$src] = [ hex $1, hex $2 ];
}
elsif ($decomp =~ /^([0-9a-fA-F]+)$/)
{
my $dst = hex $1;
# Single char decomposition
$decomp_table[$src] = $decomp_compat_table[$src] = [ $dst ];
if ($name =~ /^CJK COMPATIBILITY IDEOGRAPH/)
{
$cjk_compat_table[$src] = $dst;
$fullwidth_table[$src] = $dst if $src >= 0x10000;
}
}
}
}
close $UNICODE_DATA;
# patch the category of some special characters
for (my $i = 0; $i < @decomp_table; $i++)
{
next unless defined $decomp_table[$i];
$category_table[$i] |= $category_table[$decomp_table[$i]->[0]];
}
foreach my $cat (keys %special_categories)
{
my $flag = $ctype{$cat};
foreach my $i (@{$special_categories{$cat}}) { $category_table[$i] |= $flag; }
}
for (my $i = 0; $i < @decomp_compat_table; $i++)
{
next unless defined $decomp_compat_table[$i];
next unless @{$decomp_compat_table[$i]} == 2;
$category_table[$i] |= $category_table[$decomp_compat_table[$i]->[1]] & $ctype{"diacritic"};
}
# load the composition exclusions
my $EXCL = open_data_file( "ucd", "CompositionExclusions.txt" );
while (<$EXCL>)
{
s/\#.*//; # remove comments
if (/^([0-9a-fA-F]+)\.\.([0-9a-fA-F]+)\s*$/)
{
foreach my $i (hex $1 .. hex $2) { $comp_exclusions[$i] = 1; }
}
elsif (/^([0-9a-fA-F]+)\s*$/)
{
$comp_exclusions[hex $1] = 1;
}
}
close $EXCL;
# load the IDNA mappings
@idna_decomp_table = @decomp_compat_table;
my $IDNA = open_data_file( "idna", "IdnaMappingTable.txt" );
while (<$IDNA>)
{
s/\#.*//; # remove comments
next if /^\s*$/;
my ($char, $type, $mapping) = split /;/;
my ($ch1, $ch2);
if ($char =~ /([0-9a-fA-F]+)\.\.([0-9a-fA-F]+)/)
{
$ch1 = hex $1;
$ch2 = hex $2;
}
elsif ($char =~ /([0-9a-fA-F]+)/)
{
$ch1 = $ch2 = hex $1;
}
if ($type =~ /mapped/ || $type =~ /deviation/)
{
$mapping =~ s/^\s*(([0-9a-fA-F]+\s+)+)\s*$/$1/;
my @seq = map { hex $_; } split /\s+/, $mapping;
foreach my $i ($ch1 .. $ch2) { $idna_decomp_table[$i] = @seq ? \@seq : [ 0 ]; }
}
elsif ($type =~ /valid/)
{
}
elsif ($type =~ /ignored/)
{
foreach my $i ($ch1 .. $ch2) { $idna_decomp_table[$i] = [ 0 ]; }
}
elsif ($type =~ /disallowed/)
{
foreach my $i ($ch1 .. $ch2)
{
$idna_decomp_table[$i] = undef;
$idna_disallowed[$i] = 1;
}
}
}
close $IDNA;
# load the Unihan mappings
my $UNIHAN = open_data_file( "unihan", "Unihan_Variants.txt" );
while (<$UNIHAN>)
{
s/\#.*//; # remove comments
next if /^\s*$/;
if (/^U\+([0-9a-fA-F]{4})\s+kTraditionalVariant\s+U\+([0-9a-fA-F]{4})$/)
{
next if hex $1 < 0x4dc0; # skip extension A
$chinese_traditional_table[hex $1] = hex $2;
}
elsif (/^U\+([0-9a-fA-F]{4})\s+kSimplifiedVariant\s+U\+([0-9a-fA-F]{4})$/)
{
next if hex $1 < 0x4dc0; # skip extension A
$chinese_simplified_table[hex $1] = hex $2;
}
}
close $UNIHAN;
foreach my $i (0xf900..0xfaff)
{
next unless defined $cjk_compat_table[$i];
next if defined $chinese_simplified_table[$cjk_compat_table[$i]];
$chinese_simplified_table[$i] = $cjk_compat_table[$i];
}
}
################################################################
# add a new registry key
sub add_registry_key($$$)
{
my ($base, $key, $defval) = @_;
$registry_keys{"$base\\$key"} = [ $defval ] unless defined $registry_keys{"$base\\$key"};
}
################################################################
# add a new registry value with explicit type
sub add_registry_value($$$$)
{
my ($base, $key, $name, $value) = @_;
add_registry_key( $base, $key, undef );
push @{$registry_keys{"$base\\$key"}}, "'$name' = $value";
}
################################################################
# add a new registry string value
sub add_registry_string_value($$$$)
{
my ($base, $key, $name, $value) = @_;
$value =~ s/\'/\'\'/g;
add_registry_value( $base, $key, $name, "s '$value'" );
}
################################################################
# add a new registry dword value
sub add_registry_dword_value($$$$)
{
my ($base, $key, $name, $value) = @_;
add_registry_value( $base, $key, $name, "d $value" );
}
################################################################
# add a new registry binary value
sub add_registry_binary_value($$$$)
{
my ($base, $key, $name, $value) = @_;
add_registry_value( $base, $key, $name, "b " . join "", map { sprintf "%02x", $_; } unpack( "C*", $value ));
}
################################################################
# define a new lead byte
sub add_lead_byte($)
{
my $ch = shift;
return if defined $cp2uni[$ch];
push @lead_bytes, $ch;
$cp2uni[$ch] = 0;
}
################################################################
# define a new char mapping
sub add_mapping($$)
{
my ($cp, $uni) = @_;
$cp2uni[$cp] = $uni unless defined($cp2uni[$cp]);
$uni2cp[$uni] = $cp unless defined($uni2cp[$uni]);
if ($cp > 0xff) { add_lead_byte( $cp >> 8 ); }
}
################################################################
# get a mapping including glyph chars for MB_USEGLYPHCHARS
sub get_glyphs_mapping(@)
{
my @table = @_;
for (my $i = 0; $i < @glyph2uni; $i++)
{
$table[$i] = $glyph2uni[$i] if defined $glyph2uni[$i];
}
return @table;
}
################################################################
# build EUC-JP table from the JIS 0208/0212 files
sub dump_eucjp_codepage()
{
@cp2uni = ();
@glyph2uni = ();
@lead_bytes = ();
@uni2cp = ();
$default_char = $DEF_CHAR;
$default_wchar = 0x30fb;
# ASCII chars
foreach my $i (0x00 .. 0x7f) { add_mapping( $i, $i ); }
# lead bytes
foreach my $i (0x8e, 0xa1 .. 0xfe) { add_lead_byte($i); }
# JIS X 0201 right plane
foreach my $i (0xa1 .. 0xdf) { add_mapping( 0x8e00 + $i, 0xfec0 + $i ); }
# undefined chars
foreach my $i (0x80 .. 0x8d, 0x8f .. 0x9f) { $cp2uni[$i] = $i; }
$cp2uni[0xa0] = 0xf8f0;
$cp2uni[0xff] = 0xf8f3;
# Fix backslash conversion
add_mapping( 0xa1c0, 0xff3c );
# Add private mappings for rows undefined in JIS 0208/0212
my $private = 0xe000;
foreach my $hi (0xf5 .. 0xfe)
{
foreach my $lo (0xa1 .. 0xfe)
{
add_mapping( ($hi << 8) + $lo, $private++ );
}
}
foreach my $hi (0xf5 .. 0xfe)
{
foreach my $lo (0x21 .. 0x7e)
{
add_mapping( ($hi << 8) + $lo, $private++ );
}
}
my $INPUT = open_data_file( "jis0208" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^0x[0-9a-fA-F]+\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s+(\#.*)?/)
{
add_mapping( 0x8080 + hex $1, hex $2 );
next;
}
die "Unrecognized line $_\n";
}
close $INPUT;
$INPUT = open_data_file( "jis0212" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s+(\#.*)?/)
{
add_mapping( 0x8000 + hex $1, hex $2 );
next;
}
die "Unrecognized line $_\n";
}
close $INPUT;
output_codepage_file( 20932 );
}
################################################################
# build Korean Wansung table from the KSX1001 file
sub dump_krwansung_codepage(@)
{
my @cp949 = @_;
@cp2uni = ();
@glyph2uni = ();
@lead_bytes = ();
@uni2cp = ();
$default_char = 0x3f;
$default_wchar = 0x003f;
# ASCII and undefined chars
foreach my $i (0x00 .. 0x9f) { add_mapping( $i, $i ); }
add_mapping( 0xa0, 0xf8e6 );
add_mapping( 0xad, 0xf8e7 );
add_mapping( 0xae, 0xf8e8 );
add_mapping( 0xaf, 0xf8e9 );
add_mapping( 0xfe, 0xf8ea );
add_mapping( 0xff, 0xf8eb );
my $INPUT = open_data_file( "ksx1001" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s+(\#.*)?/)
{
add_mapping( 0x8080 + hex $1, hex $2 );
next;
}
die "Unrecognized line $_\n";
}
close $INPUT;
# get some extra mappings from cp 949
my @defined_lb;
map { $defined_lb[$_] = 1; } @lead_bytes;
foreach my $i (0x0000 .. 0xffff)
{
next if ($i >= 0x1100 && $i <= 0x11ff); # range not used in 20949
next unless defined $cp949[$i];
if ($cp949[$i] >= 0xff)
{
# only add chars for lead bytes that exist in 20949
my $hi = $cp949[$i] >> 8;
my $lo = $cp949[$i] & 0xff;
next unless $defined_lb[$hi];
next unless $lo >= 0xa1 && $lo <= 0xfe;
}
add_mapping( $cp949[$i], $i );
}
output_codepage_file( 20949 );
}
################################################################
# dump an array of integers
sub dump_array($$@)
{
my ($bit_width, $default, @array) = @_;
my $format = sprintf "0x%%0%ux", $bit_width / 4;
my $i;
my $ret = " ";
for ($i = 0; $i < $#array; $i++)
{
$ret .= sprintf($format, defined $array[$i] ? $array[$i] : $default);
$ret .= (($i % 8) != 7) ? ", " : ",\n ";
}
$ret .= sprintf($format, defined $array[$i] ? $array[$i] : $default);
return $ret;
}
################################################################
# dump an SBCS mapping table in binary format
sub dump_binary_sbcs_table($)
{
my $codepage = shift;
my @header = ( 13, $codepage, 1, $default_char, $default_wchar, $cp2uni[$default_char], $uni2cp[$default_wchar] );
my $wc_offset = 256 + 3 + (@glyph2uni ? 256 : 0);
print OUTPUT pack "S<*", @header;
print OUTPUT pack "C12", (0) x 12;
print OUTPUT pack "S<*", $wc_offset, map { $_ || 0; } @cp2uni[0 .. 255];
if (@glyph2uni)
{
print OUTPUT pack "S<*", 256, get_glyphs_mapping(@cp2uni[0 .. 255]);
}
else
{
print OUTPUT pack "S<*", 0;
}
print OUTPUT pack "S<*", 0, 0;
print OUTPUT pack "C*", map { defined $_ ? $_ : $default_char; } @uni2cp[0 .. 65535];
}
################################################################
# dump a DBCS mapping table in binary format
sub dump_binary_dbcs_table($)
{
my $codepage = shift;
my @lb_ranges = get_lb_ranges();
my @header = ( 13, $codepage, 2, $default_char, $default_wchar, $cp2uni[$default_char], $uni2cp[$default_wchar] );
my @offsets = (0) x 256;
my $pos = 0;
foreach my $i (@lead_bytes)
{
$offsets[$i] = ($pos += 256);
$cp2uni[$i] = 0;
}
my $wc_offset = 256 + 3 + 256 * (1 + scalar @lead_bytes);
print OUTPUT pack "S<*", @header;
print OUTPUT pack "C12", @lb_ranges, 0 x 12;
print OUTPUT pack "S<*", $wc_offset, map { $_ || 0; } @cp2uni[0 .. 255];
print OUTPUT pack "S<*", 0, scalar @lb_ranges / 2, @offsets;
foreach my $i (@lead_bytes)
{
my $base = $i << 8;
print OUTPUT pack "S<*", map { defined $_ ? $_ : $default_wchar; } @cp2uni[$base .. $base + 255];
}
print OUTPUT pack "S<", 4;
print OUTPUT pack "S<*", map { defined $_ ? $_ : $default_char; } @uni2cp[0 .. 65535];
}
################################################################
# get the list of defined lead byte ranges
sub get_lb_ranges()
{
my @list = ();
my @ranges = ();
foreach my $i (@lead_bytes) { $list[$i] = 1; }
my $on = 0;
for (my $i = 0; $i < 256; $i++)
{
if ($on)
{
if (!defined $list[$i]) { push @ranges, $i-1; $on = 0; }
}
else
{
if ($list[$i]) { push @ranges, $i; $on = 1; }
}
}
if ($on) { push @ranges, 0xff; }
return @ranges;
}
################################################################
# dump the Indic Syllabic Category table
sub dump_indic($)
{
my $filename = shift;
my @indic_table;
my $INPUT = open_data_file( "ucd", "IndicSyllabicCategory.txt" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;\s*([a-zA-Z_]+)\s*#/)
{
my $type = $2;
die "unknown indic $type" unless defined $indic_types{$type};
if (hex $1 < 65536)
{
$indic_table[hex $1] = $indic_types{$type};
}
next;
}
elsif (/^\s*([0-9a-fA-F]+)\.\.\s*([0-9a-fA-F]+)\s*;\s*([A-Za-z_]+)\s*#/)
{
my $type = $3;
die "unknown indic $type" unless defined $indic_types{$type};
if (hex $1 < 65536 and hex $2 < 65536)
{
foreach my $i (hex $1 .. hex $2)
{
$indic_table[$i] = $indic_types{$type};
}
}
next;
}
die "malformed line $_";
}
close $INPUT;
my $prev_data_file = $current_data_file;
$INPUT = open_data_file( "ucd", "IndicPositionalCategory.txt" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;\s*([a-zA-Z_]+)\s*#/)
{
my $type = $2;
die "unknown matra $type" unless defined $matra_types{$type};
$indic_table[hex $1] |= $matra_types{$type} << 8;
next;
}
elsif (/^\s*([0-9a-fA-F]+)\.\.\s*([0-9a-fA-F]+)\s*;\s*([A-Za-z_]+)\s*#/)
{
my $type = $3;
die "unknown matra $type" unless defined $matra_types{$type};
foreach my $i (hex $1 .. hex $2)
{
$indic_table[$i] |= $matra_types{$type} << 8;
}
next;
}
die "malformed line $_";
}
close $INPUT;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
print "Building $filename\n";
print OUTPUT "/* Unicode Indic Syllabic Category */\n";
print OUTPUT "/* generated from $prev_data_file */\n";
print OUTPUT "/* and from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
print OUTPUT "#include \"windef.h\"\n\n";
dump_two_level_mapping( "indic_syllabic_table", $indic_types{'Other'}, 16, @indic_table );
close OUTPUT;
save_file($filename);
}
################################################################
# dump the Line Break Properties table
sub dump_linebreak($)
{
my $filename = shift;
my @break_table;
my $INPUT = open_data_file( "ucd", "LineBreak.txt" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;\s*([0-9A-Z][0-9A-Z][0-9A-Z])+\s*/)
{
my $type = $2;
die "unknown breaktype $type" unless defined $break_types{$type};
$break_table[hex $1] = $break_types{$type};
next;
}
elsif (/^\s*([0-9a-fA-F]+)\.\.\s*([0-9a-fA-F]+)\s*;\s*([0-9A-Z][0-9A-Z][0-9A-Z])+\s*/)
{
my $type = $3;
die "unknown breaktype $type" unless defined $break_types{$type};
foreach my $i (hex $1 .. hex $2)
{
$break_table[$i] = $break_types{$type};
}
next;
}
elsif (/^\s*([0-9a-fA-F]+)\s*;\s*([0-9A-Z][0-9A-Z])+\s*/)
{
my $type = $2;
die "unknown breaktype $type" unless defined $break_types{$type};
$break_table[hex $1] = $break_types{$type};
next;
}
elsif (/^\s*([0-9a-fA-F]+)\.\.\s*([0-9a-fA-F]+)\s*;\s*([0-9A-Z][0-9A-Z])+\s*/)
{
my $type = $3;
die "unknown breaktype $type" unless defined $break_types{$type};
foreach my $i (hex $1 .. hex $2)
{
$break_table[$i] = $break_types{$type};
}
next;
}
die "malformed line $_";
}
close $INPUT;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
print "Building $filename\n";
print OUTPUT "/* Unicode Line Break Properties */\n";
print OUTPUT "/* generated from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
print OUTPUT "#include \"windef.h\"\n\n";
dump_three_level_mapping( "wine_linebreak_table", $break_types{'XX'}, 16, @break_table );
close OUTPUT;
save_file($filename);
}
my %scripts =
(
"Unknown" => 0,
"Common" => 1,
"Inherited" => 2,
"Arabic" => 3,
"Armenian" => 4,
"Avestan" => 5,
"Balinese" => 6,
"Bamum" => 7,
"Batak" => 8,
"Bengali" => 9,
"Bopomofo" => 10,
"Brahmi" => 11,
"Braille" => 12,
"Buginese" => 13,
"Buhid" => 14,
"Canadian_Aboriginal" => 15,
"Carian" => 16,
"Cham" => 17,
"Cherokee" => 18,
"Coptic" => 19,
"Cuneiform" => 20,
"Cypriot" => 21,
"Cyrillic" => 22,
"Deseret" => 23,
"Devanagari" => 24,
"Egyptian_Hieroglyphs" => 25,
"Ethiopic" => 26,
"Georgian" => 27,
"Glagolitic" => 28,
"Gothic" => 29,
"Greek" => 30,
"Gujarati" => 31,
"Gurmukhi" => 32,
"Han" => 33,
"Hangul" => 34,
"Hanunoo" => 35,
"Hebrew" => 36,
"Hiragana" => 37,
"Imperial_Aramaic" => 38,
"Inscriptional_Pahlavi" => 39,
"Inscriptional_Parthian" => 40,
"Javanese" => 41,
"Kaithi" => 42,
"Kannada" => 43,
"Katakana" => 44,
"Kayah_Li" => 45,
"Kharoshthi" => 46,
"Khmer" => 47,
"Lao" => 48,
"Latin" => 49,
"Lepcha" => 50,
"Limbu" => 51,
"Linear_B" => 52,
"Lisu" => 53,
"Lycian" => 54,
"Lydian" => 55,
"Malayalam" => 56,
"Mandaic" => 57,
"Meetei_Mayek" => 58,
"Mongolian" => 59,
"Myanmar" => 60,
"New_Tai_Lue" => 61,
"Nko" => 62,
"Ogham" => 63,
"Ol_Chiki" => 64,
"Old_Italic" => 65,
"Old_Persian" => 66,
"Old_South_Arabian" => 67,
"Old_Turkic" => 68,
"Oriya" => 69,
"Osmanya" => 70,
"Phags_Pa" => 71,
"Phoenician" => 72,
"Rejang" => 73,
"Runic" => 74,
"Samaritan" => 75,
"Saurashtra" => 76,
"Shavian" => 77,
"Sinhala" => 78,
"Sundanese" => 79,
"Syloti_Nagri" => 80,
"Syriac" => 81,
"Tagalog" => 82,
"Tagbanwa" => 83,
"Tai_Le" => 84,
"Tai_Tham" => 85,
"Tai_Viet" => 86,
"Tamil" => 87,
"Telugu" => 88,
"Thaana" => 89,
"Thai" => 90,
"Tibetan" => 91,
"Tifinagh" => 92,
"Ugaritic" => 93,
"Vai" => 94,
"Yi" => 95,
# Win8/Win8.1
"Chakma" => 96,
"Meroitic_Cursive" => 97,
"Meroitic_Hieroglyphs" => 98,
"Miao" => 99,
"Sharada" => 100,
"Sora_Sompeng" => 101,
"Takri" => 102,
# Win10
"Bassa_Vah" => 103,
"Caucasian_Albanian" => 104,
"Duployan" => 105,
"Elbasan" => 106,
"Grantha" => 107,
"Khojki" => 108,
"Khudawadi" => 109,
"Linear_A" => 110,
"Mahajani" => 111,
"Manichaean" => 112,
"Mende_Kikakui" => 113,
"Modi" => 114,
"Mro" => 115,
"Nabataean" => 116,
"Old_North_Arabian" => 117,
"Old_Permic" => 118,
"Pahawh_Hmong" => 119,
"Palmyrene" => 120,
"Pau_Cin_Hau" => 121,
"Psalter_Pahlavi" => 122,
"Siddham" => 123,
"Tirhuta" => 124,
"Warang_Citi" => 125,
# Win10 RS1
"Adlam" => 126,
"Ahom" => 127,
"Anatolian_Hieroglyphs" => 128,
"Bhaiksuki" => 129,
"Hatran" => 130,
"Marchen" => 131,
"Multani" => 132,
"Newa" => 133,
"Old_Hungarian" => 134,
"Osage" => 135,
"SignWriting" => 136,
"Tangut" => 137,
# Win10 RS4
"Masaram_Gondi" => 138,
"Nushu" => 139,
"Soyombo" => 140,
"Zanabazar_Square" => 141,
# Win10 1903
"Dogra" => 142,
"Gunjala_Gondi" => 143,
"Hanifi_Rohingya" => 144,
"Makasar" => 145,
"Medefaidrin" => 146,
"Old_Sogdian" => 147,
"Sogdian" => 148,
# Win10 2004
"Elymaic" => 149,
"Nyiakeng_Puachue_Hmong" => 150,
"Nandinagari" => 151,
"Wancho" => 152,
# Win11
"Chorasmian" => 153,
"Dives_Akuru" => 154,
"Khitan_Small_Script" => 155,
"Yezidi" => 156,
);
################################################################
# dump Script IDs table
sub dump_scripts($)
{
my $filename = shift;
my $header = $filename;
my @scripts_table;
my $script_index;
my $i;
my $INPUT = open_data_file( "ucd", "Scripts.txt" );
# Fill the table
# Unknown script id is always 0, so undefined scripts are automatically treated as such
while (<$INPUT>)
{
my $type = "";
next if /^\#/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;\s*([a-zA-Z_]+)\s*/)
{
$type = $2;
if (defined $scripts{$type})
{
$scripts_table[hex $1] = $scripts{$type};
}
next;
}
elsif (/^\s*([0-9a-fA-F]+)\.\.\s*([0-9a-fA-F]+)\s*;\s*([a-zA-Z_]+)\s*/)
{
$type = $3;
if (defined $scripts{$type})
{
foreach my $i (hex $1 .. hex $2)
{
$scripts_table[$i] = $scripts{$type};
}
}
next;
}
}
close $INPUT;
$header = "$filename.h";
open OUTPUT,">$header.new" or die "Cannot create $header";
print "Building $header\n";
print OUTPUT "/* Unicode Script IDs */\n";
print OUTPUT "/* generated from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
print OUTPUT "enum unicode_script_id {\n";
foreach my $script (sort { $scripts{$a} <=> $scripts{$b} } keys %scripts)
{
print OUTPUT " Script_$script = $scripts{$script},\n";
}
print OUTPUT " Script_LastId = ", (scalar keys %scripts) - 1, "\n";
print OUTPUT "};\n";
close OUTPUT;
save_file($header);
$filename = "$filename.c";
open OUTPUT,">$filename.new" or die "Cannot create $header";
print "Building $filename\n";
print OUTPUT "/* Unicode Script IDs */\n";
print OUTPUT "/* generated from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
print OUTPUT "#include \"windef.h\"\n\n";
dump_three_level_mapping( "wine_scripts_table", 0, 16, @scripts_table );
close OUTPUT;
save_file($filename);
}
################################################################
# dump the BiDi mirroring table
sub dump_mirroring($)
{
my $filename = shift;
my @mirror_table = ();
my $INPUT = open_data_file( "ucd", "BidiMirroring.txt" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;\s*([0-9a-fA-F]+)/)
{
$mirror_table[hex $1] = hex $2;
next;
}
die "malformed line $_";
}
close $INPUT;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
print "Building $filename\n";
print OUTPUT "/* Unicode BiDi mirroring */\n";
print OUTPUT "/* generated from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
print OUTPUT "#include \"windef.h\"\n\n";
dump_two_level_mapping( "wine_mirror_map", 0, 16, @mirror_table );
close OUTPUT;
save_file($filename);
}
################################################################
# dump the Bidi Brackets
sub dump_bracket($)
{
my $filename = shift;
my @bracket_table;
my $INPUT = open_data_file( "ucd", "BidiBrackets.txt" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;\s*([0-9a-fA-F]+);\s*([con])/)
{
my $type = $3;
die "unknown bracket $type" unless defined $bracket_types{$type};
die "characters too distant $1 and $2" if abs(hex($2) - hex($1)) >= 128;
$bracket_table[hex $1] = (hex($2) - hex($1)) % 255;
$bracket_table[hex $1] += $bracket_types{$type} << 8;
next;
}
die "malformed line $_";
}
close $INPUT;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
print "Building $filename\n";
print OUTPUT "/* Unicode Bidirectional Bracket table */\n";
print OUTPUT "/* generated from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
print OUTPUT "#include \"windef.h\"\n\n";
dump_two_level_mapping( "bidi_bracket_table", 0, 16, @bracket_table );
close OUTPUT;
save_file($filename);
}
################################################################
# dump the Arabic shaping table
sub dump_shaping($)
{
my $filename = shift;
my @joining_table = @initial_joining_table;
my $INPUT = open_data_file( "ucd", "ArabicShaping.txt" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;.*;\s*([RLDCUT])\s*;\s*(\w+)/)
{
my $type = $2;
$joining_table[hex $1] = $joining_types{$type};
next;
}
die "malformed line $_";
}
close $INPUT;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
print "Building $filename\n";
print OUTPUT "/* Unicode Arabic shaping */\n";
print OUTPUT "/* generated from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
print OUTPUT "#include \"windef.h\"\n\n";
dump_two_level_mapping( "wine_shaping_table", 0, 16, @joining_table );
print OUTPUT "\nconst unsigned short wine_shaping_forms[256][4] =\n{\n";
for (my $i = 0x600; $i <= 0x6ff; $i++)
{
printf OUTPUT " { 0x%04x, 0x%04x, 0x%04x, 0x%04x },\n",
${joining_forms{"isolated"}}[$i] || $i,
${joining_forms{"final"}}[$i] || $i,
${joining_forms{"initial"}}[$i] || $i,
${joining_forms{"medial"}}[$i] || $i;
}
print OUTPUT "};\n";
close OUTPUT;
save_file($filename);
}
################################################################
# dump the Arabic shaping table
sub dump_arabic_shaping($)
{
my $filename = shift;
my @joining_table = @initial_joining_table;
my $INPUT = open_data_file( "ucd", "ArabicShaping.txt" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;.*;\s*([RLDCUT])\s*;\s*(\w+)/)
{
my $type = $2;
my $group = $3;
if ($group eq "ALAPH" || $group eq "DALATH RISH")
{
$joining_table[hex $1] = $joining_types{$group};
}
else
{
$joining_table[hex $1] = $joining_types{$type};
}
next;
}
die "malformed line $_";
}
close $INPUT;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
print "Building $filename\n";
print OUTPUT "/* Unicode Arabic shaping */\n";
print OUTPUT "/* generated from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
print OUTPUT "#include \"windef.h\"\n\n";
dump_three_level_mapping( "arabic_shaping_table", 0, 16, @joining_table );
close OUTPUT;
save_file($filename);
}
################################################################
# dump the Vertical Orientation table
sub dump_vertical($$)
{
my ($filename, $unix) = @_;
my @vertical_table;
my $INPUT = open_data_file( "ucd", "VerticalOrientation.txt" );
while (<$INPUT>)
{
next if /^\#/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
if (/^\s*([0-9a-fA-F]+)\s*;\s*([a-zA-Z_]+)\s*/)
{
my $type = $2;
die "unknown vertical $type" unless defined $vertical_types{$type};
if (hex $1 < 65536)
{
$vertical_table[hex $1] = $vertical_types{$type};
}
next;
}
elsif (/^\s*([0-9a-fA-F]+)\.\.\s*([0-9a-fA-F]+)\s*;\s*([A-Za-z_]+)\s*/)
{
my $type = $3;
die "unknown vertical $type" unless defined $vertical_types{$type};
foreach my $i (hex $1 .. hex $2)
{
$vertical_table[$i] = $vertical_types{$type};
}
next;
}
die "malformed line $_";
}
close $INPUT;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
print "Building $filename\n";
print OUTPUT "/* Unicode Vertical Orientation */\n";
print OUTPUT "/* generated from $current_data_file */\n";
print OUTPUT "/* DO NOT EDIT!! */\n\n";
if ($unix)
{
print OUTPUT "#if 0\n";
print OUTPUT "#pragma makedep unix\n";
print OUTPUT "#endif\n\n";
}
print OUTPUT "#include \"windef.h\"\n\n";
dump_two_level_mapping( "vertical_orientation_table", $vertical_types{'R'}, 16, @vertical_table );
close OUTPUT;
save_file($filename);
}
################################################################
# compress a mapping table by removing identical rows
sub compress_array($$@)
{
my $rows = shift;
my $def = shift;
my @table = @_;
my $len = @table / $rows;
my @array;
my $data = "";
# try to merge table rows
for (my $row = 0; $row < $rows; $row++)
{
my $rowtxt = pack "U*", map { defined($_) ? $_ : $def; } @table[($row * $len)..(($row + 1) * $len - 1)];
my $pos = index $data, $rowtxt;
if ($pos == -1)
{
# check if the tail of the data can match the start of the new row
my $first = substr( $rowtxt, 0, 1 );
for (my $i = length($data) - 1; $i > 0; $i--)
{
$pos = index( substr( $data, -$i ), $first );
last if $pos == -1;
$i -= $pos;
next unless substr( $data, -$i ) eq substr( $rowtxt, 0, $i );
substr( $data, -$i ) = "";
last;
}
$pos = length $data;
$data .= $rowtxt;
}
$array[$row] = $rows + $pos;
}
return @array, unpack "U*", $data;
}
################################################################
# dump a char -> value mapping table using two-level tables
sub dump_two_level_mapping($$$@)
{
my $name = shift;
my $def = shift;
my $size = shift;
my $type = $size == 16 ? "unsigned short" : "unsigned int";
my (@array, @row_array, @data, @row_data);
(@row_array[0..4095], @data) = compress_array( 4096, $def, @_[0..65535] );
(@array[0..255], @row_data) = compress_array( 256, 0, @row_array );
for (my $i = 0; $i < @row_data; $i++) { $row_data[$i] += @row_data + 256 - 4096; }
printf OUTPUT "const %s %s[%d] =\n{\n", $type, $name, @array + @row_data + @data;
printf OUTPUT " /* level 1 offsets */\n%s,\n", dump_array( $size, 0, @array );
printf OUTPUT " /* level 2 offsets */\n%s,\n", dump_array( $size, 0, @row_data );
printf OUTPUT " /* values */\n%s\n};\n", dump_array( $size, 0, @data );
}
################################################################
# dump a char -> value mapping table using three-level tables
sub dump_three_level_mapping($$@)
{
my $name = shift;
my $def = shift;
my $size = shift;
my $type = $size == 16 ? "unsigned short" : "unsigned int";
my $level3 = ($MAX_CHAR + 1) / 16;
my $level2 = $level3 / 16;
my $level1 = $level2 / 16;
my @array3 = compress_array( $level3, $def, @_[0..$MAX_CHAR] );
my @array2 = compress_array( $level2, 0, @array3[0..$level3-1] );
my @array1 = compress_array( $level1, 0, @array2[0..$level2-1] );
for (my $i = $level2; $i < @array2; $i++) { $array2[$i] += @array1 + @array2 - $level2 - $level3; }
for (my $i = $level1; $i < @array1; $i++) { $array1[$i] += @array1 - $level2; }
printf OUTPUT "const %s %s[%u] =\n{\n", $type, $name, @array1 + (@array2 - $level2) + (@array3 - $level3);
printf OUTPUT " /* level 1 offsets */\n%s,\n", dump_array( $size, 0, @array1[0..$level1-1] );
printf OUTPUT " /* level 2 offsets */\n%s,\n", dump_array( $size, 0, @array1[$level1..$#array1] );
printf OUTPUT " /* level 3 offsets */\n%s,\n", dump_array( $size, 0, @array2[$level2..$#array2] );
printf OUTPUT " /* values */\n%s\n};\n", dump_array( $size, 0, @array3[$level3..$#array3] );
}
################################################################
# dump a binary case mapping table in l_intl.nls format
sub dump_binary_case_table(@)
{
my (@table) = @_;
my @difftable;
my @res;
for (my $i = 0; $i < @table; $i++)
{
next unless defined $table[$i];
$difftable[$i] = ($table[$i] - $i) & 0xffffffff;
}
my (@low_array1, @low_array2, @low_data, @low_row_data);
(@low_array2[0..4095], @low_data) = compress_array( 4096, 0, @difftable[0..65535] );
(@low_array1[0..255], @low_row_data) = compress_array( 256, 0, @low_array2 );
if (scalar @table > 0x10000)
{
my (@high_array1, @high_array2, @high_data, @high_row_data);
(@high_array2[0..32767], @high_data) = compress_array( 32768, 0, @difftable[65536..$MAX_CHAR] );
(@high_array1[0..1023], @high_row_data) = compress_array( 1024, 0, @high_array2 );
push @res, map { $_ + 1024; } @low_array1;
push @res, map { $_ + @res + @low_row_data + @low_data; } @high_array1;
push @res, map { $_ + @res + @low_row_data - 4096; } @low_row_data;
push @res, @low_data;
push @res, map { 2 * ($_ - 32768) + @res + @high_row_data; } @high_row_data;
return pack( "S<*", 1 + scalar @res + 2 * scalar @high_data, @res ) . pack( "L<*", @high_data );
}
else
{
push @res, @low_array1;
push @res, map { $_ + @res + @low_row_data - 4096; } @low_row_data;
push @res, @low_data;
return pack "S<*", 1 + scalar @res, @res;
}
}
################################################################
# dump case mappings for l_intl.nls
sub dump_intl_nls($)
{
my @upper_table = @toupper_table;
my @lower_table = @tolower_table;
remove_linguistic_mappings( \@upper_table, \@lower_table );
my $upper = dump_binary_case_table( @upper_table[0..65535] );
my $lower = dump_binary_case_table( @lower_table[0..65535] );
my $filename = shift;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
printf "Building $filename\n";
binmode OUTPUT;
print OUTPUT pack "S<", 1; # version
print OUTPUT $upper;
print OUTPUT $lower;
close OUTPUT;
save_file($filename);
}
################################################################
# dump the bidi direction table
sub dump_bidi_dir_table($)
{
my $filename = shift;
open OUTPUT,">$filename.new" or die "Cannot create $filename";
printf "Building $filename\n";
printf OUTPUT "/* Unicode BiDi direction table */\n";
printf OUTPUT "/* Automatically generated; DO NOT EDIT!! */\n\n";
printf OUTPUT "#include \"windef.h\"\n\n";
my @table;
for (my $i = 0; $i < @direction_table; $i++)
{
$table[$i] = $bidi_types{$direction_table[$i]} if defined $direction_table[$i];
}
dump_three_level_mapping( "bidi_direction_table", $bidi_types{"L"}, 16, @table );
close OUTPUT;
save_file($filename);
}
sub rol($$)
{
my ($byte, $count) = @_;
return (($byte << $count) | ($byte >> (8 - $count))) & 0xff;
}
################################################################
# compress the character properties table
sub compress_char_props_table($@)
{
my $rows = shift;
my @table = @_;
my $len = @table / $rows;
my $pos = 0;
my @array = (0) x $rows;
my %sequences;
# add some predefined sequences
foreach my $i (0, 0xfb .. 0xff) { $sequences{pack "L*", (rol($i,5)) x $len} = $i; }
# try to merge table rows
for (my $row = 0; $row < $rows; $row++)
{
my @table_row = map { defined $_ ? $_ : 0x7f; } @table[($row * $len)..(($row + 1) * $len - 1)];
my $rowtxt = pack "L*", @table_row;
if (defined($sequences{$rowtxt}))
{
# reuse an existing row
$array[$row] = $sequences{$rowtxt};
}
else
{
# create a new row
$sequences{$rowtxt} = $array[$row] = ++$pos;
push @array, @table_row;
}
}
return @array;
}
################################################################
# dump a normalization table in binary format
sub dump_norm_table($)
{
my $filename = shift;
my %forms = ( "nfc" => 1, "nfd" => 2, "nfkc" => 5, "nfkd" => 6, "idna" => 13 );
my %decomp = ( "nfc" => \@decomp_table,
"nfd" => \@decomp_table,
"nfkc" => \@decomp_compat_table,
"nfkd" => \@decomp_compat_table ,
"idna" => \@idna_decomp_table );
open OUTPUT,">$filename.new" or die "Cannot create $filename";
print "Building $filename\n";
my $type = $filename;
$type =~ s!.*/norm(\w+)\.nls!$1!;
my $compose = $forms{$type} & 1;
my $compat = !!($forms{$type} & 4) + ($type eq "idna");
my @version = split /\./, $UNIVERSION;
# combining classes
my @classes;
my @class_values;
foreach my $c (grep defined, @combining_class_table)
{
$classes[$c] = 1 if $c < 0x100;
}
for (my $i = 0; $i < @classes; $i++)
{
next unless defined $classes[$i];
$classes[$i] = @class_values;
push @class_values, $i;
}
push @class_values, 0 if (@class_values % 2);
die "too many classes" if @class_values >= 0x40;
# character properties
my @char_props;
my @decomposed;
my @comp_hash_table;
my $comp_hash_size = $compose ? 254 : 0;
for (my $i = 0; $i <= $MAX_CHAR; $i++)
{
next unless defined $combining_class_table[$i];
if (defined $decomp{$type}->[$i])
{
my @dec = get_decomposition( $i, $decomp{$type} );
if ($compose && (my @comp = get_composition( $i, $compat )))
{
my $hash = ($comp[0] + 95 * $comp[1]) % $comp_hash_size;
push @{$comp_hash_table[$hash]}, to_utf16( @comp, $i );
my $val = 0;
foreach my $d (@dec)
{
$val = $combining_class_table[$d];
last if $val;
}
$char_props[$i] = $classes[$val];
}
else
{
$char_props[$i] = 0xbf;
}
@dec = compose_hangul( @dec ) if $compose;
@dec = to_utf16( @dec );
push @dec, 0 if @dec >= 7;
$decomposed[$i] = \@dec;
}
else
{
if ($combining_class_table[$i] == 0x100)
{
$char_props[$i] = 0x7f;
}
elsif ($combining_class_table[$i])
{
$char_props[$i] = $classes[$combining_class_table[$i]] | 0x80;
}
elsif ($type eq "idna" && defined $idna_disallowed[$i])
{
$char_props[$i] = 0xff;
}
else
{
$char_props[$i] = 0;
}
}
}
if ($compose)
{
for (my $i = 0; $i <= $MAX_CHAR; $i++)
{
my @comp = get_composition( $i, $compat );
next unless @comp;
if ($combining_class_table[$comp[1]])
{
$char_props[$comp[0]] |= 0x40 unless $char_props[$comp[0]] & 0x80;
$char_props[$comp[1]] |= 0x40;
}
else
{
$char_props[$comp[0]] = ($char_props[$comp[0]] & ~0x40) | 0x80;
$char_props[$comp[1]] |= 0xc0;
}
}
}
# surrogates
foreach my $i (0xd800..0xdbff) { $char_props[$i] = 0xdf; }
foreach my $i (0xdc00..0xdfff) { $char_props[$i] = 0x9f; }
# Hangul
if ($type eq "nfc") { foreach my $i (0x1100..0x117f) { $char_props[$i] = 0xff; } }
elsif ($compose) { foreach my $i (0x1100..0x11ff) { $char_props[$i] = 0xff; } }
foreach my $i (0xac00..0xd7ff) { $char_props[$i] = 0xff; }
# invalid chars
if ($type eq "idna") { foreach my $i (0x00..0x1f, 0x7f) { $char_props[$i] = 0xff; } }
foreach my $i (0xfdd0..0xfdef) { $char_props[$i] = 0xff; }
foreach my $i (0x00..0x10)
{
$char_props[($i << 16) | 0xfffe] = 0xff;
$char_props[($i << 16) | 0xffff] = 0xff;
}
# decomposition hash table
my @decomp_hash_table;
my @decomp_hash_index;
my @decomp_hash_data;
my $decomp_hash_size = 944;
# build string of character data, reusing substrings when possible
my $decomp_char_data = "";
foreach my $i (sort { @{$b} <=> @{$a} } grep defined, @decomposed)
{
my $str = pack "U*", @{$i};
$decomp_char_data .= $str if index( $decomp_char_data, $str) == -1;
}
for (my $i = 0; $i < @decomposed; $i++)
{
next unless defined $decomposed[$i];
my $pos = index( $decomp_char_data, pack( "U*", @{$decomposed[$i]} ));
die "sequence not found" if $pos == -1;
my $len = @{$decomposed[$i]};
$len = 7 if $len > 7;
my $hash = $i % $decomp_hash_size;
push @{$decomp_hash_table[$hash]}, [ $i, ($len << 13) | $pos ];
}
for (my $i = 0; $i < $decomp_hash_size; $i++)
{
$decomp_hash_index[$i] = @decomp_hash_data / 2;
next unless defined $decomp_hash_table[$i];
if (@{$decomp_hash_table[$i]} == 1)
{
my $entry = $decomp_hash_table[$i]->[0];
if ($char_props[$entry->[0]] == 0xbf)
{
$decomp_hash_index[$i] = $entry->[1];
next;
}
}
foreach my $entry (@{$decomp_hash_table[$i]})
{
push @decomp_hash_data, $entry->[0] & 0xffff, $entry->[1];
}
}
push @decomp_hash_data, 0, 0;
# composition hash table
my @comp_hash_index;
my @comp_hash_data;
if (@comp_hash_table)
{
for (my $i = 0; $i < $comp_hash_size; $i++)
{
$comp_hash_index[$i] = @comp_hash_data;
push @comp_hash_data, @{$comp_hash_table[$i]} if defined $comp_hash_table[$i];
}
$comp_hash_index[$comp_hash_size] = @comp_hash_data;
push @comp_hash_data, 0, 0, 0;
}
my $level1 = ($MAX_CHAR + 1) / 128;
my @rows = compress_char_props_table( $level1, @char_props[0..$MAX_CHAR] );
my @header = ( $version[0], $version[1], $version[2], 0, $forms{$type}, $compat ? 18 : 3,
0, $decomp_hash_size, $comp_hash_size, 0 );
my @tables = (0) x 8;
$tables[0] = 16 + @header + @tables;
$tables[1] = $tables[0] + @class_values / 2;
$tables[2] = $tables[1] + $level1 / 2;
$tables[3] = $tables[2] + (@rows - $level1) / 2;
$tables[4] = $tables[3] + @decomp_hash_index;
$tables[5] = $tables[4] + @decomp_hash_data;
$tables[6] = $tables[5] + length $decomp_char_data;
$tables[7] = $tables[6] + @comp_hash_index;
print OUTPUT pack "S<16", unpack "U*", "norm$type.nlp";
print OUTPUT pack "S<*", @header;
print OUTPUT pack "S<*", @tables;
print OUTPUT pack "C*", @class_values;
print OUTPUT pack "C*", @rows[0..$level1-1];
print OUTPUT pack "C*", @rows[$level1..$#rows];
print OUTPUT pack "S<*", @decomp_hash_index;
print OUTPUT pack "S<*", @decomp_hash_data;
print OUTPUT pack "S<*", unpack "U*", $decomp_char_data;
print OUTPUT pack "S<*", @comp_hash_index;
print OUTPUT pack "S<*", @comp_hash_data;
close OUTPUT;
save_file($filename);
add_registry_string_value( $nlskey, "Normalization", sprintf( "%x", $forms{$type} ), "norm$type.nls" );
}
################################################################
# output a codepage definition file from the global tables
sub output_codepage_file($)
{
my $codepage = shift;
my $output = sprintf "nls/c_%03d.nls", $codepage;
open OUTPUT,">$output.new" or die "Cannot create $output";
printf "Building %s\n", $output;
if (!@lead_bytes) { dump_binary_sbcs_table( $codepage ); }
else { dump_binary_dbcs_table( $codepage ); }
close OUTPUT;
save_file($output);
add_registry_string_value( $nlskey, "Codepage", sprintf( "%d", $codepage ), sprintf( "c_%03d.nls", $codepage ));
}
################################################################
# output a codepage table from a Microsoft-style mapping file
sub dump_msdata_codepage($)
{
my $filename = shift;
my $state = "";
my ($codepage, $width, $count);
my ($lb_cur, $lb_end);
@cp2uni = ();
@glyph2uni = ();
@lead_bytes = ();
@uni2cp = ();
$default_char = $DEF_CHAR;
$default_wchar = $DEF_CHAR;
my $INPUT = open_data_file( "codepages", $filename );
while (<$INPUT>)
{
next if /^;/; # skip comments
next if /^\s*$/; # skip empty lines
next if /\x1a/; # skip ^Z
last if /^ENDCODEPAGE/;
if (/^CODEPAGE\s+(\d+)/)
{
$codepage = $1;
next;
}
if (/^CPINFO\s+(\d+)\s+0x([0-9a-fA-f]+)\s+0x([0-9a-fA-F]+)/)
{
$width = $1;
$default_char = hex $2;
$default_wchar = hex $3;
next;
}
if (/^(MBTABLE|GLYPHTABLE|WCTABLE|DBCSRANGE|DBCSTABLE)\s+(\d+)/)
{
$state = $1;
$count = $2;
next;
}
if (/^0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)/)
{
if ($state eq "MBTABLE")
{
my $cp = hex $1;
my $uni = hex $2;
$cp2uni[$cp] = $uni unless defined($cp2uni[$cp]);
next;
}
if ($state eq "GLYPHTABLE")
{
my $cp = hex $1;
my $uni = hex $2;
$glyph2uni[$cp] = $uni unless defined($glyph2uni[$cp]);
next;
}
if ($state eq "WCTABLE")
{
my $uni = hex $1;
my $cp = hex $2;
$uni2cp[$uni] = $cp unless defined($uni2cp[$uni]);
next;
}
if ($state eq "DBCSRANGE")
{
my $start = hex $1;
my $end = hex $2;
for (my $i = $start; $i <= $end; $i++) { add_lead_byte( $i ); }
$lb_cur = $start;
$lb_end = $end;
next;
}
if ($state eq "DBCSTABLE")
{
my $mb = hex $1;
my $uni = hex $2;
my $cp = ($lb_cur << 8) | $mb;
$cp2uni[$cp] = $uni unless defined($cp2uni[$cp]);
if (!--$count)
{
if (++$lb_cur > $lb_end) { $state = "DBCSRANGE"; }
}
next;
}
}
die "$filename: Unrecognized line $_\n";
}
close $INPUT;
output_codepage_file( $codepage );
if ($codepage == 949) { dump_krwansung_codepage( @uni2cp ); }
}
################################################################
# align a string length
sub align_string($$)
{
my ($align, $str) = @_;
$str .= pack "C*", (0) x ($align - length($str) % $align) if length($str) % $align;
return $str;
}
################################################################
# pad a string with zeros
sub pad_string($$)
{
my ($pad, $str) = @_;
$str .= pack "C*", (0) x ($pad - length($str)) if length($str) < $pad;
return $str;
}
################################################################
# pack a GUID string
sub pack_guid($)
{
$_ = shift;
/([0-9A-Fa-f]{8})-([0-9A-Fa-f]{4})-([0-9A-Fa-f]{4})-([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})-([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})/;
return pack "L<S<2C8", hex $1, hex $2, hex $3, hex $4, hex $5, hex $6, hex $7, hex $8, hex $9, hex $10, hex $11;
}
################################################################
# comparison function for compression sort
sub cmp_compression
{
return scalar @{$a} <=> scalar @{$b} ||
$a->[4] <=> $b->[4] ||
$a->[5] <=> $b->[5] ||
$a->[6] <=> $b->[6] ||
$a->[7] <=> $b->[7] ||
$a->[8] <=> $b->[8] ||
$a->[9] <=> $b->[9] ||
$a->[10] <=> $b->[10] ||
$a->[11] <=> $b->[11] ||
$a->[12] <=> $b->[12];
}
################################################################
# build a binary sort keys table
sub dump_sortkey_table($)
{
my $filename = shift;
my @keys;
my ($part, $section, $subsection, $guid, $version, $ling_flag);
my @multiple_weights;
my @expansions;
my @compressions;
my %exceptions;
my %guids;
my %compr_flags;
my %locales;
my $default_guid = "00000001-57ee-1e5c-00b4-d0000bb1e11e";
my $jamostr = "";
my $re_hex = '0x[0-9A-Fa-f]+';
my $re_key = '(\d+\s+\d+\s+\d+\s+\d+)';
$guids{$default_guid} = { };
my %flags = ( "HAS_3_BYTE_WEIGHTS" => 0x01, "REVERSEDIACRITICS" => 0x10, "DOUBLECOMPRESSION" => 0x20, "INVERSECASING" => 0x40 );
my $KEYS = open_data_file( "sorting" );
printf "Building $filename\n";
while (<$KEYS>)
{
s/\s*;.*$//;
next if /^\s*$/; # skip empty lines
if (/^\s*(SORTKEY|SORTTABLES)/)
{
$part = $1;
next;
}
if (/^\s*(ENDSORTKEY|ENDSORTTABLES)/)
{
$part = $section = "";
next;
}
if (/^\s*(DEFAULT|RELEASE|REVERSEDIACRITICS|DOUBLECOMPRESSION|INVERSECASING|MULTIPLEWEIGHTS|EXPANSION|COMPATIBILITY|COMPRESSION|EXCEPTION|JAMOSORT)\s+/)
{
$section = $1;
$guid = undef;
next;
}
next unless $part;
if ("$part.$section" eq "SORTKEY.DEFAULT")
{
if (/^\s*($re_hex)\s+$re_key/)
{
$keys[hex $1] = [ split(/\s+/,$2) ];
next;
}
}
elsif ("$part.$section" eq "SORTTABLES.RELEASE")
{
if (/^\s*NLSVERSION\s+0x([0-9A-Fa-f]+)/)
{
$version = hex $1;
next;
}
if (/^\s*DEFINEDVERSION\s+0x([0-9A-Fa-f]+)/)
{
# ignore for now
next;
}
}
elsif ("$part.$section" eq "SORTTABLES.REVERSEDIACRITICS" ||
"$part.$section" eq "SORTTABLES.DOUBLECOMPRESSION" ||
"$part.$section" eq "SORTTABLES.INVERSECASING")
{
if (/^\s*SORTGUID\s+([-0-9A-Fa-f]+)/)
{
$guid = lc $1;
$guids{$guid} = { } unless defined $guids{$guid};
$guids{$guid}->{flags} |= $flags{$section};
next;
}
if (/^\s*LOCALENAME\s+([A-Za-z0-9-_]+)/)
{
$locales{$1} = $guid;
next;
}
}
elsif ("$part.$section" eq "SORTTABLES.MULTIPLEWEIGHTS")
{
if (/^\s*(\d+)\s+(\d+)/)
{
push @multiple_weights, $1, $2;
next;
}
}
elsif ("$part.$section" eq "SORTTABLES.EXPANSION")
{
if (/^\s*0x([0-9A-Fa-f]+)\s+0x([0-9A-Fa-f]+)\s+0x([0-9A-Fa-f]+)/)
{
my $pos = scalar @expansions / 2;
$keys[hex $1] = [ 2, 0, $pos & 0xff, $pos >> 8 ] unless defined $keys[hex $1];
push @expansions, hex $2, hex $3;
next;
}
}
elsif ("$part.$section" eq "SORTTABLES.COMPATIBILITY")
{
if (/^\s*0x([0-9A-Fa-f]+)\s+0x([0-9A-Fa-f]+)/)
{
$keys[hex $1] = $keys[hex $2];
next;
}
}
elsif ("$part.$section" eq "SORTTABLES.COMPRESSION")
{
if (/^\s*SORTGUID\s+([-0-9A-Fa-f]+)\s+\d*\s*([A-Z0-9_]+)?/)
{
if ($subsection || !$guid) # start a new one
{
$guid = lc $1;
$subsection = "";
$guids{$guid} = { } unless defined $guids{$guid};
$guids{$guid}->{flags} |= $flags{$2} if $2;
$guids{$guid}->{compr} = @compressions;
$exceptions{"$guid-"} = [ ] unless defined $exceptions{"$guid-"};
$compr_flags{$guid} = [ ] unless defined $compr_flags{$guid};
push @compressions, [ ];
}
else # merge with current one
{
$guids{lc $1} = { } unless defined $guids{lc $1};
$guids{lc $1}->{flags} |= $flags{$2} if $2;
$guids{lc $1}->{compr} = $guids{$guid}->{compr};
$compr_flags{lc $1} = $compr_flags{$guid};
}
next;
}
if (/^\s*LOCALENAME\s+([A-Za-z0-9-_]+)/)
{
$locales{$1} = $guid;
next;
}
if (/^\s*(TWO|THREE|FOUR|FIVE|SIX|SEVEN|EIGHT)/)
{
$subsection = $1;
next;
}
if ($subsection && /^\s*(($re_hex\s+){2,8})$re_key/)
{
my @comp = map { hex $_; } split(/\s+/,$1);
push @{$compressions[$#compressions]}, [ split(/\s+/,$3), @comp ];
# add compression flags
$compr_flags{$guid}->[$comp[0]] |= @comp >= 6 ? 0xc0 : @comp >= 4 ? 0x80 : 0x40;
next;
}
}
elsif ("$part.$section" eq "SORTTABLES.EXCEPTION")
{
if (/^\s*SORTGUID\s+([-0-9A-Fa-f]+)\s+\d*\s*(LINGUISTIC_CASING)?/)
{
$guid = lc $1;
$guids{$guid} = { } unless defined $guids{lc $1};
$ling_flag = ($2 ? "+" : "-");
$exceptions{"$guid$ling_flag"} = [ ] unless defined $exceptions{"$guid$ling_flag"};
next;
}
if (/^\s*LOCALENAME\s+([A-Za-z0-9-_]+)/)
{
$locales{$1} = $guid;
next;
}
if (/^\s*($re_hex)\s+$re_key/)
{
$exceptions{"$guid$ling_flag"}->[hex $1] = [ split(/\s+/,$2) ];
next;
}
}
elsif ("$part.$section" eq "SORTTABLES.JAMOSORT")
{
if (/^\s*$re_hex\s+(($re_hex\s*){5})/)
{
$jamostr .= pack "C8", map { hex $_; } split /\s+/, $1;
next;
}
}
die "$current_data_file: $part.$section: unrecognized line $_\n";
}
close $KEYS;
# Sortkey table
my $table;
for (my $i = 0; $i < 0x10000; $i++)
{
my @k = defined $keys[$i] ? @{$keys[$i]} : (0) x 4;
$table .= pack "C4", $k[1], $k[0], $k[2], $k[3];
}
foreach my $id (sort keys %exceptions)
{
my $pos = length($table) / 4;
my @exc = @{$exceptions{$id}};
my @filled;
my $key = (substr( $id, -1 ) eq "+" ? "ling_except" : "except");
my $guid = substr( $id, 0, -1 );
$guids{$guid}->{$key} = $pos;
$pos += 0x100;
my @flags = @{$compr_flags{$guid}} if defined $compr_flags{$guid};
for (my $j = 0; $j < 0x10000; $j++)
{
next unless defined $exc[$j] || defined $flags[$j];
$filled[$j >> 8] = 1;
$j |= 0xff;
}
for (my $j = 0; $j < 0x100; $j++)
{
$table .= pack "L<", $filled[$j] ? $pos : $j * 0x100;
$pos += 0x100 if $filled[$j];
}
for (my $j = 0; $j < 0x10000; $j++)
{
next unless $filled[$j >> 8];
my @k = defined $exc[$j] ? @{$exc[$j]} : defined $keys[$j] ? @{$keys[$j]} : (0) x 4;
$k[3] |= $flags[$j] || 0;
$table .= pack "C4", $k[1], $k[0], $k[2], $k[3];
}
}
# Case mapping tables
# standard table
my @casemaps;
my @upper = @toupper_table;
my @lower = @tolower_table;
remove_linguistic_mappings( \@upper, \@lower );
$casemaps[0] = pack( "S<*", 1) . dump_binary_case_table( @upper ) . dump_binary_case_table( @lower );
# linguistic table
$casemaps[1] = pack( "S<*", 1) . dump_binary_case_table( @toupper_table ) . dump_binary_case_table( @tolower_table );
# Turkish table
@upper = @toupper_table;
@lower = @tolower_table;
$upper[ord 'i'] = 0x130; # LATIN CAPITAL LETTER I WITH DOT ABOVE
$lower[ord 'I'] = 0x131; # LATIN SMALL LETTER DOTLESS I
$casemaps[2] = pack( "S<*", 1) . dump_binary_case_table( @upper ) . dump_binary_case_table( @lower );
my $casemaps = align_string( 8, $casemaps[0] . $casemaps[1] . $casemaps[2] );
# Char type table
my @table;
my $types = "";
my %typestr;
for (my $i = 0; $i < 0x10000; $i++)
{
my $str = pack "S<3",
($category_table[$i] || 0) & 0xffff,
defined($direction_table[$i]) ? $c2_types{$direction_table[$i]} : 0,
($category_table[$i] || 0) >> 16;
if (!defined($typestr{$str}))
{
$typestr{$str} = length($types) / 6;
$types .= $str;
}
$table[$i] = $typestr{$str};
}
my (@rows, @array, @data, @row_data);
(@rows[0..4095], @data) = compress_array( 4096, 0, @table[0..65535] );
(@array[0..255], @row_data) = compress_array( 256, 0, @rows );
for (my $i = 0; $i < 256; $i++) { $array[$i] *= 2; } # we need byte offsets
for (my $i = 0; $i < @row_data; $i++) { $row_data[$i] += 2 * @row_data + 512 - 4096; }
my $arraystr = pack("S<*", @array, @row_data) . pack("C*", @data);
my $chartypes = pack "S<2", 4 + length($types) + length($arraystr), 2 + length($types);
$chartypes = align_string( 8, $chartypes . $types . $arraystr );
# Sort tables
# guids
my $sorttables = pack "L<2", $version, scalar %guids;
foreach my $id (sort keys %guids)
{
my %guid = %{$guids{$id}};
my $flags = $guid{flags} || 0;
my $map = length($casemaps[0]) + (defined $guid{ling_except} ? length($casemaps[1]) : 0);
$sorttables .= pack_guid($id) . pack "L<5",
$flags,
defined($guid{compr}) ? $guid{compr} : 0xffffffff,
$guid{except} || 0,
$guid{ling_except} || 0,
$map / 2;
}
# expansions
$sorttables .= pack "L<S<*", scalar @expansions / 2, @expansions;
# compressions
$sorttables .= pack "L<", scalar @compressions;
my $rowstr = "";
foreach my $c (@compressions)
{
my $pos = length($rowstr) / 2;
my $min = 0xffff;
my $max = 0;
my @lengths = (0) x 8;
foreach my $r (sort cmp_compression @{$c})
{
my @row = @{$r};
$lengths[scalar @row - 6]++;
foreach my $val (@row[4..$#row])
{
$min = $val if $min > $val;
$max = $val if $max < $val;
}
$rowstr .= align_string( 4, pack "S<*", @row[4..$#row] );
$rowstr .= pack "C4", $row[1], $row[0], $row[2], $row[3];
}
$sorttables .= pack "L<S<10", $pos, $min, $max, @lengths;
}
$sorttables .= $rowstr;
# multiple weights
$sorttables .= align_string( 4, pack "L<C*", scalar @multiple_weights / 2, @multiple_weights );
# jamo sort
$sorttables .= pack("L<", length($jamostr) / 8) . $jamostr;
# Locales
add_registry_key( $nlskey, "Sorting\\Ids", "{$default_guid}" );
foreach my $loc (sort keys %locales)
{
# skip specific locales that match more general ones
my @parts = split /[-_]/, $loc;
next if @parts > 1 && defined($locales{$parts[0]}) && $locales{$parts[0]} eq $locales{$loc};
next if @parts > 2 && defined($locales{"$parts[0]-$parts[1]"}) && $locales{"$parts[0]-$parts[1]"} eq $locales{$loc};
add_registry_string_value( $nlskey, "Sorting\\Ids", $loc, "\{$locales{$loc}\}" );
}
# File header
my @header;
$header[0] = 16;
$header[1] = $header[0] + length $table;
$header[2] = $header[1] + length $casemaps;
$header[3] = $header[2] + length $chartypes;
open OUTPUT, ">$filename.new" or die "Cannot create $filename";
print OUTPUT pack "L<*", @header;
print OUTPUT $table, $casemaps, $chartypes, $sorttables;
close OUTPUT;
save_file($filename);
return $chartypes;
}
my %lcnames;
sub locale_parent($)
{
my $loc = shift;
return undef unless $loc;
return $lcnames{$loc}->{sparent} if defined $lcnames{$loc} && defined $lcnames{$loc}->{sparent};
return $lcnames{$loc}->{parent} if defined $lcnames{$loc} && defined $lcnames{$loc}->{parent};
if ($loc =~ /(.*)-[0-9A-Za-z]+/) { return $1; }
return "";
}
sub compare_locales
{
(my $n1 = $a) =~ tr/A-Z_/a-z-/;
(my $n2 = $b) =~ tr/A-Z_/a-z-/;
return $n1 cmp $n2;
}
# query an xml key
sub xml_query($$)
{
my ($xml, $query) = @_;
my $ret = $xml->find( $query );
return undef unless $ret;
printf STDERR "multiple entries for %s\n", $query if (@{$ret} > 1);
return @{$ret}[0]->textContent;
}
# query an xml key for a locale, with fallback to the parents
sub loc_query($$)
{
my ($loc, $query) = @_;
$loc = $lcnames{"en-US"} unless $loc->{name}; # fallback to "en-US" for root locale
for (my $cur = $loc->{name}; defined $cur; $cur = locale_parent( $cur ))
{
next unless defined $lcnames{$cur};
my $xml = $lcnames{$cur}->{xml};
my $ret = $xml->find( $query );
next unless $ret;
printf STDERR "%s: multiple entries for %s\n", $cur, $query if (@{$ret} > 1);
next if @{$ret}[0]->textContent eq "\x{2191}\x{2191}\x{2191}"; # "↑↑↑"
return @{$ret}[0]->textContent;
}
return undef;
}
# retrieve a locale field entry by going up the parents tree
sub locale_entry($$$)
{
my ($loc, $field, $def) = @_;
return $loc->{$field} if defined $loc->{$field};
unless ($loc->{name}) # fallback to "en-US" for root locale
{
$loc = $lcnames{"en-US"};
return $loc->{$field} if defined $loc->{$field};
}
while (defined $loc->{alias}) # resolve aliases
{
$loc = $lcnames{$loc->{alias}};
return $loc->{$field} if defined $loc->{$field};
}
my $cur = $loc->{name};
while ($cur)
{
if (defined $lcnames{$cur} && defined $lcnames{$cur}->{sparent})
{
$cur = $lcnames{$cur}->{sparent};
}
elsif ($cur =~ /(.*)-[0-9A-Za-z]+/)
{
$cur = $1;
}
else
{
return $def;
}
return $lcnames{$cur}->{$field} if defined $lcnames{$cur} && defined $lcnames{$cur}->{$field};
}
return $def;
}
my $string_data;
sub add_str_data($)
{
my $txt = shift;
my $ret = index( $string_data, $txt );
if ($ret == -1)
{
$ret = length($string_data);
$string_data .= $txt
}
return $ret / 2;
}
sub add_string($)
{
my $str = shift;
return 0 unless defined($str) && $str ne "";
my $utf = encode( "UTF16LE", $str );
return add_str_data( (pack "S<", length($utf) / 2) . $utf . (pack "S", 0) );
}
sub add_fontsig(@)
{
return add_str_data( pack "S<L<*", scalar(@_) * 2, @_ );
}
sub add_strarray(@)
{
return 0 unless @_;
return add_str_data( pack "S<L<*", scalar @_, map { add_string($_) } @_);
}
sub format_to_grouping($)
{
my $format = shift;
if ($format =~ /#,(#+),(#+0)/) { return chr(length($2)) . chr(length($1)); }
if ($format =~ /#,(#+0)/) { return chr(length($1)); }
# printf STDERR "unknown format %s\n", $format;
return chr(3);
}
sub parse_currency_format($$)
{
my $name = shift;
my ($posfmt, $negfmt) = split /;/, shift;
my @pospatterns = ( "\xa4[^\xa0]*#", # $1.1
"00[^\xa0]*\xa4", # 1.1$
"\xa4.*\xa0.*#", # $ 1.1
"00.*\xa0.*\xa4" ); # 1.1 $
my @negpatterns = ( "\\(\xa4[^\xa0]*#", # ($1.1)
"-\xa4[^\xa0]*#", # -$1.1
"\xa4[^\xa0]*-#", # $-1.1
"\xa4[^\xa0]*#.*00-", # $1.1-
"00[^\xa0]*\xa4\\)", # (1.1$)
"-#.*00[^\xa0]*\xa4", # -1.1$
"00-[^\xa0]*\xa4", # 1.1-$
"00[^\xa0]*\xa4-", # 1.1$-
"-#.*00.*\xa0.*\xa4", # -1.1 $
"-\xa4.*\xa0.*#", # -$ 1.1
"00.*\xa0.*\xa4-", # 1.1 $-
"\xa4.*\xa0.*#.*00-", # $ 1.1-
"\xa4.*\xa0.*-#", # $ -1.1
"00-.*\xa0.*\xa4", # 1.1- $
"\\(\xa4.*\xa0.*#", # ($ 1.1)
"00.*\xa0.*\xa4\\)"); # (1.1 $)
my ($pos, $neg);
for ($pos = 0; $pos < @pospatterns; $pos++)
{
last if ($posfmt =~ /$pospatterns[$pos]/);
}
#printf STDERR "$name: unknown format '%s'\n", $posfmt if ($pos == @pospatterns);
$pos = 0 if ($pos == @pospatterns);
if (defined $negfmt)
{
for ($neg = 0; $neg < @negpatterns; $neg++)
{
last if ($negfmt =~ /$negpatterns[$neg]/);
}
#printf STDERR "$name: unknown format '%s'\n", $negfmt if ($neg == @negpatterns);
$neg = 0 if ($neg == @negpatterns);
}
elsif ($pos == 0) { $neg = 1; }
elsif ($pos == 1) { $neg = 5; }
elsif ($pos == 2) { $neg = 9; }
elsif ($pos == 3) { $neg = 8; }
return ($pos, $neg);
}
sub parse_percent_format($)
{
my $fmt = shift;
my @patterns = ( "0.+%", # 1 %
"0%", # 1%
"%#", # %1
"%.+#" ); # % 1
my $pos;
for ($pos = 0; $pos < @patterns; $pos++)
{
last if ($fmt =~ /$patterns[$pos]/);
}
printf STDERR "unknown format '%s'\n", $fmt if ($pos == @patterns);
return ($pos, ($pos == 3) ? 7 : $pos);
}
sub convert_date_format($)
{
my $fmt = shift;
$fmt =~ s/G+/gg/;
$fmt =~ s/LLLL/MMMM/;
$fmt =~ s/LLL/MMM/;
$fmt =~ s/E+/dddd/;
$fmt =~ s/ccc+/dddd/;
$fmt =~ s/([^gy])y([^y])/$1yyyy$2/;
$fmt =~ s/^y([^y])/yyyy$1/;
$fmt =~ s/([^gy])y$/$1yyyy/;
return $fmt;
}
sub convert_time_format($)
{
my $fmt = shift;
$fmt =~ s/a+/tt/;
$fmt =~ s/B+/tt/;
$fmt =~ s/\x{202f}/ /;
return $fmt;
}
sub load_iso639()
{
my %iso639;
my $DATA = open_data_file( "iso639", "iso-639-3_Code_Tables_$ISO639VERSION/iso-639-3_$ISO639VERSION.tab" );
while (<$DATA>)
{
if (/^\s*[a-z]{3}\s+[a-z]{3}\s+([a-z]{3})\s+([a-z]{2})\s/) { $iso639{$2} = $1; }
}
close $DATA;
return %iso639;
}
################################################################
# build the locale table for locale.nls
sub build_locale_data()
{
my $base = "cldr-release-$CLDRVERSION";
my $suppl = load_xml_data_file( "cldr", "$base/common/supplemental/supplementalData.xml" );
my $subtags = load_xml_data_file( "cldr", "$base/common/supplemental/likelySubtags.xml" );
my $numbers = load_xml_data_file( "cldr", "$base/common/supplemental/numberingSystems.xml" );
# obsolete phone data from CLDR version 33
my $phone = load_xml_data_file( "cldr33", "common/supplemental/telephoneCodeData.xml" );
my %iso639 = load_iso639();
$string_data = pack "S2", 0, 0; # offset 0 == empty string
%lcnames = map { $_->{name} => $_ } @locales;
my %lcids;
foreach my $loc (@locales) { $lcids{$loc->{lcid}} = $loc if defined $loc->{lcid}; }
my %days = ( "sun" => 0, "mon" => 1, "tue" => 2, "wed" => 3, "thu" => 4, "fri" => 5, "sat" => 6 );
# assign locale parents
foreach my $loc (@locales)
{
next if $loc->{name} eq "";
next if defined $loc->{parent};
(my $unix_name = $loc->{name}) =~ s/-/_/g;
my $parent = xml_query( $suppl, "/supplementalData/parentLocales[not(\@component)]/parentLocale[contains(concat(' ',\@locales,' '),' $unix_name ')]/\@parent" );
if ($parent)
{
$parent =~ s/_/-/g;
$parent = "" if $parent eq "root";
}
elsif ($loc->{name} =~ /(.*)-[0-9A-Za-z]+/) { $parent = $1; }
$loc->{parent} = $parent || "";
}
# load per-locale XML files
foreach my $loc (@locales)
{
next if defined $loc->{alias};
(my $file = $loc->{file} || $loc->{name}) =~ s/-/_/g;
$file = "$base/" . ($loc->{dir} || "common") . "/main/$file.xml";
my $xml = load_xml_data_file( "cldr", $file );
$loc->{xml} = $xml;
$loc->{language} ||= xml_query( $xml, "/ldml/identity/language/\@type" );
$loc->{territory} ||= xml_query( $xml, "/ldml/identity/territory/\@type" );
$loc->{script} = xml_query( $xml, "/ldml/identity/script/\@type" );
if (!defined($loc->{territory}) && $loc->{name} =~ /-([A-Z]{2}|[0-9]{3})$/) { $loc->{territory} = $1; }
if (!defined($loc->{script}) && $loc->{name} =~ /-([A-Z][a-z]{3})(-[A-Z]{2})?$/) { $loc->{script} = $1; }
}
# assign a default territory and sort locale
foreach my $loc (@locales)
{
next if defined $loc->{alias};
next if defined $loc->{territory};
my $id = $loc->{sortlocale};
if (defined $id && ($id =~ /[-_]([A-Z0-9]+)$/))
{
$loc->{territory} = $1;
next;
}
my @children = grep /^$loc->{name}-[A-Z0-9]+$/ && !defined $lcnames{$_}->{alias}, keys %lcnames;
if (@children == 1)
{
$id = $children[0];
}
else
{
my $name = $loc->{file} || $loc->{name};
$name =~ s/-(Arab|Beng|Cyrl|Deva|Guru|Hans|Hant|Latn|Tfng|Vaii)$//;
$name =~ s/-/_/g;
$id = xml_query( $subtags, "/supplementalData/likelySubtags/likelySubtag[\@from='$name']/\@to" );
$id =~ s/_/-/g if $id;
}
if ($id =~ /[-_]([A-Z0-9]+)$/)
{
$loc->{territory} = $1;
next if defined $loc->{sortlocale};
next unless $id =~ /^$loc->{name}/;
while (defined $lcnames{$id} && defined $lcnames{$id}->{alias}) { $id = $lcnames{$id}->{alias}; }
$loc->{sortlocale} = $id if defined $lcnames{$id};
next;
}
print STDERR "no territory found for $loc->{name}\n";
}
# fill geoid table
my %geotable;
foreach my $geo (@geoids)
{
my $name = $geo->{name};
next unless defined $name;
$geo->{alias} = $geotable{$name} if defined $geotable{$name};
$geotable{$name} ||= $geo;
}
foreach my $loc (@locales)
{
next if defined $loc->{alias};
my $territory = $loc->{territory};
$geotable{$territory} ||= { name => $territory };
}
foreach my $name (keys %geotable)
{
my $geo = $geotable{$name};
$geo->{dialcode} = xml_query( $phone, "(/supplementalData/telephoneCodeData/codesByTerritory[\@territory='$name']/telephoneCountryCode)[1]/\@code" );
if ($name =~ /\d+/)
{
$geo->{uncode} = $name;
next;
}
$geo->{iso2} = $name;
$geo->{iso3} = xml_query( $suppl, "/supplementalData/codeMappings/territoryCodes[\@type='$name']/\@alpha3");
$geo->{uncode} = xml_query( $suppl, "/supplementalData/codeMappings/territoryCodes[\@type='$name']/\@numeric");
$geo->{sintlsymbol} ||= xml_query( $suppl, "(/supplementalData/currencyData/region[\@iso3166='$name']/currency[not(\@to)])[1]/\@iso4217") || "XXX";
$geo->{sintlsymbol} =~ s/XXX/XDR/;
}
foreach my $geo (@geoids)
{
$geo->{parentid} = $geotable{$geo->{parent}}->{id} if defined $geo->{parent};
next if defined $geo->{iso2};
next if defined $geo->{alias};
next unless defined $geo->{uncode};
my @contains;
my $list = xml_query( $suppl, "/supplementalData/territoryContainment/group[\@type='$geo->{uncode}' and not(\@status)]/\@contains");
push @contains, split /\s+/, $list if defined $list;
$list = xml_query( $suppl, "/supplementalData/territoryContainment/group[\@type='$geo->{uncode}' and \@status='deprecated']/\@contains");
push @contains, split /\s+/, $list if defined $list;
while (@contains)
{
my $territory = pop @contains;
if (defined $geotable{$territory})
{
$geotable{$territory}->{parentid} ||= $geo->{id};
}
elsif ($territory =~ /\d+/)
{
# expand region recursively
$list = xml_query( $suppl, "/supplementalData/territoryContainment/group[\@type='$territory' and not(\@status)]/\@contains" );
push @contains, split /\s+/, $list if defined $list;
}
}
}
# assign calendars to their locale
foreach my $cal (@calendars)
{
next unless defined $cal->{locale};
my $loc = $lcnames{$cal->{locale}};
$loc->{calendar} = [ ] unless defined $loc->{calendar};
push @{$loc->{calendar}}, $cal;
}
# assign default lcid to aliases
foreach my $loc (@locales)
{
next unless defined $loc->{alias};
next if defined $loc->{lcid};
my $alias = $loc->{alias};
my $lcid = $lcnames{$alias}->{lcid} || 0x1000;
$loc->{lcid} = $lcid | 0x80000000;
}
# assign sort aliases to parent locale
foreach my $loc (@locales)
{
next unless $loc->{name} =~ /_/;
next unless defined $loc->{alias};
my $alias = $loc->{alias};
my $parent = $lcnames{$alias};
my $basename = $parent->{name};
while (1)
{
@{$parent->{sortnames}}[($loc->{lcid} >> 16) - 1] = $loc->{name};
$alias = locale_parent( $alias );
last unless $alias && defined $lcnames{$alias};
$parent = $lcnames{$alias};
last if defined $parent->{sortbase} && $parent->{sortbase} ne $basename;
$parent->{sortbase} = $basename;
}
}
# assign an array index to all locales
my $idx = 0;
foreach my $loc (@locales)
{
next if defined $loc->{alias};
$loc->{idx} = $idx++;
}
foreach my $loc (@locales)
{
my $alias = $loc->{alias};
next unless defined $alias;
while (defined $lcnames{$alias}->{alias}) { $alias = $lcnames{$alias}->{alias}; }
$loc->{idx} = $lcnames{$alias}->{idx};
}
# output lcids table
my $lcid_data = "";
foreach my $id (sort { $a <=> $b } keys %lcids)
{
my $loc = $lcids{$id};
$lcid_data .= pack "L<S<2", $id, $loc->{idx}, add_string($loc->{name});
}
# output lcnames table
my $lcname_data = "";
foreach my $name (sort compare_locales keys %lcnames)
{
my $loc = $lcnames{$name};
$lcname_data .= pack "S<2L<", add_string($name), $loc->{idx}, $loc->{lcid} || 0x1000;
}
# output locales array
my $locale_data = "";
my $default_lcid = 0x8001;
foreach my $loc (@locales)
{
next if defined $loc->{alias};
my $sname = $loc->{name};
my $language = $loc->{language};
my $territory = $loc->{territory};
my $script = $loc->{script};
my $neutral = ($sname && $sname !~ /-$territory/);
my $sparent = $loc->{sparent} || (($sname =~ /(.*)-[0-9A-Za-z]+/) ? $1 : $loc->{parent});
my $unique_lcid = $loc->{lcid};
unless (defined $unique_lcid) { $unique_lcid = $default_lcid++; }
my $geo = $geotable{$territory};
my $territory_match = "contains(concat(' ',normalize-space(\@territories),' '),' $territory ')";
# languages and scripts
my $ssortlocale = $loc->{sortlocale} || ($neutral ? "$sname-$territory" : $sname);
my $idefaultlanguage = defined $lcnames{$ssortlocale} ? $lcnames{$ssortlocale}->{lcid} : undef;
$idefaultlanguage = $lcnames{"en-US"}->{lcid} unless $ssortlocale;
(my $siso639langname = $sname) =~ s/-.*$//;
my $siso639langname2 = $iso639{$siso639langname} || $siso639langname;
my $sopentypelang = sprintf "%-4s", locale_entry( $loc, "sopentypelang", uc $siso639langname2 );
my $sabbrevlangname = defined $loc->{lcid} ? locale_entry( $loc, "sabbrevlangname", uc $siso639langname2 ) : "ZZZ";
my $siso3166ctryname2 = $geo->{iso3} || $geo->{uncode};
my $senglanguage = loc_query( $lcnames{en}, "/ldml/localeDisplayNames/languages/language[\@type='$language' and not(\@alt)]" ) || "";
my $sengcountry = loc_query( $lcnames{en}, "/ldml/localeDisplayNames/territories/territory[\@type='$territory' and not(\@alt)]" ) || "";
my $snativelangname = loc_query( $loc, "/ldml/localeDisplayNames/languages/language[\@type='$language' and not(\@alt)]" );
my $snativectryname = loc_query( $loc, "/ldml/localeDisplayNames/territories/territory[\@type='$territory' and not(\@alt)]" );
$sengcountry =~ s/South Korea/Korea/;
$sengcountry =~ s/T\xfcrkiye/Turkey/;
$snativelangname ||= $senglanguage;
$snativectryname ||= $sengcountry;
if ($script)
{
my $engscript = loc_query( $lcnames{en}, "/ldml/localeDisplayNames/scripts/script[\@type='$script' and not(\@alt)]" );
my $nativescript = loc_query( $loc, "/ldml/localeDisplayNames/scripts/script[\@type='$script' and not(\@alt)]" );
$senglanguage .= " ($engscript)" if $engscript;
$snativelangname .= " ($nativescript)" if $nativescript;
}
my $sengdisplayname = $neutral ? $senglanguage : "$senglanguage ($sengcountry)";
my $snativedisplayname = $neutral ? $snativelangname : "$snativelangname ($snativectryname)";
$sengdisplayname =~ s/\) \(/, /;
$snativedisplayname =~ s/\) \(/, /;
my $sscripts = locale_entry( $loc, "sscripts", $script ) || xml_query( $suppl, "/supplementalData/languageData/language[\@type='$language' and not(\@alt)]/\@scripts" );
$sscripts = (join ";", (sort split / /, ($sscripts || "Latn"))) . ";";
my $ireadinglayout = locale_entry( $loc, "ireadinglayout", 0 );
my $charlayout = loc_query( $loc, "/ldml/layout/orientation/characterOrder" );
if ($charlayout eq "right-to-left")
{
$ireadinglayout = 1;
}
elsif ($charlayout eq "top-to-bottom")
{
my $linelayout = loc_query( $loc, "/ldml/layout/orientation/lineOrder" );
$ireadinglayout = $linelayout eq "right-to-left" ? 2 : 3;
}
my $igeoid = $geo->{id} || 0;
# numbers
my $sdecimal = loc_query( $loc, "/ldml/numbers/symbols[\@numberSystem='latn']/decimal" );
my $slist = locale_entry( $loc, "slist", ";" );
my $smondecimalsep = loc_query( $loc, "/ldml/numbers/symbols[\@numberSystem='latn']/currencyDecimal" ) || $sdecimal;
my $sthousand = loc_query( $loc, "/ldml/numbers/symbols[\@numberSystem='latn']/group" );
$sthousand =~ s/\x{202f}/\x{00a0}/;
my $smonthousandsep = loc_query( $loc, "/ldml/numbers/symbols[\@numberSystem='latn']/currencyGroup" ) || $sthousand;
my $spositivesign = "";
my $snegativesign = "-";
my $spercent = loc_query( $loc, "/ldml/numbers/symbols[\@numberSystem='latn']/percentSign" );
my $snan = loc_query( $loc, "/ldml/numbers/symbols[\@numberSystem='latn']/nan" );
my $sposinfinity = loc_query( $loc, "/ldml/numbers/symbols[\@numberSystem='latn']/infinity" );
my $sneginfinity = $sposinfinity ? "-$sposinfinity" : "";
my $sgrouping = format_to_grouping( loc_query( $loc, "/ldml/numbers/decimalFormats[\@numberSystem='latn']/decimalFormatLength[not(\@type)]/decimalFormat/pattern" ));
my $percentformat = loc_query( $loc, "/ldml/numbers/percentFormats[\@numberSystem='latn']/percentFormatLength[not(\@type)]/percentFormat/pattern" );
my $currencyformat = loc_query( $loc, "/ldml/numbers/currencyFormats[\@numberSystem='latn']/currencyFormatLength[not(\@type)]/currencyFormat[\@type='accounting']/pattern[not(\@alt)]" ) ||
loc_query( $loc, "/ldml/numbers/currencyFormats[\@numberSystem='latn']/currencyFormatLength[not(\@type)]/currencyFormat[\@type='standard']/pattern[not(\@alt)]" );
my $smongrouping = format_to_grouping( $currencyformat );
my ($icurrency, $inegcurr) = parse_currency_format( $sname, $currencyformat );
my ($ipospercent, $inegpercent) = parse_percent_format( $percentformat );
my $native_numbering = loc_query( $loc, "/ldml/numbers/otherNumberingSystems/native" );
my @snativedigits = split //, (locale_entry( $loc, "nativedigits", "" ) || xml_query( $numbers, "/supplementalData/numberingSystems/numberingSystem[\@id='$native_numbering']/\@digits" ));
my $digitsubstitution = !(ord($snativedigits[0]) >= 0x600 && ord($snativedigits[0]) <= 0x6ff);
my $measure = defined xml_query( $suppl, "/supplementalData/measurementData/measurementSystem[\@type='US' and $territory_match]" );
my $papersize = defined xml_query( $suppl, "/supplementalData/measurementData/paperSize[\@type='US-Letter' and $territory_match]" );
# currencies
my $sintlsymbol = $geo->{sintlsymbol} || "XDR";
my $scurrency = $geo->{scurrency} || loc_query( $loc, "/ldml/numbers/currencies/currency[\@type='$sintlsymbol']/symbol[\@alt='narrow']" );
$scurrency ||= loc_query( $loc, "/ldml/numbers/currencies/currency[\@type='$sintlsymbol']/symbol[not(\@alt)]" );
$scurrency ||= $geo->{sintlsymbol};
$geo->{scurrency} = $scurrency if $scurrency;
my $sengcurrname = $loc->{sengcurrname} || loc_query( $lcnames{en}, "/ldml/numbers/currencies/currency[\@type='$sintlsymbol']/displayName[not(\@count)]" );
my $snativecurrname = $loc->{sengcurrname} || loc_query( $loc, "/ldml/numbers/currencies/currency[\@type='$sintlsymbol']/displayName[not(\@count)]" ) || $sengcurrname;
my $icurrdigits = xml_query( $suppl, "/supplementalData/currencyData/fractions/info[\@iso4217='$sintlsymbol']/\@digits" );
$icurrdigits = 2 unless defined $icurrdigits;
# calendars
my $firstday = xml_query( $suppl, "/supplementalData/weekData/firstDay[not(\@alt) and $territory_match]/\@day" );
my $ifirstdayofweek = $firstday ? $days{$firstday} : 1;
my $firstweekofyear = (xml_query( $suppl, "/supplementalData/weekData/minDays[$territory_match]/\@count" ) || 0) == 4 ? 2 : 0;
my $serastring = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/eras/eraAbbr/era[\@type='1' and not(\@alt)]" );
my (@sdayname, @sabbrevdayname, @sshortestdayname);
foreach my $d (sort { $days{$a} <=> $days{$b} } keys %days)
{
my $n = $days{$d};
my %name;
foreach my $type (qw(wide abbreviated short))
{
$name{$type} = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/days/dayContext[\@type='format']/dayWidth[\@type='$type']/day[\@type='$d' and not(\@alt)]" );
}
push @sdayname, $name{wide};
push @sabbrevdayname, $name{abbreviated} || $name{wide};
push @sshortestdayname, $name{short} || $name{abbreviated} || $name{wide};
}
my (@smonthname, @sabbrevmonthname, @sgenitivemonth, @sabbrevgenitivemonth);
foreach my $n (1..13)
{
my $name = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/months/monthContext[\@type='stand-alone']/monthWidth[\@type='wide']/month[\@type='$n']" );
my $abbrev = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/months/monthContext[\@type='stand-alone']/monthWidth[\@type='abbreviated']/month[\@type='$n']" );
my $genitive = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/months/monthContext[\@type='format']/monthWidth[\@type='wide']/month[\@type='$n']" );
my $abbrevgen = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/months/monthContext[\@type='format']/monthWidth[\@type='abbreviated']/month[\@type='$n']" );
push @smonthname, $name || $genitive || "";
push @sabbrevmonthname, $abbrev || $abbrevgen || $name || $genitive || "";
push @sgenitivemonth, $genitive || "";
push @sabbrevgenitivemonth, $abbrevgen || $genitive || "";
}
@sgenitivemonth = () if join("|",@smonthname) eq join("|",@sgenitivemonth);
@sabbrevgenitivemonth = () if join("|",@sabbrevmonthname) eq join("|",@sabbrevgenitivemonth);
my %caltypes = ( "gregorian" => 1, "japanese" => 3, "chinese" => 4, "dangi" => 5, "islamic" => 6, "buddhist" => 7, "hebrew" => 8,
"persian" => 22, "islamic-civil" => 23, "islamic-umalqura" => 23 );
my $calpref = xml_query( $suppl, "/supplementalData/calendarPreferenceData/calendarPreference[$territory_match]/\@ordering" ) || "gregorian";
my $icalendartype;
my @scalnames;
foreach my $c (split /\s+/, $calpref)
{
next unless defined $caltypes{$c};
$icalendartype .= chr($caltypes{$c});
$scalnames[$caltypes{$c} - 1] = loc_query( $loc, "/ldml/localeDisplayNames/types/type[\@key='calendar' and \@type='$c']" );
}
# date/time formats
my $s1159 = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dayPeriods/dayPeriodContext[\@type='format']/dayPeriodWidth[\@type='abbreviated']/dayPeriod[\@type='am' and not(\@alt)]" );
my $s2359 = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dayPeriods/dayPeriodContext[\@type='format']/dayPeriodWidth[\@type='abbreviated']/dayPeriod[\@type='pm' and not (\@alt)]" );
my $sshortestam = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dayPeriods/dayPeriodContext[\@type='format']/dayPeriodWidth[\@type='narrow']/dayPeriod[\@type='am' and not(\@alt)]" );
my $sshortestpm = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dayPeriods/dayPeriodContext[\@type='format']/dayPeriodWidth[\@type='narrow']/dayPeriod[\@type='pm' and not (\@alt)]" );
my @stimeformat = (loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/timeFormats/timeFormatLength[\@type='medium']/timeFormat/pattern[not(\@alt)]" ));
push @stimeformat, loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='Hms' and not(\@alt)]" );
pop @stimeformat if $stimeformat[0] eq $stimeformat[1];
@stimeformat = map convert_time_format($_), @stimeformat;
my @sshorttime = (loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/timeFormats/timeFormatLength[\@type='short']/timeFormat/pattern[not(\@alt)]" ));
push @sshorttime, loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='Hm' and not(\@alt)]" );
pop @sshorttime if $sshorttime[0] eq $sshorttime[1];
@sshorttime = map convert_time_format($_), @sshorttime;
my @sshortdate = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='yMd' and not(\@alt)]" );
push @sshortdate, loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='yMMMd' and not(\@alt)]" );
@sshortdate = map convert_date_format($_), @sshortdate;
my @slongdate = (loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateFormats/dateFormatLength[\@type='full']/dateFormat/pattern[not(\@alt)]" ));
push @slongdate, loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateFormats/dateFormatLength[\@type='long']/dateFormat/pattern[not(\@alt)]" );
@slongdate = map convert_date_format($_), @slongdate;
my @smonthday = (loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='MMMMd' and not(\@alt)]" ));
push @smonthday, loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='Md' and not(\@alt)]" );
push @smonthday, loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='MMMd' and not(\@alt)]" );
@smonthday = map convert_date_format($_), @smonthday;
my @syearmonth = map convert_date_format($_), loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='yMMMM' and not(\@alt)]" );
my @sduration = map convert_time_format( lc $_ ), loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='Hms' and not(\@alt)]" );
my $srelativelongdate = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='MMMMEd' and not(\@alt)]" ) ||
loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='gregorian']/dateTimeFormats/availableFormats/dateFormatItem[\@id='MMMEd' and not(\@alt)]" );
$srelativelongdate = convert_date_format( $srelativelongdate );
if (defined $loc->{calendar})
{
foreach my $cal (@{$loc->{calendar}})
{
$cal->{sshortdate} = \@sshortdate;
$cal->{syearmonth} = \@syearmonth;
$cal->{slongdate} = \@slongdate;
$cal->{serastring} = [ $serastring ];
$cal->{sdayname} = \@sdayname;
$cal->{sabbrevdayname} = \@sabbrevdayname;
$cal->{smonthname} = \@smonthname;
$cal->{sabbrevmonthname} = \@sabbrevmonthname;
$cal->{scalname} = $scalnames[$cal->{id}];
$cal->{smonthday} = \@smonthday;
$cal->{sshortestdayname} = \@sshortestdayname;
$cal->{sabbreverastring} = [ $serastring ];
$cal->{sshortestdayname} = \@sshortestdayname;
$cal->{srelativelongdate} = $srelativelongdate;
}
}
# codepages
my %ansicpmap = ( 437 => 1252, 720 => 1256, 737 => 1253, 775 => 1257, 850 => 1252,
852 => 1250, 855 => 1251, 866 => 1251, 857 => 1254, 862 => 1255 );
my %maccpmap = ( 437 => 10000, 720 => 10004, 737 => 10006, 775 => 10029, 850 => 10000,
852 => 10029, 855 => 10007, 857 => 10081, 862 => 10005, 866 => 10007,
874 => 10021, 932 => 10001, 936 => 10008, 949 => 10003, 950 => 10002,
1258 => 10000 );
my %ebcdiccpmap = ( 437 => 37, 720 => 20420, 737 => 20273, 866 => 20880, 932 => 20290 );
my %codepagemasks = ( 874 => [ 0x01000000, 0x00000000, 0x00000000, 0, 0x00010000, 0x00000000, 0x00010000, 0x00000000 ],
932 => [ 0x00000000, 0x28c70000, 0x00000010, 0, 0x00020000, 0x00000000, 0x00020000, 0x00000000 ],
936 => [ 0x00000000, 0x28010000, 0x00000002, 0, 0x00040000, 0x00000000, 0x00040000, 0x00000000 ],
949 => [ 0x00000000, 0x00000000, 0x00000000, 0, 0x00080000, 0x00000000, 0x00080000, 0x00000000 ],
950 => [ 0x00000000, 0x28c10000, 0x00000012, 0, 0x00100000, 0x00000000, 0x00100000, 0x00000000 ],
1258 => [ 0x2000000f, 0x00000000, 0x00000000, 0, 0x00000100, 0x00008000, 0x00000100, 0x00008000 ],
866 => [ 0x00000200, 0x00000000, 0x00000000, 0, 0x00000004, 0x00020000, 0x00000004, 0x02020000 ],
862 => [ 0x00000800, 0x40000000, 0x00000000, 0, 0x00000020, 0x00200000, 0x00000020, 0x00200000 ],
857 => [ 0x0000001f, 0x00000000, 0x00000000, 0, 0x00000010, 0x01000000, 0x00000010, 0x01000000 ],
855 => [ 0x00000200, 0x00000000, 0x00000000, 0, 0x00000004, 0x02000000, 0x00000004, 0x02000000 ],
852 => [ 0x00000027, 0x00000000, 0x00000000, 0, 0x00000002, 0x04000000, 0x00000002, 0x04000000 ],
775 => [ 0x00000007, 0x00000000, 0x00000000, 0, 0x00000080, 0x08000000, 0x00000080, 0x08000000 ],
737 => [ 0x00000080, 0x00000000, 0x00000000, 0, 0x00000008, 0x10000000, 0x00000008, 0x10010000 ],
720 => [ 0x00002000, 0x00000000, 0x00000000, 0, 0x00000040, 0x20000000, 0x00000040, 0x20080000 ],
850 => [ 0x00000003, 0x00000000, 0x00000000, 0, 0x00000001, 0x40000000, 0x0000019f, 0xdfd70000 ],
437 => [ 0x00000003, 0x00000000, 0x00000000, 0, 0x00000001, 0x80000000, 0x0000019f, 0xdfd70000 ],
65001 => [ 0x00000000, 0x00000000, 0x00000000, 0, 0x00000000, 0x00000000, 0x0000019f, 0xdfd70000 ] );
my $oemcp = locale_entry( $loc, "oemcp", 65001 );
my $maccp = locale_entry( $loc, "maccp", undef ) || $maccpmap{$oemcp} || 65001;
my $ebcdiccp = locale_entry( $loc, "ebcdiccp", undef ) || $ebcdiccpmap{$oemcp} || 500;
$ebcdiccp = 500 if (defined $loc->{oemcp} && $loc->{oemcp} == 65001) || (defined $loc->{maccp} && $loc->{maccp} == 65001);
my $ansicp = $ansicpmap{$oemcp} || $oemcp;
my @fontsig = (0) x 8;
my $sig = locale_entry( $loc, "fontsig", [] );
foreach my $i (0..7) { $fontsig[$i] |= $codepagemasks{$oemcp}->[$i]; }
foreach my $i (0..$#{$sig}) { $fontsig[$i] |= $sig->[$i]; }
$fontsig[3] |= 1 << 31;
$fontsig[3] |= 1 << 27 if $ireadinglayout == 1;
$fontsig[3] |= 1 << 28 if $ireadinglayout == 3;
# special cases for invariant locale
unless ($loc->{name})
{
$siso639langname = "iv";
$siso639langname2 = "ivl";
$senglanguage = $snativelangname = "Invariant Language";
$sengcountry = $snativectryname = "Invariant Country";
$sengdisplayname = "Invariant Language (Invariant Country)";
$snativedisplayname = "Invariant Language (Invariant Region)";
$sengcurrname = $snativecurrname = "International Monetary Fund";
$scurrency = "\x{00a4}";
$ifirstdayofweek = 0;
$igeoid = $geotable{"US"}->{id};
@stimeformat = ("HH:mm:ss");
@sshortdate = ("MM/dd/yyyy", "yyyy-MM-dd");
@slongdate = ("dddd, dd MMMM yyyy");
@syearmonth = ("yyyy MMMM");
@smonthday = ("MMMM dd", "MMMM d", "M/d", "MMM d");
@sshorttime = ("HH:mm", "hh:mm tt", "H:mm", "h:mm tt");
$srelativelongdate = "dddd, MMMM dd";
$sposinfinity = "Infinity";
$sneginfinity = "-Infinity";
$spositivesign = "+";
$ipospercent = $inegpercent = 0;
}
# output data
$locale_data .= pack "L<2",
add_string( $sname ), # name
add_string( $sopentypelang ); # LOCALE_SOPENTYPELANGUAGETAG
$locale_data .= pack "S<14",
$loc->{lcid} || 0x1000, # LOCALE_ILANGUAGE
$unique_lcid, # unique_lcid
locale_entry( $loc, "idigits", 2 ), # LOCALE_IDIGITS
locale_entry( $loc, "inegnumber", 1 ), # LOCALE_INEGNUMBER
$icurrdigits, # LOCALE_ICURRDIGITS
$icurrency, # LOCALE_ICURRENCY
$inegcurr, # LOCALE_INEGCURR
locale_entry( $loc, "ilzero", 1 ), # LOCALE_ILZERO
!$neutral, # LOCALE_INEUTRAL
$ifirstdayofweek, # LOCALE_IFIRSTDAYOFWEEK
$firstweekofyear, # LOCALE_IFIRSTWEEKOFYEAR
$geo->{dialcode} || 1 , # LOCALE_ICOUNTRY,
$measure, # LOCALE_IMEASURE
$digitsubstitution; # LOCALE_IDIGITSUBSTITUTION
$locale_data .= pack "L<18",
add_string( $sgrouping ), # LOCALE_SGROUPING
add_string( $smongrouping ), # LOCALE_SMONGROUPING
add_string( $slist ), # LOCALE_SLIST
add_string( $sdecimal ), # LOCALE_SDECIMAL
add_string( $sthousand ), # LOCALE_STHOUSAND
add_string( $scurrency ), # LOCALE_SCURRENCY
add_string( $smondecimalsep ), # LOCALE_SMONDECIMALSEP
add_string( $smonthousandsep ), # LOCALE_SMONTHOUSANDSEP
add_string( $spositivesign ), # LOCALE_SPOSITIVESIGN
add_string( $snegativesign ), # LOCALE_SNEGATIVESIGN
add_string( $s1159 ), # LOCALE_S1159
add_string( $s2359 ), # LOCALE_S2359
add_strarray( @snativedigits ), # LOCALE_SNATIVEDIGITS
add_strarray( @stimeformat ), # LOCALE_STIMEFORMAT
add_strarray( @sshortdate ), # LOCALE_SSHORTDATE
add_strarray( @slongdate ), # LOCALE_SLONGDATE
add_strarray( @syearmonth ), # LOCALE_SYEARMONTH
add_strarray( @sduration ); # LOCALE_SDURATION
$locale_data .= pack "S<8",
$idefaultlanguage || 0x1000, # LOCALE_IDEFAULTLANGUAGE
$ansicp, # LOCALE_IDEFAULTANSICODEPAGE
$oemcp, # LOCALE_IDEFAULTCODEPAGE
$maccp, # LOCALE_IDEFAULTMACCODEPAGE
$ebcdiccp, # LOCALE_IDEFAULTEBCDICCODEPAGE
$igeoid < 65536 ? $igeoid : 39070, # old_geoid
$papersize ? 1 : 9, # LOCALE_IPAPERSIZE
0; # FIXME # islamic_cal
$locale_data .= pack "L<24",
add_string( $icalendartype ), # LOCALE_ICALENDARTYPE
add_string( $sabbrevlangname ), # LOCALE_SABBREVLANGNAME
add_string( $siso639langname ), # LOCALE_SISO639LANGNAME
add_string( $senglanguage ), # LOCALE_SENGLANGUAGE
add_string( $snativelangname ), # LOCALE_SNATIVELANGNAME
add_string( $sengcountry ), # LOCALE_SENGCOUNTRY
add_string( $snativectryname ), # LOCALE_SNATIVECTRYNAME
add_string( $siso3166ctryname2 ), # LOCALE_SABBREVCTRYNAME
add_string( $territory ), # LOCALE_SISO3166CTRYNAME
add_string( $sintlsymbol ), # LOCALE_SINTLSYMBOL
add_string( $sengcurrname ), # LOCALE_SENGCURRNAME
add_string( $snativecurrname ), # LOCALE_SNATIVECURRNAME
add_fontsig( @fontsig ), # LOCALE_FONTSIGNATURE
add_string( $siso639langname2 ), # LOCALE_SISO639LANGNAME2
add_string( $siso3166ctryname2 ), # LOCALE_SISO3166CTRYNAME2
add_string( $sparent ), # LOCALE_SPARENT
add_strarray( @sdayname ), # LOCALE_SDAYNAME
add_strarray( @sabbrevdayname ), # LOCALE_SABBREVDAYNAME
add_strarray( @smonthname ), # LOCALE_SMONTHNAME
add_strarray( @sabbrevmonthname ), # LOCALE_SABBREVMONTHNAME
add_strarray( @sgenitivemonth ), # LOCALE_SGENITIVEMONTH
add_strarray( @sabbrevgenitivemonth ), # LOCALE_SABBREVGENITIVEMONTH
add_strarray( @scalnames ), # LOCALE_SCALNAMES
add_strarray( @{$loc->{sortnames}} ); # LOCALE_SSORTNAMES
$locale_data .= pack "S<6",
$inegpercent, # LOCALE_INEGATIVEPERCENT
$ipospercent, # LOCALE_IPOSITIVEPERCENT
0, # unknown
$ireadinglayout, # LOCALE_IREADINGLAYOUT
0x2a, # unknown
0x2a; # unknown
$locale_data .= pack "L<24",
0, # unknown
add_string( $sengdisplayname ), # LOCALE_SENGLISHDISPLAYNAME
add_string( $snativedisplayname ), # LOCALE_SNATIVEDISPLAYNAME
add_string( $spercent ), # LOCALE_SPERCENT
add_string( $snan ), # LOCALE_SNAN
add_string( $sposinfinity ), # LOCALE_SPOSINFINITY
add_string( $sneginfinity ), # LOCALE_SNEGINFINITY
0, # unknown
add_string( $serastring ), # CAL_SERASTRING
add_string( $serastring ), # CAL_SABBREVERASTRING
0, # unknown
add_string( $ssortlocale ), # LOCALE_SCONSOLEFALLBACKNAME
add_strarray( @sshorttime ), # LOCALE_SSHORTTIME
add_strarray( @sshortestdayname ), # CAL_SSHORTESTDAYNAME
0, # unknown
add_string( $ssortlocale ), # LOCALE_SSORTLOCALE
add_string( "0409:00000409" ), # FIXME # LOCALE_SKEYBOARDSTOINSTALL
add_string( $sscripts ), # LOCALE_SSCRIPTS
add_string( $srelativelongdate ), # LOCALE_SRELATIVELONGDATE
$igeoid, # LOCALE_IGEOID
add_string( $sshortestam || "a" ), # LOCALE_SSHORTESTAM
add_string( $sshortestpm || "p" ), # LOCALE_SSHORTESTPM
add_strarray( @smonthday ), # LOCALE_SMONTHDAY
add_string( "k0-windows-us" ) # FIXME # keyboard_layout
}
# output language groups
my %groups;
add_registry_key( $nlskey, "Locale", "00000409" );
foreach my $loc (@locales)
{
next unless defined $loc->{lcid};
next if ($loc->{lcid} & 0x80000000);
next if !defined($loc->{alias}) && $loc->{name} !~ /-$loc->{territory}/; # skip neutral locales
my $group = locale_entry( $loc, "group", 1 );
my $name = sprintf( "%08x", $loc->{lcid} );
my $val = sprintf( "%x", $group );
add_registry_string_value( $nlskey, "Locale", $name, $val ) unless ($loc->{lcid} & 0x000f0000);
add_registry_string_value( $nlskey, "Locale\\Alternate Sorts", $name, $val ) if $loc->{name} =~ /_/;
$groups{$val} = 1;
}
foreach my $group (keys %groups) { add_registry_string_value( $nlskey, "Language Groups", $group, "1" ); }
# output calendar data
my $calendar_data = "";
foreach my $cal (@calendars)
{
my $scalname = $cal->{name};
my $iyearoffsetrange = 0;
my $itwodigityearmax = $cal->{itwodigityearmax};
my @sshortdate;
my @syearmonth;
my @slongdate;
my @serastring;
my @sdayname;
my @sabbrevdayname;
my @smonthname;
my @sabbrevmonthname;
my @smonthday;
my @sabbreverastring;
my @sshortestdayname;
my $type = $cal->{type};
if (defined $cal->{locale} && defined $type)
{
my $loc = $lcnames{$cal->{locale}};
my $fmt = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/dateTimeFormats/availableFormats/dateFormatItem[\@id='yMd' and not(\@alt)]" );
push @sshortdate, $fmt if $fmt;
$fmt = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/dateTimeFormats/availableFormats/dateFormatItem[\@id='yyyyMd' and not(\@alt)]" );
push @sshortdate, $fmt if $fmt;
$fmt = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/dateTimeFormats/availableFormats/dateFormatItem[\@id='yMMMd' and not(\@alt)]" );
push @sshortdate, $fmt if $fmt;
$fmt = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/dateTimeFormats/availableFormats/dateFormatItem[\@id='yyyyMMMd' and not(\@alt)]" );
push @sshortdate, $fmt if $fmt;
@sshortdate = map convert_date_format($_), @sshortdate;
$fmt = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/dateFormats/dateFormatLength[\@type='full']/dateFormat/pattern[not(\@alt)]" );
push @slongdate, $fmt if $fmt;
$fmt = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/dateFormats/dateFormatLength[\@type='long']/dateFormat/pattern[not(\@alt)]" );
push @slongdate, $fmt if $fmt;
@slongdate = map convert_date_format($_), @slongdate;
foreach my $n (1..13)
{
my $name = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/months/monthContext[\@type='format']/monthWidth[\@type='wide']/month[\@type='$n' and not(\@yeartype)]" );
my $abbrev = loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/months/monthContext[\@type='format']/monthWidth[\@type='abbreviated']/month[\@type='$n' and not(\@yeartype)]" );
push @smonthname, $name || "";
push @sabbrevmonthname, $abbrev || $name || "";
}
$scalname ||= loc_query( $loc, "/ldml/localeDisplayNames/types/type[\@key='calendar' and \@type='$type']" );
if (defined $cal->{eras})
{
my @eras;
my $idx = 1;
foreach my $era (@{$cal->{eras}})
{
my $start = xml_query( $suppl, "/supplementalData/calendarData/calendar[\@type='$type']/eras/era[\@type='$era']/\@start" );
next unless $start =~ /^(-?\d+)-(\d+)-(\d+)/;
my ($year, $mon, $day, $zero, $first) = ($1, $2, $3, $1 - 1, 1);
if ($zero < 0)
{
$first -= $zero;
$year = 1;
$itwodigityearmax = 2049 - $zero;
}
unshift @eras, pack( "S<8", 6, $idx++, $year, $mon, $day, $zero, $first, 0 );
push @serastring, loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/eras/eraAbbr/era[\@type='$era']" );
push @sabbreverastring, loc_query( $loc, "/ldml/dates/calendars/calendar[\@type='$type']/eras/eraNarrow/era[\@type='$era']" );
}
$iyearoffsetrange = add_str_data( pack "S<L<*", scalar @eras, map { add_str_data($_); } @eras );
}
}
@sshortdate = @{$cal->{sshortdate}} if defined $cal->{sshortdate} && !@sshortdate;
@syearmonth = @{$cal->{syearmonth}} if defined $cal->{syearmonth};
@slongdate = @{$cal->{slongdate}} if defined $cal->{slongdate} && !@slongdate;
@serastring = @{$cal->{serastring}} if defined $cal->{serastring} && !@serastring;
@sdayname = @{$cal->{sdayname}} if defined $cal->{sdayname};
@sabbrevdayname = @{$cal->{sabbrevdayname}} if defined $cal->{sabbrevdayname};
@smonthname = @{$cal->{smonthname}} if defined $cal->{smonthname} && !join("",@smonthname);
@sabbrevmonthname = @{$cal->{sabbrevmonthname}} if defined $cal->{sabbrevmonthname} && !join("",@sabbrevmonthname);
@smonthday = @{$cal->{smonthday}} if defined $cal->{smonthday};
@sabbreverastring = @{$cal->{sabbreverastring}} if defined $cal->{sabbreverastring} && !@sabbreverastring;
@sshortestdayname = @{$cal->{sshortestdayname}} if defined $cal->{sshortestdayname};
my $srelativelongdate = $cal->{srelativelongdate};
@serastring = ("A.D.") unless @serastring;
@sabbreverastring = ("AD") unless @sabbreverastring;
if ($cal->{id} != 1) # calendar 1 is a placeholder, information is fetched from locale instead
{
@sshortdate = ("") unless @sshortdate;
@syearmonth = ("") unless @syearmonth;
@slongdate = ("") unless @slongdate;
@sdayname = ("") x 7 unless @sdayname;
@sabbrevdayname = ("") x 7 unless @sabbrevdayname;
@sshortestdayname = ("") x 7 unless @sshortestdayname;
@smonthname = ("") x 13 unless @smonthname;
@sabbrevmonthname = ("") x 13 unless @sabbrevmonthname;
@smonthday = ("") unless @smonthday;
}
$calendar_data .= pack "S<2L<17",
$cal->{id}, # CAL_ICALINTVALUE
$itwodigityearmax || 99, # CAL_ITWODIGITYEARMAX
add_strarray( @sshortdate ), # CAL_SSHORTDATE
add_strarray( @syearmonth ), # CAL_SYEARMONTH
add_strarray( @slongdate ), # CAL_SLONGDATE
add_strarray( @serastring ), # CAL_SERASTRING
$iyearoffsetrange, # CAL_IYEAROFFSETRANGE
add_strarray( @sdayname ), # CAL_SDAYNAME
add_strarray( @sabbrevdayname ), # CAL_SABBREVDAYNAME
add_strarray( @smonthname ), # CAL_SMONTHNAME
add_strarray( @sabbrevmonthname ), # CAL_SABBREVMONTHNAME
add_string( $scalname ), # CAL_SCALNAME
add_strarray( @smonthday ), # CAL_SMONTHDAY
add_strarray( @sabbreverastring ), # CAL_SABBREVERASTRING
add_strarray( @sshortestdayname ), # CAL_SSHORTESTDAYNAME
add_string( $srelativelongdate ); # CAL_SRELATIVELONGDATE
}
# output locale header
my $nb_lcids = scalar keys %lcids;
my $nb_locales = scalar grep { !defined $_->{alias} } @locales;
my $nb_lcnames = scalar keys %lcnames;
my $locale_size = length($locale_data) / $nb_locales;
my $nb_calendars = scalar @calendars;
my $calendar_size = length($calendar_data) / $nb_calendars;
my $lcids_offset = 19 * 4; # size of header
my $lcnames_offset = $lcids_offset + length $lcid_data;
my $locales_offset = $lcnames_offset + length $lcname_data;
my $calendar_offset = $locales_offset + length $locale_data;
my $strings_offset = $calendar_offset + length $calendar_data;
my $locale_header = pack "L<7S<4L<S<2L<3S<2L<4",
8, # offset
0,
7, # version
0x5344534e, # magic
0, 0, 0,
0,
$nb_lcids,
$nb_locales,
$locale_size,
$locales_offset,
$nb_lcnames,
0,
$lcids_offset,
$lcnames_offset,
0,
$nb_calendars,
$calendar_size,
$calendar_offset,
$strings_offset,
0, 0;
return align_string( 4, $locale_header . $lcid_data . $lcname_data . $locale_data . $calendar_data . $string_data );
}
################################################################
# build the charmaps table for locale.nls
sub build_charmaps_data()
{
my $data = "";
# MAP_FOLDDIGITS
my @digits = (ord('0') .. ord('9'));
$digitmap_table[0x3007] = $digits[0]; # Ideographic Zero
@digitmap_table[0x0c78..0x0c7b] = @digits[0..3]; # Telugu Fraction Digits
@digitmap_table[0x0c7c..0x0c7e] = @digits[1..3]; # Telugu Fraction Digits
@digitmap_table[0x3021..0x3029] = @digits[1..9]; # Hangzhou Numerals
@digitmap_table[0xa8e0..0xa8e9] = @digits; # Combining Devanagari Digits
@digitmap_table[0x10107..0x1010f] = @digits[1..9]; # Aegean Numbers
$digitmap_table[0x10320] = $digits[1]; # Old Italic Numerals
$digitmap_table[0x10321] = $digits[5]; # Old Italic Numerals
$data .= dump_binary_case_table( @digitmap_table );
# CJK compatibility map
$data .= dump_binary_case_table( @cjk_compat_table );
# LCMAP_HIRAGANA/KATAKANA
my (@hiragana_table, @katakana_table);
foreach my $ch (0x3041..0x3096, 0x309d..0x309e)
{
$hiragana_table[$ch + 0x60] = $ch;
$katakana_table[$ch] = $ch + 0x60;
}
$data .= dump_binary_case_table( @hiragana_table ) . dump_binary_case_table( @katakana_table );
# LCMAP_HALFWIDTH/FULLWIDTH
$halfwidth_table[0x2018] = 0x0027;
$halfwidth_table[0x2019] = 0x0027;
$halfwidth_table[0x201c] = 0x0022;
$halfwidth_table[0x201d] = 0x0022;
$halfwidth_table[0x309b] = 0xff9e;
$halfwidth_table[0x309c] = 0xff9f;
$fullwidth_table[0x309b] = 0x3099;
$fullwidth_table[0x309c] = 0x309a;
$data .= dump_binary_case_table( @halfwidth_table ) . dump_binary_case_table( @fullwidth_table );
# LCMAP_TRADITIONAL/SIMPLIFIED_CHINESE
$data .= dump_binary_case_table( @chinese_traditional_table ) . dump_binary_case_table( @chinese_simplified_table );
# FIXME: some more unknown tables here
return $data;
}
################################################################
# build the geoids table for locale.nls
sub build_geoids_data()
{
my $data = "";
my %index;
my $idx = 0;
my @geo_header = (0x00650067, 0x0000006f, 0, 4 * 7, scalar @geoids, 0, 0);
foreach my $geo (@geoids)
{
my $id = $geo->{id};
$geo = $geo->{alias} if defined $geo->{alias};
my $lat = "0.000";
my $long = "0.000";
my $iso2 = $geo->{iso2} || "XX";
my $iso3 = $geo->{iso3} || "XX";
my $isregion = $geo->{region} || (defined $geo->{uncode} && !defined $geo->{iso2});
my $sintlsymbol = $geo->{sintlsymbol} || "XDR";
my $scurrency = $geo->{scurrency} || "\x{00a4}";
$data .= pack( "L<", $id );
$data .= pad_string( 24, encode( "UTF16LE", $lat ));
$data .= pad_string( 24, encode( "UTF16LE", $long ));
$data .= pack( "L<2", $isregion ? 14 : 16, $geo->{parentid} || 39070 );
$data .= pad_string( 8, encode( "UTF16LE", $iso2 ));
$data .= pad_string( 8, encode( "UTF16LE", $iso3 ));
$data .= pack( "S<2", $geo->{uncode} || 0, $geo->{dialcode} || 0 );
$data .= pad_string( 8, encode( "UTF16LE", $sintlsymbol ));
$data .= pad_string( 16, encode( "UTF16LE", $scurrency ));
$index{$geo->{name}} = $idx if $geo->{name};
$idx++;
}
$index{"XX"} = $index{"001"};
$geo_header[5] = $geo_header[3] + length $data;
$geo_header[6] = scalar keys %index;
foreach my $name (sort keys %index)
{
$data .= pad_string( 8, encode( "UTF16LE", $name ));
$data .= pack "L<", $index{$name};
}
$geo_header[2] = $geo_header[3] + length $data;
return pack( "L<7", @geo_header ) . $data;
}
################################################################
# build a binary locale table
sub dump_locales($$)
{
my ($filename, $chartypes) = @_;
printf "Building $filename\n";
my $locale_data = build_locale_data();
my $charmaps_data = build_charmaps_data();
my $geoids_data = build_geoids_data();
my $scripts_data = ""; # FIXME
my @header = ( 0 ) x 8;
$header[0] = 4 * scalar @header; # chartypes offset
$header[4] = $header[0] + length $chartypes; # locales offset
$header[5] = $header[4] + length $locale_data; # charmaps offset
$header[6] = $header[5] + length $charmaps_data; # geoids offset
$header[7] = $header[6] + length $geoids_data; # scripts offset
open OUTPUT, ">$filename.new" or die "Cannot create $filename";
print OUTPUT pack "L<*", @header;
print OUTPUT $chartypes, $locale_data, $charmaps_data, $geoids_data, $scripts_data;
close OUTPUT;
save_file($filename);
}
################################################################
# return the day of week of the first of the month
sub month_first_dow($$)
{
my ($year, $month) = @_;
my @time = gmtime( timegm_modern( 0, 0, 0, 1, $month - 1, $year ));
return $time[6];
}
################################################################
# compare system time values
sub compare_systime($$)
{
my ($a, $b) = @_;
return $a->[0] <=> $b->[0] ||
$a->[1] <=> $b->[1] ||
$a->[2] <=> $b->[2] ||
$a->[3] <=> $b->[3] ||
$a->[4] <=> $b->[4] ||
$a->[5] <=> $b->[5] ||
$a->[6] <=> $b->[6];
}
################################################################
# compare the zone transition date with the rule date
sub compare_transition_date($$$$)
{
my ($stdoff, $isdst, $zone, $rule) = @_;
if (scalar @{$zone} <= 1)
{
return (!defined($zone->[0]) || $zone->[0] > $rule->[0]) ? 1 : -1;
}
my @date = parse_transition_date( $stdoff, $isdst, $zone->[0], $zone->[1], $zone->[2], $zone->[3] || 0 );
return compare_systime( \@date, $rule );
}
################################################################
# get the Windows zone names from the CLDR data
sub load_windows_zones()
{
my $current_name;
my %names;
my $base = "cldr-release-$CLDRVERSION";
my $INPUT = open_data_file( "cldr", "$base/common/supplemental/windowsZones.xml" );
while (<$INPUT>)
{
if (/<!-- +(\(UTC[^<]*) -->.*/)
{
$current_name = $1;
}
if (/<mapZone other="(.*)" territory="001" type="(.*)"\/>/)
{
$names{$1} = [ $current_name, $2 ];
}
}
close $INPUT;
return %names;
}
################################################################
# parse a transition date specification from the tzdata files
sub parse_transition_date($$@)
{
use integer;
my ($stdoff, $isdst, $year, $in, $on, $at) = @_;
$on = "1" unless defined $on;
$at = "0" unless defined $at;
my %months = ( Jan => 1, Feb => 2, Mar => 3, Apr => 4, May => 5, Jun => 6,
Jul => 7, Aug => 8, Sep => 9, Oct => 10, Nov => 11, Dec => 12 );
my %days = ( Sun => 0, Mon => 1, Tue => 2, Wed => 3, Thu => 4, Fri => 5, Sat => 6 );
my $mon = $in ? $months{$in} : 1;
my ($week, $dow, $flag, $time, $sec);
my $first = month_first_dow( $year, $mon );
if ($on =~ /^last(.*)$/)
{
$week = 5;
$dow = $days{$1};
}
elsif ($on =~ /^(.*)>=(\d+)$/)
{
$dow = $days{$1};
my $diff = ($first + 6 - $dow) % 7;
$week = $2 >= 25 ? 5 : ($2 + 6 + $diff) / 7;
}
elsif ($on =~ /^(.*)<=(\d+)$/)
{
$dow = $days{$1};
my $diff = ($first + $2 + 6 - $dow) % 7;
$week = ($2 + 6 - $diff) / 7;
if (!$week)
{
$week = 5;
if (!--$mon) { $mon = 12; $year--; }
}
}
elsif ($on =~ /^\d+$/)
{
$dow = ($first + $on - 1) % 7;
$week = $on >= 25 ? 5 : ($on + 6) / 7;
}
else
{
die "unsupported date specification $year $in $on $at";
}
if ($at =~ /^(\d+):(\d+):(\d+)([uws]?)$/)
{
$time = $1 * 60 + $2;
$sec = $3;
$flag = $4;
}
elsif ($at =~ /^(\d+):(\d+)([uws]?)$/)
{
$time = $1 * 60 + $2;
$flag = $3;
}
elsif ($at =~ /^(\d+)([uws]?)$/)
{
$time = $1 * 60;
$flag = $2;
}
else
{
die "unsupported time specification $year $in $on $at";
}
$flag ||= "w";
$time -= $stdoff if $flag eq "u";
$time += 60 if !$isdst && $flag ne "w";
if ($time < 0) # previous day
{
$week-- if $week < 5 && $dow == month_first_dow( $year, $mon );
$week-- if $week == 5 && $dow == month_first_dow( $year + ($mon == 12), $mon % 12 + 1 );
if (!$week)
{
$week = 5;
if (!--$mon) { $mon = 12; $year--; }
}
$dow = ($dow + 6) % 7;
$time += 24 * 60;
}
return ($year, $mon, $week, $dow, $time / 60, $time % 60, $sec || 0);
}
################################################################
# parse a system time value as a SYSTEMTIME structure
sub pack_systime(@)
{
my ($year, $mon, $week, $dow, $hour, $min, $sec) = @_;
return pack "S<8", 0, $mon, $dow, $week, $hour < 24 ? ($hour, $min, $sec, 0) : (23, 59, 59, 999);
}
################################################################
# parse a timezone offset from the tzdata files
sub parse_tz_offset($)
{
my ($hour, $min) = split /:/, shift;
$min ||= 0;
return $hour < 0 ? -$hour * 60 + $min : -$hour * 60 - $min; # invert sign
}
################################################################
# build the timezone data
sub dump_timezones($@)
{
my $filename = shift;
my $FIRST_YEAR = 2000;
my $LAST_YEAR = 2030;
my %names = load_windows_zones();
my %zones;
my %rules;
my %links;
my %res_indices;
printf "Building $filename\n";
open OUTPUT, ">$filename.new" or die "Cannot create $filename";
print OUTPUT "/* Automatically generated; DO NOT EDIT!! */\n\n";
print OUTPUT "#include \"winresrc.h\"\n\n";
print OUTPUT "#pragma makedep po\n\n";
print OUTPUT "LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT\n\n";
print OUTPUT "STRINGTABLE\n{\n";
# load tzdata files
foreach my $filename (@_)
{
my $FILE = open_data_file( "tzdata", $filename );
my $zonename;
while (<$FILE>)
{
chomp;
s/\#.*$//;
next if /^\s*$/;
my @fields = split /\s+/;
if ($fields[0] eq "Zone" || ($zonename && $fields[0] eq ""))
{
shift @fields;
$zonename = shift @fields unless $zonename;
my ($stdoff, $rules, $dummy, @date) = @fields;
$zones{$zonename} ||= [ ];
push @{$zones{$zonename}}, [ parse_tz_offset( $stdoff ), $rules, @date ];
$zonename = undef unless @date; # last entry doesn't have an until date
next;
}
if ($fields[0] eq "Rule")
{
shift @fields;
my ($rulename, $from, $to, $dummy, $in, $on, $at, $save) = @fields;
$to = $from if $to eq "only";
$to = $LAST_YEAR if $to eq "max";
push @{$rules{$rulename}}, [ parse_tz_offset( $save ), $from, $to, $in, $on, $at ];
next;
}
if ($fields[0] eq "Link")
{
$links{$fields[2]} = $fields[1];
next;
}
die "unrecognized line $_";
}
close $FILE;
}
foreach my $name (sort { uc($a) cmp uc($b) } keys %names)
{
my ($display, $zone) = @{$names{$name}};
$zone = $links{$zone} if defined $links{$zone};
# build list of transitions
my @transitions;
my @from_date = ( 1 );
my $last_stdoff = 0;
for (my $i = 0; $i < scalar @{$zones{$zone}}; $i++)
{
my ($stdoff, $rule, @until_date) = @{$zones{$zone}->[$i]};
my $isdst = ($last_stdoff != $stdoff);
$from_date[0] ||= $LAST_YEAR;
my @systime = parse_transition_date( $stdoff, $isdst, @from_date );
push @transitions, [ $stdoff, -1, \@systime ];
if (defined $rules{$rule})
{
foreach my $r (@{$rules{$rule}})
{
my ($offset, $from, $to, $in, $on, $at) = @{$r};
foreach my $year ($from..$to)
{
next if $year < $from_date[0];
next if $until_date[0] && $year > $until_date[0];
my @systime = parse_transition_date( $stdoff, !!$offset, $year, $in, $on, $at );
next if compare_transition_date( $stdoff, $isdst, \@until_date, \@systime ) <= 0;
my $ret = compare_transition_date( $stdoff, $isdst, \@from_date, \@systime );
next if $ret > 0;
pop @transitions if !$ret; # remove transition if there's a dst change at the same time
push @transitions, [ $stdoff, $offset, \@systime ];
}
}
}
@from_date = @until_date;
$last_stdoff = $stdoff;
}
@transitions = sort { compare_systime( $a->[2], $b->[2] ) } @transitions;
# build per-year dynamic info
my @info;
my $last_dstoff = 0;
my $last_dst = 0;
my $year = $FIRST_YEAR;
while ($year <= $LAST_YEAR)
{
if (@transitions && $transitions[0]->[2]->[0] < $year)
{
$last_stdoff = $transitions[0]->[0];
shift @transitions;
next;
}
my ($std, $dst, @trans);
my $cur_stdoff = $last_stdoff;
my $cur_dstoff = ($name =~ /^UTC/) ? 0 : -60;
while (@transitions && $transitions[0]->[2]->[0] == $year)
{
my $t = shift @transitions;
my ($stdoff, $dstoff, $systime) = @{$t};
$systime = pack_systime( @{$systime} );
if (!$dstoff) # std
{
$cur_stdoff = $stdoff unless $std;
$std = $systime;
}
elsif ($dstoff != -1) # dst
{
$cur_dstoff = $dstoff unless $dst;
$dst ||= $systime;
}
elsif ($stdoff != $last_stdoff) # rule transition
{
# Handle a special case: Samoa moved to the other side of
# the date line between 2011-12-03 and 2012-01-01,
# entirely skipping the day 2011-12-31. We ignore this
# change because it happens on a year boundary and more
# importantly it would generate on offset of -25 hours,
# which some programs (e.g., Mono) do not like. See
# https://bugs.winehq.org/show_bug.cgi?id=51758
if ($last_stdoff - $stdoff < 24 * 60)
{
@trans = ($last_stdoff, $stdoff, $systime);
$cur_stdoff = $stdoff;
}
}
elsif ($dst) # rule transition with no stdoff change
{
$std = $systime;
}
$last_dstoff = ($dstoff == -1) ? 0 : $dstoff;
}
$last_stdoff = $cur_stdoff;
if ($cur_dstoff > 0) # swap std and dst to ensure that offset is negative
{
($std, $dst) = ($dst, $std);
$cur_stdoff += $cur_dstoff;
$cur_dstoff = -$cur_dstoff;
}
if (@trans)
{
# heuristic to prefer switching dst
if ($last_dst == $year - 1 || (!$last_dst && $trans[0] > $trans[1]))
{
$dst ||= $trans[2];
$cur_stdoff = $trans[0];
$cur_dstoff = $trans[1] - $trans[0];
}
else
{
$std ||= $trans[2];
$cur_stdoff = $trans[1];
$cur_dstoff = $trans[0] - $trans[1];
}
}
if ($std || $dst)
{
$std ||= pack_systime( parse_transition_date( 0, 0, $year, "Jan", 1 ));
$dst ||= pack_systime( parse_transition_date( 0, 0, $year, "Jan", 1 ));
$last_dst = $year;
}
else
{
$std = pack "S<8", 0;
$dst = pack "S<8", 0;
$cur_stdoff += $last_dstoff;
}
$info[$year++] = pack( "l<3", $cur_stdoff, 0, $cur_dstoff ) . $std . $dst;
}
# output registry keys
my $std_name = $name eq "UTC" ? "Coordinated Universal Time" : $name;
my $dlt_name = $std_name =~ s/Standard Time/Daylight Time/r;
my $res_idx = hex( substr( Digest::SHA::sha1_hex($name), -3, 3 )) << 4;
$res_idx += 16 while exists $res_indices{$res_idx};
$res_indices{$res_idx} = 1;
add_registry_string_value( $zonekey, $name, "Display", $display );
add_registry_string_value( $zonekey, $name, "Std", $std_name );
add_registry_string_value( $zonekey, $name, "Dlt", $dlt_name );
add_registry_string_value( $zonekey, $name, "MUI_Std", sprintf( "\@tzres.dll,-%u", $res_idx ));
add_registry_string_value( $zonekey, $name, "MUI_Dlt", sprintf( "\@tzres.dll,-%u", $res_idx + 1 ));
add_registry_string_value( $zonekey, $name, "MUI_Display", sprintf( "\@tzres.dll,-%u", $res_idx + 2 ));
add_registry_binary_value( $zonekey, $name, "TZI", $info[$LAST_YEAR] );
printf OUTPUT "%7d \"#msgctxt#maximum 31 characters#%s\"\n", $res_idx, $std_name;
printf OUTPUT "%7d \"#msgctxt#maximum 31 characters#%s\"\n", $res_idx + 1, $dlt_name;
printf OUTPUT "%7d \"%s\"\n", $res_idx + 2, $display;
my $first_year = $FIRST_YEAR;
my $last_year = $LAST_YEAR;
$last_year-- while $last_year > $FIRST_YEAR && $info[$last_year] eq $info[$last_year - 1];
$first_year++ while $first_year < $last_year && $info[$first_year] eq $info[$last_year];
next if $last_year <= $first_year;
foreach my $i ($first_year..$last_year)
{
add_registry_binary_value( $zonekey, "$name\\Dynamic DST", $i, $info[$i] );
}
add_registry_dword_value( $zonekey, "$name\\Dynamic DST", "FirstEntry", $first_year );
add_registry_dword_value( $zonekey, "$name\\Dynamic DST", "LastEntry", $last_year );
}
print OUTPUT "}\n";
close OUTPUT;
save_file($filename);
}
################################################################
# build the script to create registry keys
sub dump_registry_script($%)
{
my ($filename, %keys) = @_;
my $indent = 1;
my @prev;
printf "Building %s\n", $filename;
open OUTPUT, ">$filename.new" or die "Cannot create $filename";
print OUTPUT "HKLM\n{\n";
foreach my $k (sort { ($a =~ tr/a-z\\/A-Z\001/r) cmp ($b =~ tr/a-z\\/A-Z\001/r) } keys %keys)
{
my @subkeys = split /\\/, $k;
while (@prev && @subkeys && $prev[0] eq $subkeys[0]) { shift @prev; shift @subkeys; }
while (@prev) { printf OUTPUT "%*s}\n", 4 * --$indent, ""; shift @prev; }
my ($def, @vals) = @{$keys{$k}};
for (my $i = 0; $i < @subkeys; $i++)
{
my $name = $subkeys[$i];
my $prefix = "";
if ($name =~ /^-/)
{
$name =~ s/^-//;
$prefix = "NoRemove ";
}
if ($name =~ /\s/)
{
$name = "'$name'";
}
printf OUTPUT "%*s%s%s%s\n%*s{\n", 4 * $indent, "", $prefix, $name,
$i == $#subkeys && $def ? " = s '$def'" : "", 4 * $indent, "";
$indent++;
}
foreach my $v (sort @vals) { printf OUTPUT "%*sval $v\n", 4 * $indent, ""; }
@prev = split /\\/, $k;
}
while (@prev) { printf OUTPUT "%*s}\n", 4 * --$indent, ""; shift @prev; }
printf OUTPUT "}\n";
close OUTPUT;
save_file($filename);
}
################################################################
# save a file if modified
sub save_file($)
{
my $file = shift;
if (-f $file && !system "cmp $file $file.new >/dev/null")
{
unlink "$file.new";
}
else
{
rename "$file.new", "$file";
}
}
################################################################
# main routine
chdir ".." if -f "./make_unicode";
load_data();
dump_bidi_dir_table( "dlls/gdi32/uniscribe/direction.c" );
dump_bidi_dir_table( "dlls/dwrite/direction.c" );
dump_bidi_dir_table( "dlls/wineps.drv/direction.c" );
dump_mirroring( "dlls/gdi32/uniscribe/mirror.c" );
dump_mirroring( "dlls/dwrite/mirror.c" );
dump_bracket( "dlls/gdi32/uniscribe/bracket.c" );
dump_bracket( "dlls/dwrite/bracket.c" );
dump_shaping( "dlls/gdi32/uniscribe/shaping.c" );
dump_arabic_shaping( "dlls/dwrite/shapers/arabic_table.c" );
dump_linebreak( "dlls/gdi32/uniscribe/linebreak.c" );
dump_linebreak( "dlls/dwrite/linebreak.c" );
dump_scripts( "dlls/dwrite/scripts" );
dump_indic( "dlls/gdi32/uniscribe/indicsyllable.c" );
dump_vertical( "dlls/win32u/vertical.c", 1 );
dump_vertical( "dlls/wineps.drv/vertical.c", 0 );
dump_intl_nls("nls/l_intl.nls");
dump_norm_table( "nls/normnfc.nls" );
dump_norm_table( "nls/normnfd.nls" );
dump_norm_table( "nls/normnfkc.nls" );
dump_norm_table( "nls/normnfkd.nls" );
dump_norm_table( "nls/normidna.nls" );
my $chartypes = dump_sortkey_table( "nls/sortdefault.nls" );
dump_locales( "nls/locale.nls", $chartypes );
foreach my $file (@allfiles) { dump_msdata_codepage( $file ); }
dump_eucjp_codepage();
dump_timezones( "dlls/tzres/tzres.rc", @timezone_files );
dump_registry_script( "dlls/kernelbase/kernelbase.rgs", %registry_keys );
exit 0;
# Local Variables:
# compile-command: "./make_unicode"
# End: