Files
ofdgo/ofdgo_reader.go
T
2026-10-05 12:49:51 +08:00

834 lines
24 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"
"bytes"
"encoding/xml"
"fmt"
"io"
"path"
"slices"
"strings"
)
// Reader OFD文件阅读器
// Reader及共享它的Renderer需串行调用
type Reader struct {
Path string
Zip *zip.Reader
Closer io.Closer
OFD *OFD
RootDir string
ResMap map[string]string
resourcesRead map[string]bool
resourceFiles map[string]string
imageCatalog []ImageInfo
mediaCache map[string]MultiMedia
fontCache map[string]*Font
fontResourcesRead bool
fontFaces map[string][]*FontFace
colorSpaceCache map[string]*ColorSpace
drawParamCache map[string]*DrawParam
compositeGraphicUnitCache map[string]*CompositeGraphicUnit
pageHeaderCache map[string]PageContent
doc *Document
relatedPending bool
documentIndex int
Stamps map[string][]Stamp
Annots map[string][]Annotation
annotationFiles map[string][]string
fileIndex map[string]*zip.File
fileIndexFold map[string]*zip.File
files map[string][]byte
fileNamesFold map[string]string
encryption *encryptionState
}
// Close 关闭阅读器持有的文件,重复调用不再关闭底层文件
// 返回: error 错误信息
func (r *Reader) Close() error {
if r.Closer != nil {
closer := r.Closer
r.Closer = nil
return closer.Close()
}
return nil
}
// DocumentCount 获取包内文档数量
// 返回: int 文档数量
func (r *Reader) DocumentCount() int {
if r.OFD == nil {
return 0
}
return len(r.OFD.DocBody)
}
// DocumentIndex 获取当前文档索引,从0开始
// 返回: int 文档索引
func (r *Reader) DocumentIndex() int {
return r.documentIndex
}
// DocumentPageCount 获取指定文档页数,不切换当前文档或加载资源、注释和签名
// 入参: index 文档索引,从0开始
// 返回: int 页数, error 错误信息
func (r *Reader) DocumentPageCount(index int) (int, error) {
if index < 0 || index >= r.DocumentCount() {
return 0, fmt.Errorf("document index %d out of range", index)
}
if index == r.documentIndex && r.doc != nil {
return len(r.doc.Pages.Page), nil
}
data, err := r.readFile(r.OFD.DocBody[index].DocRoot)
if err != nil {
return 0, err
}
var doc struct {
XMLName xml.Name `xml:"Document"`
Pages Pages `xml:"Pages"`
}
if err := xml.Unmarshal(data, &doc); err != nil {
return 0, fmt.Errorf("failed to unmarshal document.xml: %w", err)
}
return len(doc.Pages.Page), nil
}
// Document 创建指定文档的独立阅读器,不改变当前文档或混用资源缓存
// 返回的阅读器不关闭输入文件,使用期间不得关闭持有输入文件的原阅读器
// 入参: index 文档索引,从0开始
// 返回: *Reader 文档阅读器, error 错误信息
func (r *Reader) Document(index int) (*Reader, error) {
if index < 0 || index >= r.DocumentCount() {
return nil, fmt.Errorf("document index %d out of range", index)
}
next := &Reader{Path: r.Path, Zip: r.Zip, files: r.files, encryption: r.encryption, documentIndex: index}
if err := next.initRoot(); err != nil {
return nil, err
}
if _, err := next.Doc(); err != nil {
return nil, err
}
return next, nil
}
// Doc 获取当前文档结构
// 返回: *Document 文档结构, error 错误信息
func (r *Reader) Doc() (*Document, error) {
doc, err := r.docStructure()
if err != nil {
return nil, err
}
if r.relatedPending {
r.relatedPending = false
_ = r.parseAnnotations(doc)
_ = r.parseSignatures(doc)
}
return doc, nil
}
// docStructure 读取文档及资源索引,关联注解和签名由Doc按需解析
// 返回: *Document 文档结构, error 错误信息
func (r *Reader) docStructure() (*Document, error) {
if r.doc != nil {
return r.doc, nil
}
if r.OFD == nil || r.documentIndex < 0 || r.documentIndex >= len(r.OFD.DocBody) {
return nil, fmt.Errorf("no docbody found")
}
docAttr := r.OFD.DocBody[r.documentIndex]
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
}
for _, res := range doc.CommonData.DocumentRes {
r.loadRes(res)
}
for _, res := range doc.CommonData.PublicRes {
r.loadRes(res)
}
r.doc = &doc
r.relatedPending = true
return r.doc, nil
}
// PageContent 获取页面内容
// 每次返回独立的页面内容,修改后可传入Renderer重绘,不会写回原文件
// 入参: page 页面对象
// 返回: *PageContent 页面内容, error 错误信息
func (r *Reader) PageContent(page Page) (*PageContent, error) {
return r.readPageContent(page, false)
}
// PageCount 获取当前文档总页数
// 返回: int 页数, error 错误信息
func (r *Reader) PageCount() (int, error) {
doc, err := r.Doc()
if err != nil {
return 0, err
}
return len(doc.Pages.Page), nil
}
// PageContentByIndex 按当前文档页面顺序读取页面内容
// 入参: index 页面索引,从0开始
// 返回: *PageContent 页面内容, error 错误信息
func (r *Reader) PageContentByIndex(index int) (*PageContent, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
if index < 0 || index >= len(doc.Pages.Page) {
return nil, fmt.Errorf("page index %d out of range", index)
}
return r.PageContent(doc.Pages.Page[index])
}
// PageArea 读取页面区域,不解析页面图元
// 入参: page 页面对象
// 返回: PageArea 页面区域, error 错误信息
func (r *Reader) PageArea(page Page) (PageArea, error) {
content, err := r.readPageHeader(page)
if err != nil {
return PageArea{}, err
}
return r.resolvePageArea(content.Area, content.Template)
}
// ResPath 获取资源的完整路径
// 入参: resLink 资源链接
// 返回: string 完整路径
func (r *Reader) ResPath(resLink string) string {
if resLink == "" {
return ""
}
resLink = strings.ReplaceAll(resLink, "\\", "/")
resLink = strings.TrimSpace(resLink)
if resLink == "" {
return ""
}
if strings.HasPrefix(resLink, "/") {
return cleanPackagePath(resLink)
}
resLink = path.Clean(resLink)
rootDir := strings.TrimPrefix(strings.ReplaceAll(r.RootDir, "\\", "/"), "/")
if rootDir != "" && (resLink == rootDir || strings.HasPrefix(resLink, rootDir+"/")) {
return resLink
}
return cleanPackagePath(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 || r.documentIndex < 0 || r.documentIndex >= len(r.OFD.DocBody) {
return nil, fmt.Errorf("no docbody found")
}
return &r.OFD.DocBody[r.documentIndex].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
}
// Actions 获取文档动作
// 返回: []Action 动作列表, error 错误信息
func (r *Reader) Actions() ([]Action, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
return doc.Actions, nil
}
// Bookmarks 获取文档书签
// 返回: []Bookmark 书签列表, error 错误信息
func (r *Reader) Bookmarks() ([]Bookmark, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
return doc.Bookmarks.Bookmark, nil
}
// Attachments 获取独立的附件列表,修改返回值不影响阅读器缓存
// 返回: []Attachment 附件列表, error 错误信息
func (r *Reader) Attachments() ([]Attachment, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
if strings.TrimSpace(doc.Attachments.Path) != "" && doc.Attachments.Attachment == nil {
var attachments Attachments
partPath, err := r.readDocumentPart(doc.Attachments.Path, &attachments)
if err != nil {
return nil, err
}
for i := range attachments.Attachment {
attachment := &attachments.Attachment[i]
if fileLoc := resolveResourcePath(partPath, "", attachment.FileLoc); fileLoc != "" {
attachment.FileLoc = "/" + fileLoc
}
}
if attachments.Attachment == nil {
attachments.Attachment = []Attachment{}
}
doc.Attachments.Attachment = attachments.Attachment
}
attachments := slices.Clone(doc.Attachments.Attachment)
for i := range attachments {
if attachments[i].Size != nil {
size := *attachments[i].Size
attachments[i].Size = &size
}
}
return attachments, nil
}
// OpenAttachment 打开附件文件流,调用方负责关闭,完整读取后校验ZIP数据
// 入参: id 附件标识
// 返回: io.ReadCloser 附件文件流, error 错误信息
func (r *Reader) OpenAttachment(id string) (io.ReadCloser, error) {
attachments, err := r.Attachments()
if err != nil {
return nil, err
}
for _, attachment := range attachments {
if attachment.ID == id {
return r.openFile(r.ResPath(attachment.FileLoc))
}
}
return nil, fmt.Errorf("attachment not found: %s", id)
}
// AttachmentData 获取附件文件数据
// 入参: id 附件标识
// 返回: []byte 附件数据, error 错误信息
func (r *Reader) AttachmentData(id string) ([]byte, error) {
stream, err := r.OpenAttachment(id)
if err != nil {
return nil, err
}
defer stream.Close()
return io.ReadAll(stream)
}
// CustomTags 获取自定义标引
// 返回: []CustomTag 自定义标引列表, error 错误信息
func (r *Reader) CustomTags() ([]CustomTag, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
if strings.TrimSpace(doc.CustomTags.Path) != "" && doc.CustomTags.CustomTag == nil {
var customTags CustomTags
partPath, err := r.readDocumentPart(doc.CustomTags.Path, &customTags)
if err != nil {
return nil, err
}
for i := range customTags.CustomTag {
tag := &customTags.CustomTag[i]
tag.SchemaLoc = resolveResourcePath(partPath, "", tag.SchemaLoc)
tag.FileLoc = resolveResourcePath(partPath, "", tag.FileLoc)
}
if customTags.CustomTag == nil {
customTags.CustomTag = []CustomTag{}
}
doc.CustomTags.CustomTag = customTags.CustomTag
}
return doc.CustomTags.CustomTag, 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
}
if strings.TrimSpace(doc.Extensions.Path) != "" && doc.Extensions.Extension == nil {
var extensions Extensions
partPath, err := r.readDocumentPart(doc.Extensions.Path, &extensions)
if err != nil {
return nil, err
}
for i := range extensions.Extension {
extension := &extensions.Extension[i]
for j := range extension.ExtendData {
extension.ExtendData[j] = resolveResourcePath(partPath, "", extension.ExtendData[j])
}
}
if extensions.Extension == nil {
extensions.Extension = []Extension{}
}
doc.Extensions.Extension = extensions.Extension
}
return doc.Extensions.Extension, nil
}
// initRoot 读取根节点信息
// 返回: error 错误信息
func (r *Reader) initRoot() error {
if err := r.indexPackage(); err != nil {
return err
}
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.resourcesRead = make(map[string]bool)
r.mediaCache = make(map[string]MultiMedia)
r.resourceFiles = make(map[string]string)
r.fontCache = make(map[string]*Font)
r.fontFaces = make(map[string][]*FontFace)
r.colorSpaceCache = make(map[string]*ColorSpace)
r.drawParamCache = make(map[string]*DrawParam)
r.compositeGraphicUnitCache = make(map[string]*CompositeGraphicUnit)
r.pageHeaderCache = make(map[string]PageContent)
return nil
}
// indexPackage 建立包路径索引,拒绝会使显示与验签产生歧义的同名条目
// 返回: error 错误信息
func (r *Reader) indexPackage() error {
count := 0
if r.Zip != nil {
count = len(r.Zip.File)
}
r.fileIndex = make(map[string]*zip.File, count)
r.fileIndexFold = make(map[string]*zip.File, count)
r.fileNamesFold = make(map[string]string, count+len(r.files))
if r.Zip != nil {
for _, f := range r.Zip.File {
if f.FileInfo().IsDir() {
continue
}
name := cleanPackagePath(f.Name)
if !validPackagePath(name) {
return fmt.Errorf("invalid package path: %q", f.Name)
}
fold := strings.ToLower(name)
if previous, ok := r.fileIndexFold[fold]; ok {
return fmt.Errorf("ambiguous package paths: %q and %q", previous.Name, f.Name)
}
r.fileIndex[name] = f
r.fileIndexFold[fold] = f
r.fileNamesFold[fold] = name
}
}
for name := range r.files {
if name != cleanPackagePath(name) || !validPackagePath(name) {
return fmt.Errorf("invalid package path: %q", name)
}
fold := strings.ToLower(name)
if previous, ok := r.fileNamesFold[fold]; ok && previous != name {
return fmt.Errorf("ambiguous package paths: %q and %q", previous, name)
}
r.fileNamesFold[fold] = name
}
return nil
}
// validPackagePath 判断规范化路径是否位于包内
// 入参: name 规范化路径
// 返回: bool 是否有效
func validPackagePath(name string) bool {
return name != "." && name != ".." && !strings.HasPrefix(name, "../") && !strings.HasPrefix(name, "/") && !strings.ContainsAny(name, ":\x00")
}
// readFile 读取文档内的文件
// 入参: name 文件名
// 返回: []byte 文件内容, error 错误信息
func (r *Reader) readFile(name string) ([]byte, error) {
data, borrowed, err := r.readFileData(name)
if borrowed {
data = bytes.Clone(data)
}
return data, err
}
// readFileView 借用已加载文件或读取压缩文件,返回数据不得修改
// 入参: name 文件名
// 返回: []byte 只读文件内容, error 错误信息
func (r *Reader) readFileView(name string) ([]byte, error) {
data, _, err := r.readFileData(name)
return data, err
}
// readFileData 统一解析包内路径并区分缓存借用与独立读取
// 入参: name 文件名
// 返回: []byte 文件内容, bool 是否借用缓存, error 错误信息
func (r *Reader) readFileData(name string) ([]byte, bool, error) {
name = cleanPackagePath(name)
if actual, ok := r.fileNamesFold[strings.ToLower(name)]; ok {
name = actual
}
if data, ok := r.files[name]; ok {
return data, true, nil
}
if f, ok := r.packageFile(name); ok {
if data, ok := r.files[cleanPackagePath(f.Name)]; ok {
return data, true, nil
}
data, err := readZipFile(f)
return data, false, err
}
return nil, false, 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 actual, ok := r.fileNamesFold[strings.ToLower(name)]; ok {
name = actual
}
if data, ok := r.files[name]; ok {
return io.NopCloser(bytes.NewReader(data)), nil
}
if f, ok := r.packageFile(name); ok {
if data, ok := r.files[cleanPackagePath(f.Name)]; ok {
return io.NopCloser(bytes.NewReader(data)), nil
}
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)
}
// loadRes 加载资源文件
// 入参: resPath 资源路径
func (r *Reader) loadRes(resPath string) {
if resPath == "" {
return
}
fullPath := r.ResPath(resPath)
if r.resourcesRead[fullPath] {
return
}
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 i := range res.ColorSpaces.ColorSpace {
cs := &res.ColorSpaces.ColorSpace[i]
r.colorSpaceCache[cs.ID] = cs
r.resourceFiles[cs.ID] = fullPath
}
for _, mm := range res.MultiMedias.MultiMedia {
if mm.MediaFile != "" {
if finalPath := resolveResourcePath(resPath, baseLoc, mm.MediaFile); finalPath != "" {
mm.MediaFile = finalPath
if r.mediaCache == nil {
r.mediaCache = make(map[string]MultiMedia)
}
r.mediaCache[mm.ID] = mm
r.ResMap[mm.ID] = finalPath
r.resourceFiles[mm.ID] = fullPath
}
}
}
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
r.resourceFiles[f.ID] = fullPath
}
for i := range res.DrawParams.DrawParam {
dp := &res.DrawParams.DrawParam[i]
r.drawParamCache[dp.ID] = dp
r.resourceFiles[dp.ID] = fullPath
}
for i := range res.CompositeGraphicUnits.CompositeGraphicUnit {
cgu := &res.CompositeGraphicUnits.CompositeGraphicUnit[i]
r.compositeGraphicUnitCache[cgu.ID] = cgu
r.resourceFiles[cgu.ID] = fullPath
}
r.resourcesRead[fullPath] = true
}
// 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)
}
// readPageContent 读取页面,可跳过文字提取不使用的独立路径和图片
// 入参: page 页面对象, textOnly 是否仅用于文字提取
// 返回: *PageContent 页面内容, error 错误信息
func (r *Reader) readPageContent(page Page, textOnly bool) (*PageContent, error) {
fullPath := r.ResPath(page.BaseLoc)
data, err := r.readFile(fullPath)
if err != nil {
return nil, err
}
var content PageContent
if textOnly {
decoder := &textPageTokens{Decoder: xml.NewDecoder(bytes.NewReader(data))}
err = xml.NewTokenDecoder(decoder).Decode(&content)
} else {
err = xml.Unmarshal(data, &content)
}
if err != nil {
return nil, fmt.Errorf("failed to unmarshal page content: %w", err)
}
content.ID = page.ID
for _, res := range content.PageRes {
r.loadRes(resolveResourcePath(page.BaseLoc, "", res))
}
content.Area, err = r.resolvePageArea(content.Area, content.Template)
if err != nil {
return nil, err
}
return &content, nil
}
// loadPageResources 加载页面资源,不解析图元
// 入参: page 页面引用
func (r *Reader) loadPageResources(page Page) {
header, err := r.readPageHeader(page)
if err != nil {
return
}
for _, res := range header.PageRes {
r.loadRes(resolveResourcePath(page.BaseLoc, "", res))
}
}
// readPageHeader 读取页面区域、模板引用和资源声明
// 入参: page 页面对象
// 返回: PageContent 页面头部, error 错误信息
func (r *Reader) readPageHeader(page Page) (PageContent, error) {
name := r.ResPath(page.BaseLoc)
if content, ok := r.pageHeaderCache[name]; ok {
return content, nil
}
f, err := r.openFile(name)
if err != nil {
return PageContent{}, err
}
defer f.Close()
var content PageContent
d := xml.NewDecoder(f)
scan:
for {
token, err := d.Token()
if err == io.EOF {
break
}
if err != nil {
return PageContent{}, err
}
if start, ok := token.(xml.StartElement); ok {
switch start.Name.Local {
case "Page":
continue
case "Template":
var template Template
if err := d.DecodeElement(&template, &start); err != nil {
return PageContent{}, err
}
content.Template = append(content.Template, template)
case "Area":
if err := d.DecodeElement(&content.Area, &start); err != nil {
return PageContent{}, err
}
case "PageRes":
var res string
if err := d.DecodeElement(&res, &start); err != nil {
return PageContent{}, err
}
content.PageRes = append(content.PageRes, res)
case "Content":
break scan
default:
if err := d.Skip(); err != nil {
return PageContent{}, err
}
}
}
}
r.pageHeaderCache[name] = content
return content, nil
}
// resolvePageArea 解析页面区域的继承关系
// 入参: area 页面区域, templates 模板引用
// 返回: PageArea 页面区域, error 错误信息
func (r *Reader) resolvePageArea(area PageArea, templates []Template) (PageArea, error) {
if area != (PageArea{}) || r.doc == nil {
return area, nil
}
for _, ref := range templates {
for _, template := range r.doc.CommonData.TemplatePage {
if template.ID != ref.TemplateID {
continue
}
content, err := r.readPageHeader(Page{BaseLoc: template.BaseLoc})
if err != nil {
return PageArea{}, err
}
if content.Area != (PageArea{}) {
return content.Area, nil
}
break
}
}
return r.doc.CommonData.PageArea, nil
}
// readDocumentPart 读取文档关联文件
// 入参: loc 文件位置, value 文档结构
// 返回: string 文件路径, error 错误信息
func (r *Reader) readDocumentPart(loc string, value any) (string, error) {
fullPath := r.ResPath(strings.TrimSpace(loc))
data, err := r.readFile(fullPath)
if err != nil {
return "", err
}
if err := xml.Unmarshal(data, value); err != nil {
return "", fmt.Errorf("failed to unmarshal %s: %w", path.Base(fullPath), err)
}
return fullPath, nil
}