test: the corpus gains a document whose chapters are numbered
15-chapters carries what only a stylesheet holds: numbering on the heading styles and two heading styles of the document's own. The round-trip legs now carry that stylesheet too — a style the file names but the built-ins never heard of can only be written from there — and one absolute assertion says the chapters read as headings, which a round trip cannot: both its sides go through the same importer, so a consistent misreading compares equal. A numbered heading style rides the package's factory slot; a second definition under the same id leaves two, and LibreOffice reads the first.
This commit is contained in:
1 parent
e12a564f52
commit
8b006d22a8
4 files changed
+82
-10
No files matched your search
+22
-3
@@ -53,16 +53,35 @@ describe.skipIf(!files.length)('the authored corpus', () => {
|
||||
|
||||
for (const f of files) {
|
||||
it(`${f} imports and survives its own round trip`, async () => {
|
||||
const doc = importAny(f, load(f));
|
||||
const read = readAny(f, load(f));
|
||||
const doc = read.content;
|
||||
expect(outline(doc).length).toBeGreaterThan(0);
|
||||
const margins = { top: 2, bottom: 2, left: 2, right: 2 };
|
||||
// The file's own stylesheet goes back out with it, as it does when the app saves:
|
||||
// a style the document names but builtinStyleSheet() has never heard of can only
|
||||
// be written from there.
|
||||
const again = f.endsWith('.odt')
|
||||
? importOdt(await buildOdt(doc, margins, 'portrait'))
|
||||
: importDocx(await buildDocx(doc, margins, 'portrait'));
|
||||
? importOdt(await buildOdt(doc, margins, 'portrait', undefined, undefined, undefined, read.styles))
|
||||
: importDocx(await buildDocx(doc, margins, 'portrait', undefined, undefined, undefined, read.styles));
|
||||
expect(outline(again.content)).toEqual(outline(doc));
|
||||
});
|
||||
}
|
||||
|
||||
// What the legs above cannot see: both sides of a round trip go through the same
|
||||
// importer, so a file read wrongly but consistently still compares equal. 15-chapters
|
||||
// carries chapter numbering on its heading styles and two heading styles of its own —
|
||||
// read as a list, or as body text, either would round-trip happily.
|
||||
const CHAPTERS = ['heading1+brk:Introduction', 'heading2:Motivation',
|
||||
'heading1+brk:Results', 'heading2:Method',
|
||||
'heading1[Appendix 1]+brk:Appendix', 'heading2[Appendix 2]:List of Tables'];
|
||||
for (const f of files.filter((n) => n.startsWith('15-chapters'))) {
|
||||
it(`${f} reads its numbered chapters as headings`, () => {
|
||||
const doc = importAny(f, load(f));
|
||||
expect(outline(doc).filter((l) => l.startsWith('heading'))).toEqual(CHAPTERS);
|
||||
expect(outline(doc).filter((l) => /^(ordered|bullet)List|^listItem/.test(l))).toEqual([]);
|
||||
});
|
||||
}
|
||||
|
||||
// Word re-saves of the same documents (tests/corpus/word/): Word rewrites the file
|
||||
// in its own dialect — rsids, proofErr, separator notes, theme docDefaults — which
|
||||
// neither the docx lib nor LibreOffice produce, so this is the third author here.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -31,25 +31,35 @@ const para = (text, o = {}) => new Paragraph({ children: [new TextRun({ text, ..
|
||||
// Spacing, font and outline level are declared: LibreOffice fills a built-in style's
|
||||
// gaps from its own defaults where Word uses only what the file declares, and without
|
||||
// the level it converts a heading to a <text:p> merely carrying the heading's style.
|
||||
const heading = (id, name, halfPt, before, after, level) => ({
|
||||
const heading = (id, name, halfPt, before, after, level, numbering) => ({
|
||||
id, name, basedOn: 'Normal', next: 'Normal', quickFormat: true,
|
||||
run: { font: 'Arial', size: halfPt, bold: true },
|
||||
paragraph: { spacing: { before, after }, outlineLevel: level },
|
||||
paragraph: { spacing: { before, after }, outlineLevel: level, ...(numbering ? { numbering } : {}) },
|
||||
});
|
||||
|
||||
async function write(name, sections, defaultRun = { font: 'Times New Roman', size: 24 }, extra = {}) {
|
||||
// `chapters` names a numbering the heading styles carry: chapter numbering lives on the
|
||||
// style in both products, never on the paragraph. `extra.styles` adds the document's own
|
||||
// styles beside the built-ins rather than replacing the whole block.
|
||||
async function write(name, sections, defaultRun = { font: 'Times New Roman', size: 24 }, extra = {}, chapters = null) {
|
||||
if (only && !only.test(name)) return;
|
||||
const { styles: ownStyles, ...rest } = extra;
|
||||
const h1 = heading('Heading1', 'Heading 1', 36, 240, 120, 0, chapters && { reference: chapters, level: 0 });
|
||||
const h2 = heading('Heading2', 'Heading 2', 32, 200, 100, 1, chapters && { reference: chapters, level: 1 });
|
||||
// The package writes its own Heading1-6 regardless, so a second definition under the
|
||||
// same id leaves two — and LibreOffice reads the first, dropping everything declared
|
||||
// here. A numbered heading therefore rides the factory slot instead (see FACTORY_SLOTS
|
||||
// in src/lib/export/CLAUDE.md); the plain fixtures keep the shape they were built with.
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
default: { document: { run: defaultRun } },
|
||||
default: { document: { run: defaultRun }, ...(chapters ? { heading1: h1, heading2: h2 } : {}) },
|
||||
paragraphStyles: [
|
||||
// Word always writes an explicit Normal; the docx lib would emit docDefaults only.
|
||||
{ id: 'Normal', name: 'Normal', run: defaultRun, paragraph: { spacing: { after: 0 } } },
|
||||
heading('Heading1', 'Heading 1', 36, 240, 120, 0),
|
||||
heading('Heading2', 'Heading 2', 32, 200, 100, 1),
|
||||
...(chapters ? [] : [h1, h2]),
|
||||
...(ownStyles?.paragraphStyles ?? []),
|
||||
],
|
||||
},
|
||||
...extra,
|
||||
...rest,
|
||||
sections,
|
||||
});
|
||||
writeFileSync(join(OUT, name), await Packer.toBuffer(doc));
|
||||
@@ -286,6 +296,49 @@ await write('14-formulas.docx', [{
|
||||
],
|
||||
}]);
|
||||
|
||||
// 15. Chapter numbering on the heading styles, plus heading styles of the document's
|
||||
// own ("Appendix 1"/"2", named nothing like "Heading n"). Both live in the stylesheet
|
||||
// and nowhere in the document tree, so only a leg carrying it exercises them.
|
||||
await write('15-chapters.docx', [{
|
||||
properties: { page },
|
||||
children: [
|
||||
para('Introduction', { p: { style: 'Heading1', pageBreakBefore: true } }),
|
||||
para(LOREM),
|
||||
para('Motivation', { p: { style: 'Heading2' } }),
|
||||
para(LOREM),
|
||||
para('Results', { p: { style: 'Heading1', pageBreakBefore: true } }),
|
||||
para(LOREM),
|
||||
para('Method', { p: { style: 'Heading2' } }),
|
||||
para(LOREM),
|
||||
para('Appendix', { p: { style: 'Appendix1', pageBreakBefore: true } }),
|
||||
para(LOREM),
|
||||
para('List of Tables', { p: { style: 'Appendix2' } }),
|
||||
para(LOREM),
|
||||
],
|
||||
}], undefined, {
|
||||
styles: {
|
||||
paragraphStyles: [
|
||||
{ id: 'Appendix1', name: 'Appendix 1', basedOn: 'Heading1', next: 'Normal', quickFormat: true,
|
||||
paragraph: { outlineLevel: 0 } },
|
||||
{ id: 'Appendix2', name: 'Appendix 2', basedOn: 'Heading2', next: 'Normal', quickFormat: true,
|
||||
paragraph: { outlineLevel: 1 } },
|
||||
],
|
||||
},
|
||||
numbering: {
|
||||
config: [{
|
||||
reference: 'chapter-numbering',
|
||||
levels: [
|
||||
// The level names the style it numbers and the style points back at the level:
|
||||
// both halves, or LibreOffice reads the pair as an ordinary list.
|
||||
{ level: 0, format: LevelFormat.DECIMAL, text: '%1.', alignment: AlignmentType.START,
|
||||
style: { paragraph: { indent: { left: 0, hanging: 360 } }, style: 'Heading1' } },
|
||||
{ level: 1, format: LevelFormat.DECIMAL, text: '%1.%2', alignment: AlignmentType.START,
|
||||
style: { paragraph: { indent: { left: 0, hanging: 432 } }, style: 'Heading2' } },
|
||||
],
|
||||
}],
|
||||
},
|
||||
}, 'chapter-numbering');
|
||||
|
||||
// ODT twins, written by LibreOffice itself — the dominant ODT producer, so they carry
|
||||
// its own conventions (percentage font sizes, Text Body, list styles) and exercise the
|
||||
// foreign-document path our own exporter never produces.
|
||||
|
||||
Reference in new issue
Block a user