Summary
Spreadsheet cells carrying a numFmt are rendered from their stored value with the display format dropped. A cell that reads 7.5% in Excel converts to 0.075, and one that reads $1,234.50 converts to 1234.5.
The percent case is the damaging one: it is not a cosmetic loss but a value that is wrong by two orders of magnitude for any consumer reading the Markdown, which for this library is usually an LLM. A conversion-rate column of 0.075 reads as 0.075%, not 7.5%, and nothing in the output signals that a format was dropped.
Dates are handled correctly, which makes the behaviour inconsistent: mm/dd/yyyy renders as 2026-03-15, so a reader could reasonably assume other display formats survive too.
Reproduction
Tested with firecrawl-anydoc==0.1.6 and openpyxl==3.1.5.
import datetime
from pathlib import Path
import anydoc
from openpyxl import Workbook
path = Path("number-formats.xlsx")
workbook = Workbook()
sheet = workbook.active
sheet.title = "Formats"
sheet.append(["kind", "value"])
sheet.append(["percent", 0.075])
sheet.append(["currency", 1234.5])
sheet.append(["date", datetime.date(2026, 3, 15)])
sheet["B2"].number_format = "0.0%"
sheet["B3"].number_format = '"$"#,##0.00'
sheet["B4"].number_format = "mm/dd/yyyy"
workbook.save(path)
print(anydoc.to_markdown(str(path)))
Actual
| kind | value |
| --- | --- |
| percent | 0.075 |
| currency | 1234.5 |
| date | 2026-03-15 |
Expected
The displayed value, as the workbook shows it:
| kind | value |
| --- | --- |
| percent | 7.5% |
| currency | $1,234.50 |
| date | 2026-03-15 |
Cause
format_data in src/formats/sheet/mod.rs switches on calamine's Data enum alone. Dates survive because calamine resolves them to Data::DateTime itself; percent and currency cells arrive as Data::Float and fall through to format_float, which never consults the cell's number format.
calamine 0.36.1 does not appear to expose the numFmt code on its public API, so this likely needs xl/styles.xml read directly — resolving cellXfs[xfId].numFmtId against the built-in format IDs plus any numFmts overrides — which the existing package/XML layer already supports. The built-in IDs alone (9 and 10 for percent, 5-8 and 37-44 for currency and accounting) would cover most real workbooks.
If full format emulation is out of scope, even handling percent alone would remove the wrong-by-100x failure, which is the part that silently corrupts meaning rather than just dropping presentation.
Context
Found while benchmarking anydoc for accuracy across a mixed corpus of real business documents. Text recall was 100% on every file tested and the library was 15-40x faster than the alternative I compared against, so this stood out as the one case where output was confidently wrong rather than merely lossy.
Summary
Spreadsheet cells carrying a
numFmtare rendered from their stored value with the display format dropped. A cell that reads7.5%in Excel converts to0.075, and one that reads$1,234.50converts to1234.5.The percent case is the damaging one: it is not a cosmetic loss but a value that is wrong by two orders of magnitude for any consumer reading the Markdown, which for this library is usually an LLM. A conversion-rate column of
0.075reads as 0.075%, not 7.5%, and nothing in the output signals that a format was dropped.Dates are handled correctly, which makes the behaviour inconsistent:
mm/dd/yyyyrenders as2026-03-15, so a reader could reasonably assume other display formats survive too.Reproduction
Tested with
firecrawl-anydoc==0.1.6andopenpyxl==3.1.5.Actual
Expected
The displayed value, as the workbook shows it:
Cause
format_datainsrc/formats/sheet/mod.rsswitches on calamine'sDataenum alone. Dates survive because calamine resolves them toData::DateTimeitself; percent and currency cells arrive asData::Floatand fall through toformat_float, which never consults the cell's number format.calamine 0.36.1 does not appear to expose the
numFmtcode on its public API, so this likely needsxl/styles.xmlread directly — resolvingcellXfs[xfId].numFmtIdagainst the built-in format IDs plus anynumFmtsoverrides — which the existing package/XML layer already supports. The built-in IDs alone (9 and 10 for percent, 5-8 and 37-44 for currency and accounting) would cover most real workbooks.If full format emulation is out of scope, even handling percent alone would remove the wrong-by-100x failure, which is the part that silently corrupts meaning rather than just dropping presentation.
Context
Found while benchmarking anydoc for accuracy across a mixed corpus of real business documents. Text recall was 100% on every file tested and the library was 15-40x faster than the alternative I compared against, so this stood out as the one case where output was confidently wrong rather than merely lossy.