Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ yarn-debug.log*
yarn-error.log*

# local env files
.env
.env.local
.env.development.local
.env.test.local
Expand All @@ -50,3 +51,7 @@ package-lock.json
/Bruno
/tsconfig.tsbuildinfo
/public/generated.css


.specstory/
.cursorindexingignore
44 changes: 43 additions & 1 deletion src/converters/libreoffice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ export const properties = {
"xml",
"zabw",
],
impress: [
"ppt",
"pptx",
"pps",
"ppsx",
"pptm",
"pot",
"potx",
"potm",
"odp",
"otp",
"fodp",
"sxi",
],
},
to: {
text: [
Expand All @@ -72,10 +86,19 @@ export const properties = {
"xhtml",
"xml",
],
impress: [
"pdf",
"ppt",
"pptx",
"odp",
"otp",
"fodp",
"html",
],
},
};

type FileCategories = "text" | "calc";
type FileCategories = "text" | "calc" | "impress";

const filters: Record<FileCategories, Record<string, string>> = {
text: {
Expand Down Expand Up @@ -121,13 +144,31 @@ const filters: Record<FileCategories, Record<string, string>> = {
zabw: "AbiWord",
},
calc: {},
impress: {
ppt: "MS PowerPoint 97",
pptx: "Impress MS PowerPoint 2007 XML",
pps: "MS PowerPoint 97",
ppsx: "Impress MS PowerPoint 2007 XML",
pptm: "Impress MS PowerPoint 2007 XML VBA",
pot: "MS PowerPoint 97 Vorlage",
potx: "Impress MS PowerPoint 2007 XML Template",
potm: "Impress MS PowerPoint 2007 XML Template",
odp: "impress8",
otp: "impress8_template",
fodp: "OpenDocument Presentation Flat XML",
sxi: "StarOffice XML (Impress)",
pdf: "impress_pdf_Export",
html: "impress_html_Export",
},
};

const getFilters = (fileType: string, converto: string) => {
if (fileType in filters.text && converto in filters.text) {
return [filters.text[fileType], filters.text[converto]];
} else if (fileType in filters.calc && converto in filters.calc) {
return [filters.calc[fileType], filters.calc[converto]];
} else if (fileType in filters.impress && converto in filters.impress) {
return [filters.impress[fileType], filters.impress[converto]];
}
return [null, null];
};
Expand All @@ -145,6 +186,7 @@ export function convert(
// Build arguments array
const args: string[] = [];
args.push("--headless");
args.push("--invisible");
const [inFilter, outFilter] = getFilters(fileType, convertTo);

if (inFilter) {
Expand Down
71 changes: 69 additions & 2 deletions tests/converters/libreoffice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ test("invokes soffice with --headless and outdir derived from targetPath", async
expect(cmd).toBe("soffice");
expect(args).toEqual([
"--headless",
"--invisible",
`--infilter="MS Word 2007 XML"`,
"--convert-to",
"odt:writer8",
Expand All @@ -78,7 +79,7 @@ test("uses only outFilter when input has no filter (e.g., pdf -> txt)", async ()
const { args } = requireDefined(calls[0], "Expected at least one execFile call");

expect(args).not.toContainEqual(expect.stringMatching(/^--infilter=/));
expect(args).toEqual(["--headless", "--convert-to", "txt", "--outdir", "out", "in.pdf"]);
expect(args).toEqual(["--headless", "--invisible", "--convert-to", "txt", "--outdir", "out", "in.pdf"]);
});

test("uses only infilter when convertTo has no out filter (e.g., docx -> pdf)", async () => {
Expand All @@ -87,7 +88,7 @@ test("uses only infilter when convertTo has no out filter (e.g., docx -> pdf)",
const { args } = requireDefined(calls[0], "Expected at least one execFile call");

// If docx has an infilter, it should be present
expect(args).toEqual(["--headless", "--convert-to", "pdf", "--outdir", "out", "in.docx"]);
expect(args).toEqual(["--headless", "--invisible", "--convert-to", "pdf", "--outdir", "out", "in.docx"]);

const i = args.indexOf("--convert-to");
expect(i).toBeGreaterThanOrEqual(0);
Expand Down Expand Up @@ -159,3 +160,69 @@ test("logs stderr on exec error as well", async () => {
// The callback still provided stderr; your implementation logs it before settling
expect(errors).toContain("stderr: EPIPE");
});

// --- PowerPoint/Impress conversions ------------------------------------------
test("converts pptx to pdf with impress filters", async () => {
await convert("in.pptx", "pptx", "pdf", "out/out.pdf", undefined, mockExecFile);

const { cmd, args } = requireDefined(calls[0], "Expected at least one execFile call");
expect(cmd).toBe("soffice");
expect(args).toEqual([
"--headless",
"--invisible",
`--infilter="Impress MS PowerPoint 2007 XML"`,
"--convert-to",
"pdf:impress_pdf_Export",
"--outdir",
"out",
"in.pptx",
]);
});

test("converts ppt to pdf with impress filters", async () => {
await convert("in.ppt", "ppt", "pdf", "out/out.pdf", undefined, mockExecFile);

const { args } = requireDefined(calls[0], "Expected at least one execFile call");
expect(args).toEqual([
"--headless",
"--invisible",
`--infilter="MS PowerPoint 97"`,
"--convert-to",
"pdf:impress_pdf_Export",
"--outdir",
"out",
"in.ppt",
]);
});

test("converts pptx to odp with impress filters", async () => {
await convert("in.pptx", "pptx", "odp", "out/out.odp", undefined, mockExecFile);

const { args } = requireDefined(calls[0], "Expected at least one execFile call");
expect(args).toEqual([
"--headless",
"--invisible",
`--infilter="Impress MS PowerPoint 2007 XML"`,
"--convert-to",
"odp:impress8",
"--outdir",
"out",
"in.pptx",
]);
});

test("converts odp to pptx with impress filters", async () => {
await convert("in.odp", "odp", "pptx", "out/out.pptx", undefined, mockExecFile);

const { args } = requireDefined(calls[0], "Expected at least one execFile call");
expect(args).toEqual([
"--headless",
"--invisible",
`--infilter="impress8"`,
"--convert-to",
"pptx:Impress MS PowerPoint 2007 XML",
"--outdir",
"out",
"in.odp",
]);
});