From c3f4b2963df371a6d8688d5dd0029fdc98cdbb10 Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Thu, 9 Jul 2026 15:34:13 +0000 Subject: [PATCH] Serialize the #This Row section specifier in title case stringifyStructRef sentence-cased section specifiers: it upper-cased the first letter and lower-cased the rest. That is correct for the single-word specifiers (#All, #Data, #Headers, #Totals) but wrong for the two-word #This Row, which came out as "#This row". Excel's canonical serialization is "#This Row" (both words capitalized), and a lower-cased "row" is off-canonical, so title-case each word instead. --- lib/stringifyStructRef.spec.ts | 10 ++++++++-- lib/stringifyStructRef.ts | 10 ++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/stringifyStructRef.spec.ts b/lib/stringifyStructRef.spec.ts index efe4b1b..88b8ae9 100644 --- a/lib/stringifyStructRef.spec.ts +++ b/lib/stringifyStructRef.spec.ts @@ -113,12 +113,18 @@ describe('longform serialize (in xlsx mode)', () => { table: 'Table2', columns: [ 'col1' ], sections: [ 'this row' ] - }, { thisRow: true })).toBe('Table2[[#This row],[col1]]'); + }, { thisRow: true })).toBe('Table2[[#This Row],[col1]]'); expect(stringifyStructRef({ table: 'Table2', columns: [ 'col1' ], sections: [ 'this row' ] - }, { thisRow: true })).toBe('Table2[[#This row],[col1]]'); + }, { thisRow: true })).toBe('Table2[[#This Row],[col1]]'); + + // Keyword-only this-row (no column) also title-cases both words. + expect(stringifyStructRef({ + table: 'Table2', + sections: [ 'this row' ] + }, { thisRow: true })).toBe('Table2[#This Row]'); }); }); diff --git a/lib/stringifyStructRef.ts b/lib/stringifyStructRef.ts index eeeb2e6..87a0f6a 100644 --- a/lib/stringifyStructRef.ts +++ b/lib/stringifyStructRef.ts @@ -9,8 +9,10 @@ function needsBraces (str: string): boolean { return /[^a-zA-Z0-9\u00a1-\uffff]/.test(str); } -function toSentenceCase (str: string): string { - return str[0].toUpperCase() + str.slice(1).toLowerCase(); +function toTitleCase (str: string): string { + // Section specifiers are title-cased per word, so the two-word `this row` + // serializes as `This Row` (Excel's canonical form), not `This row`. + return str.toLowerCase().replace(/\b\w/g, c => c.toUpperCase()); } export function stringifySRef (refObject: ReferenceStruct, thisRow = false) { @@ -22,7 +24,7 @@ export function stringifySRef (refObject: ReferenceStruct, thisRow = false) { const numSections = refObject.sections?.length ?? 0; // single section if (numSections === 1 && !numColumns) { - s += `[#${toSentenceCase(refObject.sections[0])}]`; + s += `[#${toTitleCase(refObject.sections[0])}]`; } // single column else if (!numSections && numColumns === 1) { @@ -37,7 +39,7 @@ export function stringifySRef (refObject: ReferenceStruct, thisRow = false) { } else if (numSections) { s += refObject.sections - .map(d => `[#${toSentenceCase(d)}]`) + .map(d => `[#${toTitleCase(d)}]`) .join(','); if (numColumns) { s += ',';