Files
ragflow/internal/deepdoc/parser/pdf/ocr_merge_test.go
Jack d12fd3b79d feat: parser pages range and parse type validation for dataset/document (#17293)
## Summary

Adds page-range parsing support to the Go-native pipeline path and
introduces strict `parse_type` validation for both dataset and document
update endpoints.

## What changed

### Pages range parsing
- **`internal/utility/pdf_pages.go`** — `NormalizePDFPages`: normalizes
raw page ranges (list of `[from,to]` 1-indexed inclusive ranges) into
sorted, merged, deduplicated `[][]int`. Invalid ranges are dropped.
- **`internal/ingestion/pipeline/pdf_pages.go`** —
`NormalizeParserConfigPages`: walks any parser_config map and normalizes
`"pages"` values under every component → filetype setup, so the
persisted config always carries clean, merged ranges.
- **`internal/deepdoc/parser/pdf/parser.go`** — integrates
`resolvePagesToProcess` to filter parsed PDF pages by the configured
ranges.
- Pipeline integration (parser pages):
`internal/parser/parser/pdf_parser_common.go`, `chunk_process.go`, plus
associated e2e and unit tests.

### Parse type validation (shared logic)
- **`internal/service/parser_mode.go`** (new) — `ValidateParseTypeMode`:
shared function that validates `parse_type` (1=BuiltIn/parser_id,
2=Pipeline/pipeline_id) and ensures the corresponding field is present.
Used by both dataset and document update endpoints.
- **`internal/service/dataset/crud.go`** / `update.go` — replaces inline
`isPipelineMode`/`isBuiltinMode` computation with the shared
`service.ValidateParseTypeMode`.
- **`internal/service/document/document_dataset_update.go`** — adds
strict `parse_type` validation in `validateDatasetDocumentUpdate`,
simplifies the reparse logic to a two-way switch (isBuiltin/isPipeline)
now that parse_type is always valid.
- **`internal/service/document/document.go`** — adds `ParseType` field
to `UpdateDatasetDocumentRequest`.
- **`internal/service/document/document_dataset_update.go`** —
`updateDocumentParserConfig` fallback path when DSL loading fails.
- **`internal/service/parser_mode_test.go`** (new) — test coverage for
nil, invalid, and missing-field scenarios.

### Frontend
- **`web/src/interfaces/request/document.ts`** — adds `parseType` to
`IChangeParserRequestBody`.
- **`web/src/hooks/use-document-request.ts`** —
`useSetDocumentPipelineParser` sends `parse_type` in the PATCH payload.
- **`web/src/pages/dataset/dataset/use-change-document-parser.ts`** —
Go/Python branching for the document parser config dialog.
-
**`web/src/components/document-pipeline-dialog/use-document-pipeline-form.ts`**
— `buildSubmitData` returns `parseType` (bugfix: was dropped from the
return value).

### Test changes
- **Removed**: 2 tests that verified the old "mutually exclusive" error
(replaced by `ValidateParseTypeMode` coverage).
- **Modified**: 6 tests across document and dataset packages to include
`ParseType` in request structs.
- **Added**: new e2e tests for pages parsing (`pages_e2e_test.go`,
`pdf_parser_pages_e2e_test.go`) and unit tests for `NormalizePDFPages`,
`NormalizeParserConfigPages`, `resolvePagesToProcess`.

## Backward compatibility
- The `parse_type` field is **required** when `parser_id` or
`pipeline_id` is sent. This changes the contract for both dataset and
document PATCH endpoints, but aligns the Go backend with the existing
frontend behavior (the frontend already sends `parse_type`). Callers
that omit `parse_type` when updating parser/pipeline selections will
receive a clear error message.
- Existing callers that only update fields like `name`, `enabled`, or
`meta_fields` are unaffected.
- Test updates ensure all known call sites are compliant.
2026-07-23 19:57:27 +08:00

89 lines
2.3 KiB
Go

//go:build cgo && manual
package pdf
import (
"context"
"image/png"
"os"
"ragflow/internal/common"
inf "ragflow/internal/deepdoc/parser/pdf/inference"
pdftype "ragflow/internal/deepdoc/parser/pdf/type"
util "ragflow/internal/deepdoc/parser/pdf/util"
"strings"
"testing"
)
// TestOCR_mergeChars_RealScanned tests ocrMergeChars on a real scanned
// medical PDF where pdf_oxide extracts noise (RASB@PS, random symbols)
// instead of real text. This validates that detect+merge+recognize
// produces readable English from the scan.
func TestOCR_mergeChars_RealScanned(t *testing.T) {
url := common.GetEnv(common.EnvDeepDocURL)
if url == "" {
t.Skip("DEEPDOC_URL not set")
}
dd, err := inf.NewClient(url)
if err != nil {
t.Fatal(err)
}
if !dd.Health() {
t.Fatal("DeepDoc not available")
}
pdfPath := "testdata/real_pdfs/1例3个月喉噗合并先天性心脏病患儿气管插管的麻醉护理.pdf"
data, err := os.ReadFile(pdfPath)
if err != nil {
t.Fatal(err)
}
eng, err := NewEngine(data)
if err != nil {
t.Fatal(err)
}
chars, err := eng.ExtractChars(0)
if err != nil {
t.Fatal(err)
}
t.Logf("pdf_oxide Chars: %d", len(chars))
var sample strings.Builder
for i, c := range chars {
if i >= 200 {
break
}
sample.WriteString(c.Text)
}
t.Logf("pdf_oxide sample: %q", sample.String())
t.Logf("isScanNoise: %v", util.IsScanNoise(sample.String()))
t.Logf("isGarbledPage: %v", util.IsGarbledPage(chars))
img, err := eng.RenderPageImage(0, 72*3)
if err != nil {
t.Fatal(err)
}
p := NewParser(pdftype.ParserConfig{})
boxes := p.ocrMergeChars(context.Background(), img, chars, dd, 0)
t.Logf("ocrMergeChars boxes: %d", len(boxes))
for i, b := range boxes {
// Save go render for comparison
f, _ := os.Create("/tmp/_go_render.png")
png.Encode(f, img)
f.Close()
t.Logf("Go render saved: %v -> /tmp/_go_render.png", img.Bounds())
end := min(120, len(b.Text))
t.Logf(" [%d] (%.0f,%.0f)-(%.0f,%.0f) text=%q",
i, b.X0, b.Top, b.X1, b.Bottom, b.Text[:end])
}
scanBoxes := p.ocrDetectAndRecognize(context.Background(), img, dd, 0, "scan page")
t.Logf("ocrScanPage boxes (no chars): %d", len(scanBoxes))
for i, b := range scanBoxes {
end := min(120, len(b.Text))
t.Logf(" [%d] (%.0f,%.0f)-(%.0f,%.0f) text=%q",
i, b.X0, b.Top, b.X1, b.Bottom, b.Text[:end])
}
}