Excel, CSV, Parquet

Excel workbooks, CSV/TSV tables and Apache Parquet files all end up as the same thing in BDF: a sheet View, the endless-plane layout BDF uses for tabular data (spec.md §4.1). CSV/TSV and Parquet have no drawing code of their own — converter/csv and converter/parquet build a plain grid of values and hand it to converter/xlsx's grid layout and drawing code, the same code that lays out and draws worksheet cells. That means a CSV or Parquet file looks and behaves exactly like an Excel sheet in the viewer: a bold, frozen header row, gridlines and frozen panes, and cells you select the way you'd select cells in a spreadsheet (drag, Shift, row/column headers, arrow keys). They copy out as tab-separated values and an HTML table rather than as a picture.

Try it

Drop a file on the viewer, or try a sample: Excel workbook, a sheet with equation objects, Shift_JIS TSV file, or Parquet table.

Excel (.xlsx)

converter/xlsx reads the OPC zip package (.xlsx, .xlsm, .xltx, .xltm) and turns each worksheet into a sheet View. Cell layout, formatting and number-format rendering are all done by the converter itself. BDF has no relayout, so this is the one place that interpretation happens; the sheet is then drawn into Tiles. Cell values are the ones stored in the file (a formula's cached result); formulas themselves are not recalculated.

What's read:

Not read: right-to-left sheets (drawn left to right), pivot-table styles (pivot values are drawn as ordinary cells), sparklines, form controls, legacy VML-format shapes, images stored inside a cell, and the old binary .xls format.

Options (-param, from bdf generate -h):

Option Values Default
-param hidden= true hidden sheets excluded
-param views= sheet, print, both sheet (the sheets as Excel shows them); print is the printed pages, both both
-param paper= a3, a4, a5, b4, b5, letter, legal … the paper of sheets whose page setup names none: a4 for a Japanese workbook, else letter

-hidden (shorthand for -param hidden=true) includes hidden sheets; macro and dialog sheets are always skipped, with a warning. See design.md §3.6 for the internals, and design.md §6 for how a sheet's cells are split into the Tiles a sheet View is drawn from.

Printed pages (the print View)

With -param views=print, a sheet becomes a flow View (print1, print2 …) of the pages Excel prints. For a workbook made as a form, this is what it is meant to look like. With both it follows the sheet View.

A page has header, footer and body layers, and its body rectangle is inside the margins, so a viewer's continuous mode stacks the bodies alone. What several pages show alike, such as the print titles, is one Object stored once.

Excel rounds row heights and column widths to the pixels of the printer when it prints, so an automatic page break can be a row away from Excel's; a manual break is where it is. Pictures in headers and footers (&G), the ways of printing error values, and printed notes are not handled.

Filling a workbook as a template

A workbook whose cells hold tags such as {{name}} is a template of a form that data fills (bdf fill, xlsx.Fill); the result is a document of the printed pages. See Making forms.

Equation objects

An equation placed on a sheet with Insert → Equation (Office Math inside a text box or a shape) is laid out from its Office Math in a14:m, as in PowerPoint. A sample is converter/xlsx/testdata/math.xlsx. Cell formulas (=SUM(A1:A3)) are something else: the converter shows the results saved in the file. See formulas in Word, HTML and Markdown for the layout.

CSV and TSV

converter/csv reads a text table and produces one sheet View that looks like the file opened in Excel — because it's drawn by Excel's own converter. This package's job is entirely about the things a CSV file doesn't say explicitly: the character encoding, the dialect, and whether there's a header row. Every guess can be overridden with -param, and the guesses actually made are reported in the conversion summary (Result, the CLI's last line).

What's guessed:

Values are shown exactly as written (unlike Excel, which normalizes 1.50 to 1.5); numbers with leading zeros (007, postal codes) stay strings rather than becoming numbers, and are aligned right the same as real numbers. Column widths follow the content, and cells with line breaks wrap and grow taller. Because it's drawn by the Excel converter, a detected header row is bold, frozen, and marked as a column header for the accessible text layer — exactly as in an Excel sheet with frozen panes.

Options (-param, from bdf generate -h):

Option Values Default
-param charset= utf-8, shift_jis, euc-jp, euc-kr, gb18030, big5, windows-1252 … detected
-param delimiter= comma, tab, semicolon, pipe or another character detected
-param quote= double, single, none detected
-param header= true, false guessed
-param table= a built-in Excel table style, e.g. TableStyleMedium2 none

See design.md §3.10 for the internals.

Apache Parquet

converter/parquet reads a Parquet file — the column-oriented format pandas, Polars, DuckDB and Spark write, and the format Hugging Face datasets ship in — and produces one sheet View of the table, again drawn by converter/xlsx. Unlike CSV, there's nothing to guess: Parquet's schema carries the column types, so the only real question is how to print a value.

What's read:

Values are printed the way data tools print them: decimals in full precision, timestamps in UTC with as many fractional-second digits as the column needs, UUIDs and intervals in their standard notations, lists/maps/structs as JSON, and geometry columns (GEOMETRY, GEOGRAPHY, GeoParquet's WKB columns) as WKT. Column names are bold and frozen in row 1, the same as a detected CSV header row, with a gray row of each column's Parquet type (int64, decimal(10, 2), timestamp[ms, UTC], list<string> and so on) frozen under them in row 2.

By default only the first 10,000 rows are shown (-param rows= picks a different number, or all up to the sheet limit of 1,048,574 rows). Reading itself is fast — about 0.6 seconds for a million rows and eight columns — but laying the sheet's cells out is not: about 3 seconds and 690 MB for 100,000 rows, about 45 seconds and 2.7 GB for a million, which is why the default keeps small. There's also a 4-million-cell cap regardless of rows=, so a table with many columns gets fewer rows; columns are capped at the sheet limit of 16,384, same as Excel and CSV.

Not read: Parquet Modular Encryption (a file with an encrypted footer is detected but not opened), Brotli-compressed columns in the browser build, LZO, and the still-experimental ALP encoding.

Options (-param, from bdf generate -h):

Option Values Default
-param rows= a number, or all 10000
-param types= true, false true
-param table= a built-in Excel table style, e.g. TableStyleMedium2 none

See design.md §3.26 for the internals.