## Summary
Tag files with `.xls` extension were accepted by the extension check in
`parseTagSourceByFilename` but always produced an empty tag index:
`parseXLSXTagSource` used only `excelize.OpenReader`, which rejects OLE
BIFF workbooks (`unsupported workbook file format`). This makes legacy
Excel 97-2003 tag files unusable while the docs claim `XLSX, XLS, CSV,
TXT` support.
This PR routes OLE-magic `.xls` bytes to a new BIFF8 reader
(`go-excel-reader`, already used by the document `XLSParser`), applying
the identical per-row rule as `.xlsx`.
## Related Issue
None (follow-up of #10602 / #10660 which fixed document-side `.xls`; tag
side was left behind).
## Changes
- `internal/ingestion/component/tag_source.go`
- Detect OLE compound-document magic and route to new
`parseBIFF8TagSource` (fallback to `excelize` for OOXML-renamed `.xls`).
- `parseBIFF8TagSource`: per-sheet, no-header, first two non-empty cells
form `(content, tags)`; single-cell rows accumulate; extra cells
ignored.
- Extract shared `accumulateTagCells` helper (removes xlsx/BIFF8 loop
duplication); simplify `tagCellString` (drop redundant `string` case).
- Fix `unsupported extension` error text to include `.xls`.
- `internal/ingestion/component/tag_biff8_test.go` (new):
`TestParseBIFF8TagSource` with an xlwt-generated BIFF8 fixture (3
examples incl. single-cell accumulation).
## Test Plan
- `bash build.sh --test ./internal/ingestion/component/ -run
'TestParseBIFF8TagSource|TestParseTagSourceByFilename$|TestParseXLSXTagSource'
-v` — all PASS.
- `bash build.sh --test ./internal/ingestion/component/` — full package
PASS (~16s).
- Real-file check: document `XLSParser` parses 4 genuine `.xls` fixtures
(`produced=true`); same bytes via tag path previously returned
`count=0`, now return 3 tag examples.
## Summary
The DSL no longer supports `output_format` selection.
`normalizeParserJSON` (in `internal/ingestion/component/parser.go`)
hardcodes `setup["output_format"] = "json"` for every parser setup, so
the `ParseResult.Text` field — populated only on the now-unreachable
text output path — is dead.
This change removes `ParseResult.Text` and every read/write site:
- `internal/parser/parser/parse_result.go`: drop the `Text` field and
update payload-family helpers (`isPayloadZero`, `payloadFamily`, etc.).
- `internal/parser/parser/email_parser.go` / `doc_parser.go`: drop the
text-output branches and `output_format:"text"` handling.
- `internal/ingestion/component/parser.go`: the normalization already
forces `json`; remove the text branch in `normalizeParserJSON` /
`buildParserOutputs`.
- `internal/service/file/file_content.go`: `parseResultText` no longer
reads `res.Text`; it falls back to `res.Markdown` for legacy
markdown-only payloads.
- `internal/service/document/document_artifact.go`: DOC preview now
reads preview text from the JSON items.
- `internal/parser/parser/pdf_result.go`: drop the dead `case
result.Text != ""` fallback in `NormalizeResult` (no PDF producer sets
`parser.ParseResult.Text`; confirmed by grep).
## Test plan
- Updated / removed obsolete text-path unit tests; added guard tests
(`text_field_removed_test.go`, `text_field_removed_doc_test.go`)
asserting the field is gone and JSON is the only output family.
- `bash build.sh --test ./internal/parser/parser/...
./internal/ingestion/component/... ./internal/service/file/...
./internal/service/document/...` — all green.
- Full `bash build.sh --test` regression — all green (no FAIL).
- `bash build.sh --go` builds both `ragflow-cli` and `ragflow_server`.
## Notes
`deepdoctype.ParseResult` (a different struct in
`internal/deepdoc/parser/type`) still has its own `Text` / `Sections`
fields; those are unrelated and intentionally untouched.
---------
Co-authored-by: CodeBuddy Code <noreply@codebuddy.ai>
### 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
```