fix(libreoffice): don't force MS Word 97 infilter on .wps (#585)

* fix(libreoffice): don't force MS Word 97 infilter on .wps

.wps is Microsoft Works, not MS Word 97/.doc. Forcing that infilter
makes soffice reject a genuine Works document with "source file could
not be loaded", even though the same file converts fine when no
--infilter is passed at all (LibreOffice auto-detects it correctly).

Fixes #582

* test: lock in wps export direction; document why null covers both sides

Per review: the filter map feeds both --infilter and the --convert-to
suffix, so wps: null also affects exporting TO wps. That is deliberate
and behavior-preserving: LibreOffice has no Works export filter (its
MS_Works filter is import-only), and bare `--convert-to wps` falls back
to LibreOffice's default export filter for the extension - "MS Word 97",
the exact filter this map pinned before, so export output is unchanged.
Adds a regression test asserting the docx -> wps invocation.

---------

Co-authored-by: tr3lane <182920672+tr3lane@users.noreply.github.com>
This commit is contained in:
tr3lane
2026-07-29 04:01:02 -05:00
committed by GitHub
parent 393441faa1
commit dc61643912
2 changed files with 58 additions and 2 deletions
+15 -2
View File
@@ -77,7 +77,7 @@ export const properties = {
type FileCategories = "text" | "calc";
const filters: Record<FileCategories, Record<string, string>> = {
const filters: Record<FileCategories, Record<string, string | null>> = {
text: {
"602": "T602Document",
abw: "AbiWord",
@@ -113,7 +113,20 @@ const filters: Record<FileCategories, Record<string, string>> = {
txt: "Text",
wn: "WriteNow",
wpd: "WordPerfect",
wps: "MS Word 97",
// .wps is Microsoft Works, not MS Word 97/.doc - forcing the "MS Word 97"
// filter makes soffice reject a genuine Works document with "source file
// could not be loaded", even though it converts the same file fine with
// no --infilter at all (LibreOffice auto-detects it correctly).
//
// null is deliberate for BOTH directions here, not just the import side:
// this map feeds both --infilter (import) and the --convert-to suffix
// (export). On import, null lets LibreOffice auto-detect - its Works
// import filter (MS_Works, libwps-backed) is import-only, so it can only
// be reached via auto-detection anyway. On export, LibreOffice has no
// Works export filter at all; bare `--convert-to wps` falls back to its
// default export filter for the extension, which is "MS Word 97" - the
// exact filter this map pinned before, so export output is unchanged.
wps: null,
wpt: "MS Word 97 Vorlage",
wri: "MS_Write",
xhtml: "HTML (StarWriter)",
+43
View File
@@ -102,6 +102,49 @@ test("uses only infilter when convertTo has no out filter (e.g., docx -> pdf)",
expect(args.slice(-2)).toEqual(["out", "in.docx"]);
});
test("does not force an infilter for wps (Microsoft Works, not MS Word 97)", async () => {
// Regression test for https://github.com/C4illin/ConvertX/issues/582 -
// forcing --infilter="MS Word 97" on a genuine .wps file makes soffice
// reject it with "source file could not be loaded". No forced infilter
// lets LibreOffice auto-detect the real format instead.
await convert("in.wps", "wps", "docx", "out/out.docx", undefined, mockExecFile);
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
expect(args).toEqual([
"--headless",
"--convert-to",
"docx:MS Word 2007 XML",
"--outdir",
"out",
"in.wps",
]);
expect(args.some((a) => a.startsWith("--infilter"))).toBe(false);
});
test("does not force an outfilter for wps as an export target either", async () => {
// wps shares one filter-map entry for both directions (see the comment in
// libreoffice.ts) - docx's own infilter is still emitted (that describes
// the real source file), but no --convert-to wps:<filter> suffix is
// forced. Verified against a real soffice: this exact invocation succeeds,
// with LibreOffice's default export filter for .wps ("MS Word 97" - the
// same filter the map pinned before this change, so output is unchanged).
// LibreOffice has no Works export filter, so no suffix could do better.
await convert("in.docx", "docx", "wps", "out/out.wps", undefined, mockExecFile);
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
expect(args).toEqual([
"--headless",
"--infilter=MS Word 2007 XML",
"--convert-to",
"wps",
"--outdir",
"out",
"in.docx",
]);
});
test("strips leading './' from outdir", async () => {
await convert("in.txt", "txt", "docx", "./out/out.docx", undefined, mockExecFile);