From 643fbc5db19fb0e13a17dc23351a04f7c2951ab4 Mon Sep 17 00:00:00 2001 From: Mikk Andresen Date: Mon, 26 Jan 2026 13:20:45 +0000 Subject: [PATCH] XLSX to Markdown conversion to work with XLSX files with empty Sheets. --- .gitignore | 5 ++++- src/converters/xlsx.ts | 9 ++++++--- test/__files/test_empty_sheets.xlsx | Bin 0 -> 14850 bytes test/index.test.ts | 13 +++++++++++++ 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100755 test/__files/test_empty_sheets.xlsx diff --git a/.gitignore b/.gitignore index e46cfd3..4d37773 100644 --- a/.gitignore +++ b/.gitignore @@ -95,4 +95,7 @@ Thumbs.db # Ignore built ts files __tests__/runner/* -lib/**/* \ No newline at end of file +lib/**/* + +# IntelliJ IDE project files +.idea/ \ No newline at end of file diff --git a/src/converters/xlsx.ts b/src/converters/xlsx.ts index 5484524..b844e78 100644 --- a/src/converters/xlsx.ts +++ b/src/converters/xlsx.ts @@ -24,9 +24,12 @@ export class XlsxConverter extends HtmlConverter { let mdContent = ""; for (const sheetName of workbook.SheetNames) { - mdContent += `## ${sheetName}\n`; - let htmlContent = XLSX.utils.sheet_to_html(workbook.Sheets[sheetName]); - mdContent += (await this._convert(htmlContent))?.markdown.trim() + "\n\n"; + const sheet = workbook.Sheets[sheetName]; + if (sheet["!ref"]) { + mdContent += `## ${sheetName}\n`; + let htmlContent = XLSX.utils.sheet_to_html(sheet); + mdContent += (await this._convert(htmlContent))?.markdown.trim() + "\n\n"; + } } return { title: workbook?.Props?.Title || "Untitled", markdown: mdContent, text_content: mdContent }; } catch (e) { diff --git a/test/__files/test_empty_sheets.xlsx b/test/__files/test_empty_sheets.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..eb456a46f095f6a720849016b2c8b778039205db GIT binary patch literal 14850 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 CD(30{ literal 0 HcmV?d00001 diff --git a/test/index.test.ts b/test/index.test.ts index ada5df8..92818d4 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -183,6 +183,19 @@ describe("MarkItDown Tests", () => { expect(textContent).toContain(testString); } }); + + it("should convert .xlsx having empty sheets to markdown", async () => { + const markitdown = new MarkItDown(); + const result = await markitdown.convert( + path.join(__dirname, "__files/test_empty_sheets.xlsx") + ); + expect(result).not.toBeNull(); + expect(result).not.toBeUndefined(); + const textContent = result?.markdown.replace("\\", ""); + for (const testString of XLSX_TEST_STRINGS) { + expect(textContent).toContain(testString); + } + }); }); describe("WAV Converter", () => {