Background
src/html.rs contains two DOM-walking helpers that are byte-for-byte identical
apart from the tag they match and the vector they populate:
collect_tables (src/html.rs:106-113) — recurses collecting <table> nodes.
collect_rows (src/html.rs:116-123) — recurses collecting <tr> nodes.
fn collect_tables(handle: &Handle, tables: &mut Vec<Handle>) {
if is_element(handle, "table") { tables.push(handle.clone()); }
for child in handle.children.borrow().iter() { collect_tables(child, tables); }
}
Problem
The recursion is duplicated, so any change to traversal (depth limits, ordering,
borrow handling) must be made twice. The neighbouring is_table_cell
(src/html.rs:103) implies a third potential walk over <td>/<th>.
Objective
Replace both functions with a single canonical walker and route all callers
through it.
- Add
fn collect_elements(handle: &Handle, tag: &str, out: &mut Vec<Handle>),
or a predicate-taking variant
fn collect_matching(handle: &Handle, pred: impl Fn(&Handle) -> bool, out: &mut Vec<Handle>)
so cell collection can reuse it too.
- Express
collect_tables/collect_rows as thin call-throughs or remove them
in favour of direct calls.
Required approach
- One canonical walker. Exactly one recursive collection function; remove
the duplicated bodies and route every caller through it.
- Document it. Add an entry to
docs/developers-guide.md (Internal API
reference / HTML parser section) naming the canonical walker, its contract
(pre-order traversal, clones matching handles), and the rule that new DOM
collection must reuse it.
- Property tests. Add
proptest coverage over generated small HTML
fragments asserting invariants such as: count returned by collect_elements
equals the number of matching tags in the source; nesting order is
deterministic; non-matching documents yield an empty vector.
- Model checking (if applicable). The DOM input is unbounded; bounded model
checking is not a good fit — record this rationale in the PR rather than
adding a Kani harness.
- Snapshot assertions. Add
insta snapshot tests pinning the
HTML-to-Markdown table conversion output for representative nested-table and
multi-row fixtures, so traversal regressions surface as diffs.
All existing tests must pass; run the full make commit gate before merging.
References
Background
src/html.rscontains two DOM-walking helpers that are byte-for-byte identicalapart from the tag they match and the vector they populate:
collect_tables(src/html.rs:106-113) — recurses collecting<table>nodes.collect_rows(src/html.rs:116-123) — recurses collecting<tr>nodes.Problem
The recursion is duplicated, so any change to traversal (depth limits, ordering,
borrow handling) must be made twice. The neighbouring
is_table_cell(
src/html.rs:103) implies a third potential walk over<td>/<th>.Objective
Replace both functions with a single canonical walker and route all callers
through it.
fn collect_elements(handle: &Handle, tag: &str, out: &mut Vec<Handle>),or a predicate-taking variant
fn collect_matching(handle: &Handle, pred: impl Fn(&Handle) -> bool, out: &mut Vec<Handle>)so cell collection can reuse it too.
collect_tables/collect_rowsas thin call-throughs or remove themin favour of direct calls.
Required approach
the duplicated bodies and route every caller through it.
docs/developers-guide.md(Internal APIreference / HTML parser section) naming the canonical walker, its contract
(pre-order traversal, clones matching handles), and the rule that new DOM
collection must reuse it.
proptestcoverage over generated small HTMLfragments asserting invariants such as: count returned by
collect_elementsequals the number of matching tags in the source; nesting order is
deterministic; non-matching documents yield an empty vector.
checking is not a good fit — record this rationale in the PR rather than
adding a Kani harness.
instasnapshot tests pinning theHTML-to-Markdown table conversion output for representative nested-table and
multi-row fixtures, so traversal regressions surface as diffs.
All existing tests must pass; run the full
makecommit gate before merging.References