diff --git a/src/lib/export/odt.ts b/src/lib/export/odt.ts
index 73982e4..646b98e 100644
--- a/src/lib/export/odt.ts
+++ b/src/lib/export/odt.ts
@@ -875,7 +875,7 @@ function applyTableRowHeights(odtBytes: Uint8Array, heights: (string | null)[]):
``,
).join('\n');
- content = content.replace('', `${newStyles}\n`);
+ content = injectAutomaticStyles(content, `${newStyles}\n`);
files['content.xml'] = strToU8(content);
return rezipOdt(files);
@@ -1012,7 +1012,7 @@ function applyListItemStyles(odtBytes: Uint8Array, styles: ParaStyle[]): Uint8Ar
``,
).join('\n');
- content = content.replace('', `${newStyles}\n`);
+ content = injectAutomaticStyles(content, `${newStyles}\n`);
files['content.xml'] = strToU8(content);
return rezipOdt(files);
@@ -1095,7 +1095,7 @@ function applyPageBreaks(odtBytes: Uint8Array): Uint8Array {
content = content.split(PGB).join('');
if (minted.length) {
- content = content.replace('', `${minted.join('')}`);
+ content = injectAutomaticStyles(content, minted.join(''));
}
files['content.xml'] = strToU8(content);
@@ -1179,7 +1179,7 @@ function applyEmptyLineFontSizes(odtBytes: Uint8Array): Uint8Array {
content = content.replace(new RegExp(`${FSZ}[^${FSZ}]*${FSZ}`, 'g'), '');
if (minted.length) {
- content = content.replace('', `${minted.join('')}`);
+ content = injectAutomaticStyles(content, minted.join(''));
}
files['content.xml'] = strToU8(content);
@@ -1424,7 +1424,7 @@ function applyParagraphStyles(odtBytes: Uint8Array): Uint8Array {
content = content.replace(new RegExp(`${STY}[^${STY}]*${STY}`, 'g'), '');
if (minted.length) {
- content = content.replace('', `${minted.join('')}`);
+ content = injectAutomaticStyles(content, minted.join(''));
}
files['content.xml'] = strToU8(content);
@@ -1480,7 +1480,7 @@ function applyParagraphBoxes(odtBytes: Uint8Array): Uint8Array {
content = content.replace(new RegExp(`${PBX}[^${PBX}]*${PBX}`, 'g'), '');
if (minted.length) {
- content = content.replace('', `${minted.join('')}`);
+ content = injectAutomaticStyles(content, minted.join(''));
}
files['content.xml'] = strToU8(content);
@@ -1900,7 +1900,7 @@ function applyNestedListTypes(odtBytes: Uint8Array, fixes: (ListDef | null)[]):
});
if (minted.length) {
- content = content.replace('', `${minted.join('\n')}\n`);
+ content = injectAutomaticStyles(content, `${minted.join('\n')}\n`);
}
files['content.xml'] = strToU8(content);
return rezipOdt(files);
@@ -2093,7 +2093,7 @@ function applyCellBlocks(odtBytes: Uint8Array, cellBlocks: CellBlock[][]): Uint8
}
if (additions.length) {
- content = content.replace('', `${additions.join('\n')}\n`);
+ content = injectAutomaticStyles(content, `${additions.join('\n')}\n`);
}
files['content.xml'] = strToU8(content);
@@ -2446,7 +2446,7 @@ function applyCharacterStyles(odtBytes: Uint8Array): Uint8Array {
content = content.replace(new RegExp(`${CST}[^${CST}]*${CST}`, 'g'), '');
if (minted.length) {
- content = content.replace('', `${minted.join('')}`);
+ content = injectAutomaticStyles(content, minted.join(''));
}
files['content.xml'] = strToU8(content);
@@ -2510,9 +2510,7 @@ function applyTextEffects(odtBytes: Uint8Array): Uint8Array {
const content = resolveTextEffects(before, (s) => minted.push(s), 'TE');
if (content === before) return odtBytes;
- files['content.xml'] = strToU8(
- minted.length ? content.replace('', `${minted.join('')}`) : content,
- );
+ files['content.xml'] = strToU8(injectAutomaticStyles(content, minted.join('')));
return rezipOdt(files);
}
@@ -2831,16 +2829,19 @@ function hfBackgroundStyleXml(index: number): string {
);
}
-// Inject automatic styles, tolerating an empty/self-closed or absent section.
+// Inject automatic styles into content.xml or styles.xml, tolerating an empty/self-closed
+// or absent section — a document odf-kit gave none writes ``, and
+// a plain string replace on the closing tag would drop the styles there.
function injectAutomaticStyles(content: string, styles: string): string {
if (!styles) return content;
if (content.includes('')) {
return content.replace('', `${styles}`);
}
+ const section = `${styles}`;
if (content.includes('')) {
- return content.replace('', `${styles}`);
+ return content.replace('', section);
}
- return content.replace('${styles} `${section}${m}`);
}
// Resolve image sentinels: swap each IMG{i}IMG for its , add the binary
@@ -3587,8 +3588,7 @@ function applyHfPostProcess(odtBytes: Uint8Array, margins: PageMargins, headerPa
}
if (mintedStyles.length) {
const defs = mintedStyles.join('');
- if (styles.includes('')) styles = styles.replace('', `${defs}`);
- else styles = styles.replace('', `${defs}`);
+ styles = injectAutomaticStyles(styles, defs);
}
// Resolve HFIMG sentinels (default + first-page zones) to as-char s, then
@@ -3601,8 +3601,7 @@ function applyHfPostProcess(odtBytes: Uint8Array, margins: PageMargins, headerPa
});
const bgStyles = hfImages.map((img, i) => (img.wrap === 'inline' ? '' : hfBackgroundStyleXml(i))).join('');
if (bgStyles) {
- if (styles.includes('')) styles = styles.replace('', `${bgStyles}`);
- else styles = styles.replace('', `${bgStyles}`);
+ styles = injectAutomaticStyles(styles, bgStyles);
}
for (const img of hfImages) files[img.path] = img.bytes;
const manifestBytes = files['META-INF/manifest.xml'];
@@ -3719,9 +3718,7 @@ function applySectionMasterPages(odtBytes: Uint8Array, sets: HfSet[], pageCount:
},
);
if (minted.length) {
- content = content.includes('')
- ? content.replace('', `${minted.join('')}`)
- : content.replace('', `${minted.join('')}`);
+ content = injectAutomaticStyles(content, minted.join(''));
}
// Reuse the page layout odf-kit gave the Standard master page: the geometry is
@@ -3737,9 +3734,7 @@ function applySectionMasterPages(odtBytes: Uint8Array, sets: HfSet[], pageCount:
if (pages.length) styles = styles.replace('', `${pages.join('')}`);
if (hfStyles.length) {
const defs = hfStyles.join('');
- styles = styles.includes('')
- ? styles.replace('', `${defs}`)
- : styles.replace('', `${defs}`);
+ styles = injectAutomaticStyles(styles, defs);
}
files['content.xml'] = strToU8(content);
diff --git a/tests/unit/odt-page-break-export.test.ts b/tests/unit/odt-page-break-export.test.ts
new file mode 100644
index 0000000..060613c
--- /dev/null
+++ b/tests/unit/odt-page-break-export.test.ts
@@ -0,0 +1,24 @@
+import { describe, it, expect } from 'vitest';
+import { unzipSync, strFromU8 } from 'fflate';
+import { buildOdt } from '../../src/lib/export/odt';
+
+// A document odf-kit mints no automatic style for carries ``,
+// self-closed — replacing the closing tag silently dropped every minted style there,
+// so a manual page break exported as a plain paragraph.
+const doc = {
+ type: 'doc',
+ content: [
+ { type: 'paragraph', content: [{ type: 'text', text: 'one' }] },
+ { type: 'paragraph', attrs: { breakBefore: 'page' }, content: [{ type: 'text', text: 'two' }] },
+ ],
+};
+
+describe('ODF page break export', () => {
+ it('mints the break style even with no automatic styles section', async () => {
+ const bytes = await buildOdt(doc as never, undefined, 'portrait');
+ const content = strFromU8(unzipSync(bytes)['content.xml']);
+ const name = /style:name="(PB\d+)"[^>]*>\s*two`);
+ });
+});