fix: prefer aggregate OCR text

This commit is contained in:
2026-07-09 01:16:36 +08:00
parent 273af6a429
commit 57366ac08f
3 changed files with 197 additions and 36 deletions
+1
View File
@@ -349,3 +349,4 @@ export namespace main {
} }
} }
+133
View File
@@ -264,6 +264,9 @@ func parseOCRText(data []byte) (string, error) {
if msg := responseError(root); msg != "" { if msg := responseError(root); msg != "" {
return "", errors.New(msg) return "", errors.New(msg)
} }
if text := normalizeOCRText(extractOverallOCRText(root, "")); text != "" {
return text, nil
}
parts := collectOCRParts(root, "") parts := collectOCRParts(root, "")
if len(parts) == 0 { if len(parts) == 0 {
return "", fmt.Errorf("no text found") return "", fmt.Errorf("no text found")
@@ -312,6 +315,62 @@ func responseError(v any) string {
return "" return ""
} }
func extractOverallOCRText(v any, parentKey string) string {
switch value := v.(type) {
case string:
text := strings.TrimSpace(value)
if text == "" {
return ""
}
if looksLikeJSON(text) {
var nested any
if err := json.Unmarshal([]byte(text), &nested); err == nil {
if nestedText := extractOverallOCRText(nested, parentKey); nestedText != "" {
return nestedText
}
}
}
if isAggregateContainerKey(parentKey) {
return text
}
return ""
case map[string]any:
for _, key := range aggregateTextKeys() {
if child, ok := value[key]; ok {
if text := extractOverallOCRText(child, key); text != "" {
return text
}
}
}
for _, key := range responseContainerKeys() {
if child, ok := value[key]; ok {
if text := extractOverallOCRText(child, key); text != "" {
return text
}
}
}
keys := make([]string, 0, len(value))
for key := range value {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
if isAggregateTextKey(key) ||
isResponseContainerKey(key) ||
isBlockContainerKey(key) ||
isMetadataKey(key) {
continue
}
if text := extractOverallOCRText(value[key], key); text != "" {
return text
}
}
return ""
default:
return ""
}
}
func collectOCRParts(v any, parentKey string) []string { func collectOCRParts(v any, parentKey string) []string {
switch value := v.(type) { switch value := v.(type) {
case string: case string:
@@ -359,6 +418,36 @@ func collectOCRParts(v any, parentKey string) []string {
} }
} }
func aggregateTextKeys() []string {
return []string{
"content",
"Content",
"text",
"Text",
"full_text",
"FullText",
"fullText",
"plain_text",
"PlainText",
"plainText",
"recognized_text",
"RecognizedText",
"recognizedText",
"ocr_text",
"OCRText",
"ocrText",
}
}
func responseContainerKeys() []string {
return []string{
"Data",
"data",
"Result",
"result",
}
}
func priorityOCRKeys() []string { func priorityOCRKeys() []string {
return []string{ return []string{
"Data", "Data",
@@ -394,6 +483,38 @@ func priorityOCRKeys() []string {
} }
} }
func isAggregateTextKey(key string) bool {
for _, item := range aggregateTextKeys() {
if item == key {
return true
}
}
return false
}
func isAggregateContainerKey(key string) bool {
return isAggregateTextKey(key) || isResponseContainerKey(key)
}
func isResponseContainerKey(key string) bool {
for _, item := range responseContainerKeys() {
if item == key {
return true
}
}
return false
}
func isBlockContainerKey(key string) bool {
switch normalizeKey(key) {
case "wordsresult", "prismwordsinfo", "prismwords", "ocrinfos", "items",
"blocks", "regions", "words", "linetexts", "lines", "cells":
return true
default:
return false
}
}
func isPriorityOCRKey(key string) bool { func isPriorityOCRKey(key string) bool {
for _, item := range priorityOCRKeys() { for _, item := range priorityOCRKeys() {
if item == key { if item == key {
@@ -459,6 +580,18 @@ func dedupeNonEmpty(parts []string) []string {
return out return out
} }
func normalizeOCRText(text string) string {
lines := strings.Split(strings.TrimSpace(text), "\n")
out := make([]string, 0, len(lines))
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" {
out = append(out, line)
}
}
return strings.Join(out, "\n")
}
func looksLikeJSON(text string) bool { func looksLikeJSON(text string) bool {
return strings.HasPrefix(text, "{") || strings.HasPrefix(text, "[") return strings.HasPrefix(text, "{") || strings.HasPrefix(text, "[")
} }
@@ -142,6 +142,33 @@ func TestParseOCRTextSupportsWordsResult(t *testing.T) {
} }
} }
func TestParseOCRTextPrefersOverallContentOverBlocks(t *testing.T) {
text, err := parseOCRText([]byte(`{
"Data": "{\"content\":\"整体识别文本\",\"prism_wordsInfo\":[{\"word\":\"整体\"},{\"word\":\"识别\"},{\"word\":\"文本\"}]}"
}`))
if err != nil {
t.Fatalf("parse ocr text: %v", err)
}
if text != "整体识别文本" {
t.Fatalf("expected overall content only, got %q", text)
}
}
func TestParseOCRTextPrefersResultTextOverWordsResult(t *testing.T) {
text, err := parseOCRText([]byte(`{
"Result": {
"text": "hello world",
"words_result": [{"words":"hello"},{"words":"world"}]
}
}`))
if err != nil {
t.Fatalf("parse ocr text: %v", err)
}
if text != "hello world" {
t.Fatalf("expected result text only, got %q", text)
}
}
func TestParseOCRTextReturnsProviderError(t *testing.T) { func TestParseOCRTextReturnsProviderError(t *testing.T) {
_, err := parseOCRText([]byte(`{"ResponseMetadata":{"Error":{"Code":"BadRequest","Message":"bad image"}}}`)) _, err := parseOCRText([]byte(`{"ResponseMetadata":{"Error":{"Code":"BadRequest","Message":"bad image"}}}`))
if err == nil || !strings.Contains(err.Error(), "bad image") { if err == nil || !strings.Contains(err.Error(), "bad image") {