fix: read OOXML toggles as ST_OnOff, not as element presence
w:evenAndOddHeaders, w:titlePg and w:pageBreakBefore were treated as on whenever the element existed, but w:val="false"/"0"/"off" turns them off and both Word and the docx lib write that explicit form. A file with <w:evenAndOddHeaders w:val="false"/> therefore took the even-page header variant on every second page, which is empty — the header and footer vanished from page 2 onwards.
This commit is contained in:
1 parent
f48e5508a8
commit
3a68a144cb
2 files changed
+58
-3
No files matched your search
@@ -776,7 +776,7 @@ function blockAttrs(ppr: Element | null, kind: BlockKind, headingLevel: number |
|
||||
|
||||
if (kind === 'body') {
|
||||
const pb = fc(ppr, 'pageBreakBefore');
|
||||
if (pb && wVal(pb) !== 'false' && wVal(pb) !== '0') attrs.breakBefore = 'page';
|
||||
if (pb && onOff(pb)) attrs.breakBefore = 'page';
|
||||
}
|
||||
|
||||
// Paragraph background ("colored field", w:shd) + per-side borders ("rule line", w:pBdr).
|
||||
@@ -1461,7 +1461,8 @@ function docHasEvenOddHeaders(files: Record<string, Uint8Array>): boolean {
|
||||
const bytes = files['word/settings.xml'];
|
||||
if (!bytes) return false;
|
||||
try {
|
||||
return !!parseXml(strFromU8(bytes)).getElementsByTagNameNS(W, 'evenAndOddHeaders')[0];
|
||||
const el = parseXml(strFromU8(bytes)).getElementsByTagNameNS(W, 'evenAndOddHeaders')[0];
|
||||
return !!el && onOff(el);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -1489,7 +1490,8 @@ function parseSectPr(sect: Element | null, ctx: Ctx, oddEven = false): {
|
||||
: null;
|
||||
|
||||
// Different first page: w:titlePg turns on the "first"-type refs for page 1.
|
||||
const titlePg = !!fc(sect, 'titlePg');
|
||||
const titlePgEl = fc(sect, 'titlePg');
|
||||
const titlePg = !!titlePgEl && onOff(titlePgEl);
|
||||
const refId = (type: string, variant: 'default' | 'first' | 'even' = 'default') => {
|
||||
const ref = fcAll(sect, `${type}Reference`).find((r) => (r.getAttributeNS(W, 'type') ?? 'default') === variant);
|
||||
return ref?.getAttributeNS(R, 'id') ?? null;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// OOXML toggles are ST_OnOff: the element being present does not mean "on", since
|
||||
// w:val="false"/"0"/"off" turns it off. Word and the docx lib both write the explicit
|
||||
// false form. jsdom (vitest `environment`) supplies the global DOMParser.
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { zipSync, strToU8 } from 'fflate';
|
||||
import { importDocx } from '../../src/lib/import/docx';
|
||||
|
||||
const CT = `<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Default Extension="xml" ContentType="application/xml"/>
|
||||
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
||||
</Types>`;
|
||||
|
||||
const RELS = `<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
||||
</Relationships>`;
|
||||
|
||||
const DOC_RELS = `<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rH" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/>
|
||||
</Relationships>`;
|
||||
|
||||
const W = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"';
|
||||
const HEADER = `<?xml version="1.0"?><w:hdr ${W}><w:p><w:r><w:t>Running head</w:t></w:r></w:p></w:hdr>`;
|
||||
const DOCUMENT = `<?xml version="1.0"?><w:document ${W} xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||
<w:body><w:p><w:r><w:t>Body</w:t></w:r></w:p>
|
||||
<w:sectPr><w:headerReference w:type="default" r:id="rH"/><w:titlePg w:val="false"/></w:sectPr>
|
||||
</w:body></w:document>`;
|
||||
|
||||
const settings = (val: string) =>
|
||||
`<?xml version="1.0"?><w:settings ${W}><w:evenAndOddHeaders w:val="${val}"/></w:settings>`;
|
||||
|
||||
const build = (settingsXml: string) => zipSync({
|
||||
'[Content_Types].xml': strToU8(CT),
|
||||
'_rels/.rels': strToU8(RELS),
|
||||
'word/_rels/document.xml.rels': strToU8(DOC_RELS),
|
||||
'word/document.xml': strToU8(DOCUMENT),
|
||||
'word/header1.xml': strToU8(HEADER),
|
||||
'word/settings.xml': strToU8(settingsXml),
|
||||
});
|
||||
|
||||
describe('DOCX on/off toggles', () => {
|
||||
it('reads an explicit false as off', () => {
|
||||
const r = importDocx(build(settings('false')));
|
||||
expect(r.header).not.toBeNull();
|
||||
// Both off, so the one header repeats on every page instead of odd/even variants.
|
||||
expect(r.differentOddEven).toBe(false);
|
||||
expect(r.differentFirstPage).toBe(false);
|
||||
});
|
||||
|
||||
it('still reads a bare element as on', () => {
|
||||
const r = importDocx(build(`<?xml version="1.0"?><w:settings ${W}><w:evenAndOddHeaders/></w:settings>`));
|
||||
expect(r.differentOddEven).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user