Files
ofdgo/ofdgo_reader.go
T
2026-07-08 14:51:39 +08:00

359 lines
9.4 KiB
Go

// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ofdgo
import (
"archive/zip"
"encoding/xml"
"fmt"
"io"
"path"
"strings"
)
// Reader OFD文件阅读器
type Reader struct {
Path string
Zip *zip.Reader
Closer io.Closer
OFD *OFD
RootDir string
ResMap map[string]string
fontCache map[string]*Font
drawParamCache map[string]*DrawParam
compositeGraphicUnitCache map[string]*CompositeGraphicUnit
doc *Document
Stamps map[string][]Stamp
Annots map[string][]Annotation
fileIndex map[string]*zip.File
fileIndexFold map[string]*zip.File
}
// Close 关闭阅读器
// 返回: error 错误信息
func (r *Reader) Close() error {
if r.Closer != nil {
return r.Closer.Close()
}
return nil
}
// initRoot 读取根节点信息
// 返回: error 错误信息
func (r *Reader) initRoot() error {
r.fileIndex = make(map[string]*zip.File)
r.fileIndexFold = make(map[string]*zip.File)
for _, f := range r.Zip.File {
name := cleanPackagePath(f.Name)
r.fileIndex[name] = f
fold := strings.ToLower(name)
if _, ok := r.fileIndexFold[fold]; !ok {
r.fileIndexFold[fold] = f
}
}
data, err := r.readFile("OFD.xml")
if err != nil {
return fmt.Errorf("failed to read ofd.xml: %w", err)
}
var ofd OFD
if err := xml.Unmarshal(data, &ofd); err != nil {
return fmt.Errorf("failed to unmarshal ofd.xml: %w", err)
}
r.OFD = &ofd
r.ResMap = make(map[string]string)
r.fontCache = make(map[string]*Font)
r.drawParamCache = make(map[string]*DrawParam)
r.compositeGraphicUnitCache = make(map[string]*CompositeGraphicUnit)
return nil
}
// readFile 读取压缩包内的文件
// 入参: name 文件名
// 返回: []byte 文件内容, error 错误信息
func (r *Reader) readFile(name string) ([]byte, error) {
name = cleanPackagePath(name)
if f, ok := r.packageFile(name); ok {
return readZipFile(f)
}
return nil, fmt.Errorf("file not found: %s", name)
}
// openFile 打开压缩包内的文件流
// 入参: name 文件名
// 返回: io.ReadCloser 文件流, error 错误信息
func (r *Reader) openFile(name string) (io.ReadCloser, error) {
name = cleanPackagePath(name)
if f, ok := r.packageFile(name); ok {
return f.Open()
}
return nil, fmt.Errorf("file not found: %s", name)
}
// cleanPackagePath 清理包内文件路径
// 入参: name 文件路径
// 返回: string 清理后的文件路径
func cleanPackagePath(name string) string {
name = strings.ReplaceAll(name, "\\", "/")
name = strings.TrimPrefix(name, "/")
return path.Clean(name)
}
// packageFile 获取包内文件
// 入参: name 文件路径
// 返回: *zip.File 压缩包文件, bool 是否存在
func (r *Reader) packageFile(name string) (*zip.File, bool) {
if f, ok := r.fileIndex[name]; ok {
return f, true
}
if f, ok := r.fileIndexFold[strings.ToLower(name)]; ok {
return f, true
}
return nil, false
}
// readZipFile 读取zip文件内容
// 入参: f zip文件对象
// 返回: []byte 文件内容, error 错误信息
func readZipFile(f *zip.File) ([]byte, error) {
rc, err := f.Open()
if err != nil {
return nil, err
}
defer rc.Close()
return io.ReadAll(rc)
}
// Doc 获取主文档结构
// 返回: *Document 文档结构, error 错误信息
func (r *Reader) Doc() (*Document, error) {
if r.doc != nil {
return r.doc, nil
}
if r.OFD == nil || len(r.OFD.DocBody) == 0 {
return nil, fmt.Errorf("no docbody found")
}
docAttr := r.OFD.DocBody[0]
docRootPath := docAttr.DocRoot
r.RootDir = path.Dir(docRootPath)
data, err := r.readFile(docRootPath)
if err != nil {
return nil, err
}
var doc Document
if err := xml.Unmarshal(data, &doc); err != nil {
return nil, fmt.Errorf("failed to unmarshal document.xml: %w", err)
}
if doc.Signatures == "" {
doc.Signatures = docAttr.Signatures
}
if doc.CommonData.DocumentRes != "" {
r.loadRes(doc.CommonData.DocumentRes)
}
if doc.CommonData.PublicRes != "" {
r.loadRes(doc.CommonData.PublicRes)
}
r.doc = &doc
_ = r.parseAnnotations(&doc)
_ = r.parseSignatures(&doc)
return r.doc, nil
}
// loadRes 加载资源文件
// 入参: resPath 资源路径
func (r *Reader) loadRes(resPath string) {
if resPath == "" {
return
}
fullPath := path.Join(r.RootDir, resPath)
data, err := r.readFile(fullPath)
if err != nil {
return
}
var res Res
if err := xml.Unmarshal(data, &res); err != nil {
return
}
baseLoc := res.BaseLoc
for _, mm := range res.MultiMedias.MultiMedia {
if mm.MediaFile != "" {
if finalPath := resolveResourcePath(resPath, baseLoc, mm.MediaFile); finalPath != "" {
r.ResMap[mm.ID] = finalPath
}
}
}
for i := range res.Fonts.Font {
f := &res.Fonts.Font[i]
if f.FontFile != "" {
f.FontFile = resolveResourcePath(resPath, baseLoc, f.FontFile)
}
r.fontCache[f.ID] = f
}
for i := range res.DrawParams.DrawParam {
dp := &res.DrawParams.DrawParam[i]
r.drawParamCache[dp.ID] = dp
}
for i := range res.CompositeGraphicUnits.CompositeGraphicUnit {
cgu := &res.CompositeGraphicUnits.CompositeGraphicUnit[i]
r.compositeGraphicUnitCache[cgu.ID] = cgu
}
}
// resolveResourcePath 解析资源文件路径
// 入参: resPath 资源文件路径, baseLoc 资源基准路径, filePath 文件路径
// 返回: string 资源文件路径
func resolveResourcePath(resPath, baseLoc, filePath string) string {
p := strings.TrimSpace(filePath)
if p == "" {
return ""
}
p = strings.ReplaceAll(p, "\\", "/")
if strings.HasPrefix(p, "/") {
return strings.TrimPrefix(path.Clean(p), "/")
}
dir := path.Dir(resPath)
if baseLoc != "" {
if dir != baseLoc {
dir = path.Join(dir, baseLoc)
}
}
return path.Join(dir, p)
}
// PageContent 获取页面内容
// 入参: page 页面对象
// 返回: *PageContent 页面内容, error 错误信息
func (r *Reader) PageContent(page Page) (*PageContent, error) {
fullPath := path.Join(r.RootDir, page.BaseLoc)
data, err := r.readFile(fullPath)
if err != nil {
return nil, err
}
var content PageContent
if err := xml.Unmarshal(data, &content); err != nil {
return nil, fmt.Errorf("failed to unmarshal page content: %w", err)
}
content.ID = page.ID
return &content, nil
}
// ResPath 获取资源的完整路径
// 入参: resLink 资源链接
// 返回: string 完整路径
func (r *Reader) ResPath(resLink string) string {
if resLink == "" {
return ""
}
resLink = strings.ReplaceAll(resLink, "\\", "/")
resLink = strings.TrimSpace(resLink)
if resLink == "" {
return ""
}
resLink = strings.TrimPrefix(resLink, "/")
resLink = path.Clean(resLink)
rootDir := strings.TrimPrefix(strings.ReplaceAll(r.RootDir, "\\", "/"), "/")
if rootDir != "" && (resLink == rootDir || strings.HasPrefix(resLink, rootDir+"/")) {
return resLink
}
return path.Join(r.RootDir, resLink)
}
// ResData 获取资源文件数据
// 入参: resLink 资源链接
// 返回: []byte 资源数据, error 错误信息
func (r *Reader) ResData(resLink string) ([]byte, error) {
fullPath := r.ResPath(resLink)
return r.readFile(fullPath)
}
// Version 获取OFD版本号
// 返回: string 版本号
func (r *Reader) Version() string {
if r.OFD == nil {
return ""
}
return r.OFD.Version
}
// DocType 获取文档类型
// 返回: string 文档类型
func (r *Reader) DocType() string {
if r.OFD == nil {
return ""
}
return r.OFD.DocType
}
// DocInfo 获取文档元数据
// 返回: *DocInfo 元数据, error 错误信息
func (r *Reader) DocInfo() (*DocInfo, error) {
if r.OFD == nil || len(r.OFD.DocBody) == 0 {
return nil, fmt.Errorf("no docbody found")
}
return &r.OFD.DocBody[0].DocInfo, nil
}
// Permissions 获取文档权限信息
// 返回: *Permissions 权限信息, error 错误信息
func (r *Reader) Permissions() (*Permissions, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
return &doc.Permissions, nil
}
// Outlines 获取文档大纲
// 返回: []OutlineElem 大纲列表, error 错误信息
func (r *Reader) Outlines() ([]OutlineElem, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
return doc.Outlines.OutlineElem, nil
}
// Attachments 获取附件列表
// 返回: []Attachment 附件列表, error 错误信息
func (r *Reader) Attachments() ([]Attachment, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
return doc.Attachments.Attachment, nil
}
// CustomDatas 获取自定义数据
// 返回: []CustomData 自定义数据列表, error 错误信息
func (r *Reader) CustomDatas() ([]CustomData, error) {
info, err := r.DocInfo()
if err != nil {
return nil, err
}
if info.CustomDatas == nil {
return nil, nil
}
return info.CustomDatas.CustomData, nil
}
// Extensions 获取分列扩展项
// 返回: []Extension 扩展项列表, error 错误信息
func (r *Reader) Extensions() ([]Extension, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
return doc.Extensions.Extension, nil
}