Skip to content

refactor(html): unify duplicated DOM-walk recursion into one canonical walker #358

Description

@lodyai

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

  1. One canonical walker. Exactly one recursive collection function; remove
    the duplicated bodies and route every caller through it.
  2. 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.
  3. 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.
  4. 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.
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions