diff --git a/src/tools/documents/__fixtures__/sample.doc b/src/tools/documents/__fixtures__/sample.doc new file mode 100644 index 0000000..643838d Binary files /dev/null and b/src/tools/documents/__fixtures__/sample.doc differ diff --git a/src/tools/documents/__fixtures__/table.doc b/src/tools/documents/__fixtures__/table.doc new file mode 100644 index 0000000..ddaba51 Binary files /dev/null and b/src/tools/documents/__fixtures__/table.doc differ diff --git a/src/tools/documents/doc.fixtures.test.ts b/src/tools/documents/doc.fixtures.test.ts new file mode 100644 index 0000000..5c6321e --- /dev/null +++ b/src/tools/documents/doc.fixtures.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { extractDocText } from './doc.lib'; + +// Real Word 97-2003 binary .doc files (generated with macOS `textutil`, neutral +// synthetic content) — regression coverage for the OLE + piece-table extractor. +const load = (name: string) => + new Uint8Array(readFileSync(resolve(process.cwd(), 'src/tools/documents/__fixtures__', name))); + +describe('extractDocText on real .doc fixtures', () => { + it('extracts plain and Unicode text from a simple .doc', () => { + const text = extractDocText(load('sample.doc')); + expect(text).toContain('Hello World'); + expect(text).toContain('This is a test document.'); + expect(text).toContain('Café résumé'); // accented characters + expect(text).toContain('90% done'); + expect(text).toContain('Line A'); + expect(text).toContain('Line B'); + }); + + it('extracts a table as rows (tab-separated cells) plus a bulleted list', () => { + const text = extractDocText(load('table.doc')); + expect(text).toContain('Quarterly Report'); + expect(text).toContain('Item\tQ1\tQ2'); + expect(text).toContain('Revenue\t100\t150'); + expect(text).toContain('Costs\t40\t55'); + expect(text).toContain('First bullet'); + expect(text).toContain('Closing line.'); + }); +}); diff --git a/src/tools/documents/doc.lib.ts b/src/tools/documents/doc.lib.ts index b90dc83..8b44dd6 100644 --- a/src/tools/documents/doc.lib.ts +++ b/src/tools/documents/doc.lib.ts @@ -106,12 +106,18 @@ function decodePieces(wd: Uint8Array, table: Uint8Array, pcdt: Pcdt, ccpText: nu return out; } -/** Tidy extracted text: collapse runs of blank lines and trailing spaces. */ +/** + * Tidy extracted text. Table cells are separated by single tab marks; a run of + * two or more marks (a cell mark plus the row-end mark) is a row boundary, so + * those become line breaks. Leading tabs and blank-line runs are trimmed. + */ export function cleanDocText(s: string): string { return s .replace(/\r\n?/g, '\n') - .replace(/[ \t]+\n/g, '\n') .replace(/\uFEFF/g, '') + .replace(/\t{2,}/g, '\n') // consecutive cell/row marks \u2192 new row + .replace(/[ \t]+\n/g, '\n') + .replace(/\n[ \t]+/g, '\n') // strip leading tabs/spaces on a line .replace(/\n{3,}/g, '\n\n') .trim(); }