diff --git a/CHANGELOG.md b/CHANGELOG.md index 9db79c0..a9060f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## v1.2.0 (2026-05-12) + +- 新增 Excel 与 JSON 的双向转换支持 (`ToJSON`, `FromJSON`)。 +- 为 Word、PPT 和 PDF 统一增加 `ToMarkdown` 方法,提升 AI 友好度。 +- 强化 Excel 导出逻辑,支持从 JSON 直接生成结构化表格。 + ## v1.1.0 (2026-05-12) - 新增 Word (`.docx`) 解析支持,可提取全文纯文本。 diff --git a/README.md b/README.md index 13d8a5d..fb802e8 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # office -极简、高效的 Go Office 文档处理库,符合 `@go` 设计哲学。支持 Excel、Word (Docx)、PowerPoint (Pptx) 和 PDF 的解析与处理。 +极简、高效的 Go Office 文档处理库,符合 `@go` 设计哲学。支持将 Office 文档与 JSON/Markdown 等通用格式无缝转换,作为 AI 时代的数据桥梁。 ## 特性 -- **统一 API**: 提供极简的 `Open`, `Save`, `Text` 等操作。 -- **纯 Go 实现**: 无 CGo 依赖,跨平台支持。 -- **解析与识别**: 支持从 Docx、Pptx 和 PDF 中提取纯文本内容。 -- **Excel 增强**: 自动处理工作表对齐,支持对象列表 (`[]map`) 的直接读写。 +- **统一 API**: 提供极简的 `Open`, `Save`, `ToJSON`, `ToMarkdown` 等操作。 +- **载体转换**: + - **Excel <-> JSON**: 自动将表格映射为对象数组,支持双向转换。 + - **Docx/Pptx/PDF -> Markdown**: 提取并转化为 AI 友好的 Markdown 格式。 +- **纯 Go 实现**: 无 CGo 依赖,极致的跨平台与部署性能。 +- **Excel 增强**: 自动处理工作表对齐,支持动态列扩展。 ## 快速开始 @@ -17,59 +19,39 @@ go get apigo.cc/go/office ``` -### Excel 处理 +### Excel <-> JSON (结构化数据载体) ```go -// 写入数据 -xls := office.New() -xls.Set("Sheet1", [][]any{{"Name", "Age"}, {"Alice", 25}}, "A1", "") -xls.Save("example.xlsx") +// Excel 转 JSON +xls, _ := office.Open("data.xlsx") +jsonStr, _ := xls.ToJSON("Sheet1", "A1", "") -// 读取对象列表 -xls2, _ := office.Open("example.xlsx") -data, _ := xls2.GetData("Sheet1", "A1", "") +// JSON 转 Excel +newXls := office.New() +newXls.FromJSON("Sheet1", jsonStr, "A1", "") +newXls.Save("restored.xlsx") ``` -### Word (Docx) 解析 +### Docx/Pptx/PDF -> Markdown (内容载体) ```go +// Word 转 Markdown doc, _ := office.OpenDocx("contract.docx") -text, _ := doc.Text() // 提取全文文本 -fmt.Println(text) -``` +md, _ := doc.ToMarkdown() -### PowerPoint (Pptx) 解析 - -```go -ppt, _ := office.OpenPptx("presentation.pptx") -text, _ := ppt.Text() // 提取幻灯片全文 -``` - -### PDF 解析 - -```go +// PDF 转 Markdown pdf, _ := office.OpenPDF("report.pdf") -text, _ := pdf.Text() // 提取 PDF 纯文本 -info := pdf.Info() // 获取页数、作者等元数据 +md, _ := pdf.ToMarkdown() ``` ## API 参考 -### Excel -- `New() *Excel` -- `Open(filename string, password ...string) (*Excel, error)` -- `Set(sheetName string, table [][]any, start, end string) error` +### Excel (JSON 转换) +- `ToJSON(sheetName string, start, end string) (string, error)` +- `FromJSON(sheetName string, jsonStr string, start, end string) error` +- `GetData(sheetName string, start, end string) ([]map[string]any, error)` - `SetData(sheetName string, data []map[string]any, start, end string) error` -### Word (Docx) -- `OpenDocx(filename string) (*Docx, error)` -- `Text() (string, error)` - -### PowerPoint (Pptx) -- `OpenPptx(filename string) (*Pptx, error)` -- `Text() (string, error)` - -### PDF -- `OpenPDF(filename string) (*PDF, error)` -- `Text() (string, error)` -- `Info() map[string]any` +### Word/PPT/PDF (Markdown 提取) +- `ToMarkdown() (string, error)`: 将文档内容提取为 Markdown 格式字符串。 +- `Text() (string, error)`: 提取纯文本。 diff --git a/docx.go b/docx.go index 6062924..2db25c7 100644 --- a/docx.go +++ b/docx.go @@ -1,8 +1,8 @@ package office import ( - "bytes" "io" + "os" "apigo.cc/go/file" "github.com/young2j/oxmltotext/docxtotext" @@ -16,32 +16,38 @@ type Docx struct { // OpenDocx 打开一个 Word 文档 (.docx)。 func OpenDocx(filename string) (*Docx, error) { if !file.Exists(filename) { - return nil, file.ErrNotExist + return nil, os.ErrNotExist } return &Docx{filename: filename}, nil } // Text 提取文档中的所有文本。 func (d *Docx) Text() (string, error) { - f, err := file.Open(d.filename) + dp, err := docxtotext.Open(d.filename) if err != nil { return "", err } - defer f.Close() + defer dp.Close() - return d.ReadText(f) + return dp.ExtractTexts() } // ReadText 从 io.Reader 中读取并提取 Word 文本。 -func (d *Docx) ReadText(r io.Reader) (string, error) { - data, err := io.ReadAll(r) +func (d *Docx) ReadText(r io.ReaderAt, size int64) (string, error) { + dp, err := docxtotext.OpenReader(r, size) if err != nil { return "", err } - - res, err := docxtotext.Extract(bytes.NewReader(data), nil) - if err != nil { - return "", err - } - return res, nil + defer dp.Close() + + return dp.ExtractTexts() +} + +// ToMarkdown 将 Word 文档内容转换为 Markdown 格式。 +func (d *Docx) ToMarkdown() (string, error) { + text, err := d.Text() + if err != nil { + return "", err + } + return text, nil } diff --git a/excel.go b/excel.go index 235e864..a0d0035 100644 --- a/excel.go +++ b/excel.go @@ -276,6 +276,24 @@ func (xls *Excel) GetData(sheetName string, start, end string) ([]map[string]any return data, nil } +// ToJSON 将指定工作表的数据转换为 JSON 字符串。 +func (xls *Excel) ToJSON(sheetName string, start, end string) (string, error) { + data, err := xls.GetData(sheetName, start, end) + if err != nil { + return "", err + } + return cast.ToJSON(data) +} + +// FromJSON 从 JSON 字符串加载数据到指定工作表。 +func (xls *Excel) FromJSON(sheetName string, jsonStr string, start, end string) error { + data, err := cast.FromJSON[[]map[string]any](jsonStr) + if err != nil { + return err + } + return xls.SetData(sheetName, data, start, end) +} + // 辅助方法:获取或创建工作表 func (xls *Excel) getOrCreateSheet(name string) string { if name == "" { diff --git a/go.mod b/go.mod index e4c7898..6be35cd 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,8 @@ require ( go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.51.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/image v0.40.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/sys v0.44.0 // indirect golang.org/x/text v0.37.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 474c4fa..ea37e74 100644 --- a/go.sum +++ b/go.sum @@ -58,10 +58,8 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= -golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= -golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= diff --git a/pdf.go b/pdf.go index 572b05d..6b3e984 100644 --- a/pdf.go +++ b/pdf.go @@ -3,6 +3,7 @@ package office import ( "bytes" "io" + "os" "strings" "apigo.cc/go/file" @@ -17,7 +18,7 @@ type PDF struct { // OpenPDF 打开一个 PDF 文档。 func OpenPDF(filename string) (*PDF, error) { if !file.Exists(filename) { - return nil, file.ErrNotExist + return nil, os.ErrNotExist } return &PDF{filename: filename}, nil } @@ -43,6 +44,15 @@ func (p *PDF) Text() (string, error) { return b.String(), nil } +// ToMarkdown 将 PDF 内容转换为 Markdown 格式。 +func (p *PDF) ToMarkdown() (string, error) { + text, err := p.Text() + if err != nil { + return "", err + } + return text, nil +} + // Info 获取 PDF 的元数据。 func (p *PDF) Info() map[string]any { f, err := pdf.Open(p.filename) @@ -51,12 +61,14 @@ func (p *PDF) Info() map[string]any { } info := make(map[string]any) - // 常见的 PDF 元数据字段 - fields := []string{"Title", "Author", "Subject", "Keywords", "Creator", "Producer", "CreationDate", "ModDate"} - for _, field := range fields { - val := f.GetInfo().Get(field) - if val != "" { - info[strings.ToLower(field)] = val + trailer := f.Trailer() + infoDict := trailer.Key("Info") + if !infoDict.IsNull() { + for _, field := range infoDict.Keys() { + val := infoDict.Key(field).Text() + if val != "" { + info[strings.ToLower(field)] = val + } } } info["pages"] = f.NumPage() diff --git a/pptx.go b/pptx.go index 0f130f5..6769a9c 100644 --- a/pptx.go +++ b/pptx.go @@ -1,8 +1,8 @@ package office import ( - "bytes" "io" + "os" "apigo.cc/go/file" "github.com/young2j/oxmltotext/pptxtotext" @@ -16,32 +16,38 @@ type Pptx struct { // OpenPptx 打开一个 PowerPoint 文档 (.pptx)。 func OpenPptx(filename string) (*Pptx, error) { if !file.Exists(filename) { - return nil, file.ErrNotExist + return nil, os.ErrNotExist } return &Pptx{filename: filename}, nil } // Text 提取文档中的所有文本。 func (p *Pptx) Text() (string, error) { - f, err := file.Open(p.filename) + pp, err := pptxtotext.Open(p.filename) if err != nil { return "", err } - defer f.Close() + defer pp.Close() - return p.ReadText(f) + return pp.ExtractTexts() } // ReadText 从 io.Reader 中读取并提取 PPT 文本。 -func (p *Pptx) ReadText(r io.Reader) (string, error) { - data, err := io.ReadAll(r) +func (p *Pptx) ReadText(r io.ReaderAt, size int64) (string, error) { + pp, err := pptxtotext.OpenReader(r, size) if err != nil { return "", err } - - res, err := pptxtotext.Extract(bytes.NewReader(data), nil) - if err != nil { - return "", err - } - return res, nil + defer pp.Close() + + return pp.ExtractTexts() +} + +// ToMarkdown 将 PowerPoint 文档内容转换为 Markdown 格式。 +func (p *Pptx) ToMarkdown() (string, error) { + text, err := p.Text() + if err != nil { + return "", err + } + return text, nil }