// Package textextract 从输入材料(txt/md/json/yaml/docx)提取纯文本, // 并注入页码锚点便于引用(机制语义对齐 Agent-System internal/textextract, // 为独立底座做的纯标准库精简版)。 package textextract import ( "archive/zip" "bytes" "encoding/xml" "fmt" "io" "path/filepath" "regexp" "strings" ) // ExtractTextWithAnchors 提取文本并注入页码锚点。 func ExtractTextWithAnchors(filename string, data []byte) (string, error) { ext := strings.ToLower(filepath.Ext(filename)) switch ext { case ".txt", ".md", ".json", ".yaml", ".yml", ".csv", ".log": return InjectPageAnchors(string(data)), nil case ".docx": text, err := docxText(data) if err != nil { return "", fmt.Errorf("docx %s: %w", filename, err) } return InjectPageAnchors(text), nil default: // 二进制降级:提取可见 ASCII/UTF-8 片段。 return InjectPageAnchors(extractVisibleStrings(data)), nil } } // InjectPageAnchors 每 pageSizeAt 字符(或遇到 \f 换页符)注入 。 func InjectPageAnchors(s string) string { if s == "" { return s } var b strings.Builder page := 1 count := 0 fprintf := func() { fmt.Fprintf(&b, "\n\n\n", page) } fprintf() for _, r := range s { b.WriteRune(r) count++ if r == '\f' { page++ count = 0 fprintf() continue } if count >= 3000 { page++ count = 0 fprintf() } } return b.String() } var xmlTag = regexp.MustCompile(``) func docxText(data []byte) (string, error) { zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { return "", err } for _, f := range zr.File { if f.Name != "word/document.xml" { continue } rc, err := f.Open() if err != nil { return "", err } defer rc.Close() raw, err := io.ReadAll(rc) if err != nil { return "", err } // 段落结尾转换行,再剥离全部 XML 标签。 raw = xmlTag.ReplaceAll(raw, []byte("\n")) dec := xml.NewDecoder(bytes.NewReader(raw)) var b strings.Builder for { tok, err := dec.Token() if err == io.EOF { break } if err != nil { return "", err } if t, ok := tok.(xml.CharData); ok { b.Write(t) } } return strings.TrimSpace(b.String()), nil } return "", fmt.Errorf("word/document.xml not found") } // extractVisibleStrings 从二进制中提取可见可打印字符串(长度>=4)。 func extractVisibleStrings(data []byte) string { var out []string start := -1 for i, b := range data { printable := (b >= 0x20 && b < 0x7f) || b >= 0x80 if printable { if start < 0 { start = i } continue } if start >= 0 && i-start >= 4 { out = append(out, string(data[start:i])) } start = -1 } if start >= 0 && len(data)-start >= 4 { out = append(out, string(data[start:])) } return strings.Join(out, "\n") }