Skip to content

Table Extraction

Every call to convert() populates result.tables with one entry per <table> found in the input. Each entry has both a rendered Markdown string and a structured cell grid, so you can embed the Markdown in downstream documents or walk the grid for analysis without re-parsing.

Table extraction runs on every call. There is no opt-in flag. Use JSON output with --no-content in the CLI, or ignore result.content in library code, when you only want table data.

result.tables is a Vec<TableData> (or the equivalent list in each binding).

Field Type Description
grid TableGrid Structured cell grid.
markdown String The Markdown rendering of this table, identical to what appears in result.content.
Field Type Description
rows u32 Number of rows in the table.
cols u32 Number of columns in the table.
cells Vec<GridCell> Flat list of cells. May be shorter than rows * cols when cells span multiple rows or columns.
Field Type Description
content String Cell text. Inline formatting is flattened to plain text.
row u32 0-indexed row position.
col u32 0-indexed column position.
row_span u32 How many rows the cell occupies. Defaults to 1.
col_span u32 How many columns the cell occupies. Defaults to 1.
is_header bool true for <th>, false for <td>.
use html_to_markdown_rs::{ConversionOptions, convert};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let html = r#"
<table>
<tr><th>Name</th><th>Age</th></tr>
<tr><td>Alice</td><td>30</td></tr>
<tr><td>Bob</td><td>25</td></tr>
</table>
"#;
// `tables` is collected alongside the document tree, so it must be enabled.
let options = ConversionOptions::builder().include_document_structure(true).build();
let result = convert(html, Some(options))?;
for table in &result.tables {
for cell in &table.grid.cells {
let kind = if cell.is_header { "Header" } else { "Cell" };
println!(" {kind} (r{},c{}): {}", cell.row, cell.col, cell.content);
}
}
Ok(())
}

The Markdown in TableData.markdown is the same Markdown that appears inline inside result.content. The grid exists for code that needs cell-level access: headers vs body rows, span detection, or programmatic lookup by (row, col).

If the input has no tables, result.tables is an empty list. If the output format is "plain", tables are still extracted and their grids are still populated; only the rendering in result.content changes.

A cell with row_span > 1 or col_span > 1 appears once in cells, positioned at its top-left coordinates. Downstream code that iterates by (row, col) should respect the span or use the spans to reconstruct a dense grid.

Found a bug or mistake on this page?

If something here is wrong or out of date, open an issue on GitHub or contribute a fix via pull request.