I built the XLSX-to-Markdown converter expecting a spreadsheet to be a grid. Rows and columns. A nice Markdown table, maybe a JSON array beside it, done. That mental model lasted about three real-world workbooks.
The first breakage: a financial model with a title row merged across A1:F1, a units row in row 2, actual headers in row 3, and totals three rows above the data they summarized. My converter faithfully emitted a Markdown table whose header row was "" | "" | "" | ..." and whose first data row was "Q1 Revenue (USD, thousands)". Technically correct. Practically garbage for any agent downstream trying to parse it.
The second breakage was worse. A logistics sheet had two logical tables stacked vertically with a blank row between them, and a third table side by side in columns H onward. One sheet, three datasets, zero metadata saying so. My flattening logic happily interleaved them into one franken-table where column A of "table 1" sat next to column H of "table 3".
The fix wasn't smarter parsing — it was admitting that spreadsheets are laid out for human eyes, not machines. I added a header-detection pass that scores candidate header rows by type-consistency (a real header row has the same type in every column below it), and a table-boundary detector that splits on fully-empty rows and on column-blocks that never share a row. The JSON output gained a tables array instead of pretending there was one table. Multi-sheet workbooks stayed flattened in sheet order, but each sheet now reports its own detected table count.
The lesson that stuck: when your input format was designed for presentation, your converter's job isn't extraction — it's interpretation. The agent consuming your output needs to know where the human author intended the seams to be, because the file itself won't tell you. I ended up shipping all of this as the XLSX-to-Markdown API at https://x402.freeq.one/tools/xlsx_to_markdown.html — it returns both a best-effort Markdown table and a structured JSON array, and the JSON now carries per-table boundaries so downstream agents can decide for themselves what "the table" means.
Top comments (0)