Skip to content

Commit 9113d27

Browse files
committed
Apply the simple titlecase fallback for Unicode 4 and later
Preserve historical identity mappings for titlecase digraphs while honoring the standardized uppercase fallback for null titlecase fields. Assisted-By: GPT-6
1 parent d96a858 commit 9113d27

3 files changed

Lines changed: 35 additions & 0 deletions

File tree

‎scripts/parse-simple-case-mapping.mjs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ const parseSimpleCaseMapping = async (version) => {
1212
// The column index of Simple_Uppercase_Mapping in UnicodeData.txt:
1313
// https://www.unicode.org/reports/tr44/#UnicodeData.txt
1414
const columnIndexOfSimpleUppercaseMapping = 12;
15+
// Unicode 4.0 standardized the null titlecase fallback to uppercase.
16+
// Earlier files leave the identity mapping of titlecase digraphs empty.
17+
// https://www.unicode.org/L2/L2002/02345.htm (93-C12)
18+
const titlecaseDefaultsToUppercase = parseInt(version, 10) >= 4;
1519

1620
const source = utils.readDataFile(version, 'database');
1721
if (!source) {
@@ -24,6 +28,9 @@ const parseSimpleCaseMapping = async (version) => {
2428
continue;
2529
}
2630
const data = lineTrimmed.split(';');
31+
if (titlecaseDefaultsToUppercase && data[14] === '') {
32+
data[14] = data[columnIndexOfSimpleUppercaseMapping];
33+
}
2734
const from = parseInt(data[0], 16);
2835
for (let columnOffset = 0; columnOffset <= 2; columnOffset++) {
2936
const raw = data[columnIndexOfSimpleUppercaseMapping + columnOffset];

‎tests/parser-formats.mjs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,29 @@ import parseEmoji from '../scripts/parse-emoji.mjs';
44
import parseEmojiSequences from '../scripts/parse-emoji-sequences.mjs';
55
import parseBidiBrackets from '../scripts/parse-bidi-brackets.mjs';
66
import parseNameAliases from '../scripts/parse-name-aliases.mjs';
7+
import parseSimpleCaseMapping from '../scripts/parse-simple-case-mapping.mjs';
78
import {
89
parseBlocks,
910
parseDerivedCoreProperties,
1011
} from '../scripts/parse-blocks-scripts-properties.mjs';
1112
import utils from '../scripts/utils.mjs';
1213

14+
test('Null simple titlecase mappings default to uppercase mappings', async (t) => {
15+
t.mock.method(utils, 'readDataFile', () =>
16+
[
17+
'0061;LATIN SMALL LETTER A;Ll;0;L;;;;;N;;;0041;;',
18+
'01C6;LATIN SMALL LETTER DZ WITH CARON;Ll;0;L;<compat> 0064 017E;;;;N;;;01C4;;01C5',
19+
'0030;DIGIT ZERO;Nd;0;EN;;0;0;0;N;;;;;',
20+
].join('\n'),
21+
);
22+
for (const version of ['4.0.0', '18.0.0']) {
23+
const map = await parseSimpleCaseMapping(version);
24+
t.assert.equal(map.Titlecase.get(0x61), 0x41);
25+
t.assert.equal(map.Titlecase.get(0x1c6), 0x1c5);
26+
t.assert.ok(!map.Titlecase.has(0x30));
27+
}
28+
});
29+
1330
test('SpecialCasing accepts optional comments and flexible whitespace', async (t) => {
1431
t.mock.method(
1532
utils,

‎tests/parsers.mjs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import utils from '../scripts/utils.mjs';
77
import parseDecompositionMapping from '../scripts/parse-decomposition-mapping.mjs';
88
import parseIndicConjunctBreak from '../scripts/parse-indic-conjunct-break.mjs';
99
import parseJoiningType from '../scripts/parse-joining-type.mjs';
10+
import parseSimpleCaseMapping from '../scripts/parse-simple-case-mapping.mjs';
1011
import {
1112
parseBlocks,
1213
parseDerivedCoreProperties,
@@ -15,6 +16,16 @@ import {
1516
parseScripts,
1617
} from '../scripts/parse-blocks-scripts-properties.mjs';
1718

19+
test('Historical titlecase digraphs retain their identity mapping', async (t) => {
20+
for (const version of ['1.1.5', '2.0.14', '3.2.0', '4.0.0', '18.0.0']) {
21+
const { Titlecase, Uppercase } = await parseSimpleCaseMapping(version);
22+
for (const cp of [0x1c5, 0x1c8, 0x1cb, 0x1f2]) {
23+
t.assert.equal(Titlecase.get(cp) ?? cp, cp, version);
24+
t.assert.equal(Uppercase.get(cp), cp - 1, version);
25+
}
26+
}
27+
});
28+
1829
test('Block values cover Unicode, including No_Block gaps', async (t) => {
1930
for (const version of ['2.0.14', '3.1.0', '18.0.0']) {
2031
const blocks = await parseBlocks(version);

0 commit comments

Comments
 (0)