Files
ragflow/internal/parser/tableutil
Lukas 4969b7b41a fix(parser): place a table's footer rows after its body (#21283)
### Summary

`tableutil.ParseTableHTML` builds table rows in source order. HTML 4
required `<tfoot>` to come before `<tbody>`, and browsers draw it below
the body wherever it stands. Tables written in that order are still
common: generated reports, documentation tools, older CMS exports. For
those tables the footer row landed between the header and the data:

```html
<table>
  <thead><tr><th>Item</th><th>Qty</th></tr></thead>
  <tfoot><tr><th>Total</th><th>3</th></tr></tfoot>
  <tbody><tr><td>pen</td><td>1</td></tr><tr><td>ink</td><td>2</td></tr></tbody>
</table>
```

```text
main:         Item | Qty / Total | 3 / pen | 1 / ink | 2    HeaderRows = 2
this branch:  Item | Qty / pen | 1 / ink | 2 / Total | 3    HeaderRows = 1
```

A footer of `<th>` cells was also counted as a second header row,
because the header count is the leading run of `<th>` rows.

### What changed

`ParseTableHTML` collects the rows of a `<tfoot>` apart and appends them
after the body. Their `<th>` flags go with them, so they no longer
extend the header. Tables without a `<tfoot>` are unchanged. Nested
tables are still folded into their cell by `cellText`, as before.

The HTML parser's table items, the chunker and the deepdoc converter all
build their grids through `ParseTableHTML` / `BuildTableData`, so they
all get the rendered order.

### Testing

`TestParseTableHTMLFooterRowsComeLast` uses the table above and expects
the rows in rendered order with one header row. It fails on `main` with
`HeaderRows = 2`.

```
CGO_ENABLED=0 go test ./internal/parser/...                    # ok (chunk, parser, tableutil)
TIKTOKEN_CACHE_DIR=… go test ./internal/ingestion/component/chunker/ ./internal/service/file/
# chunker: only TestPaddleOCRHighlightsThroughPipeline fails, identically on main (local environment)
go vet ./internal/parser/tableutil/ && gofmt -l internal/parser/   # clean
```
2026-10-22 15:15:07 +08:00
..