Files
ofdgo/ofdgo_doc.go
T
2026-10-12 10:47:20 +08:00

407 lines
11 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 (
"encoding/xml"
"path"
"strings"
)
// Document 文档结构
// Signatures由阅读器按主入口和所选版本解析,不属于文档根节点的XML内容
type Document struct {
XMLName xml.Name `xml:"Document"`
CommonData CommonData `xml:"CommonData"`
Pages Pages `xml:"Pages"`
Outlines Outlines `xml:"Outlines"`
Permissions Permissions `xml:"Permissions"`
Actions []Action `xml:"Actions>Action"`
Bookmarks Bookmarks `xml:"Bookmarks"`
Annotations string `xml:"Annotations"`
Signatures string `xml:"-"`
Attachments Attachments `xml:"Attachments"`
CustomTags CustomTags `xml:"CustomTags"`
Extensions Extensions `xml:"Extensions"`
}
// Extensions 扩展集合
type Extensions struct {
XMLName xml.Name `xml:"Extensions"`
Path string `xml:",chardata"`
Extension []Extension `xml:"Extension"`
}
// Extension 扩展信息
type Extension struct {
AppName string `xml:"AppName,attr"`
Company string `xml:"Company,attr"`
AppVersion string `xml:"AppVersion,attr"`
Date string `xml:"Date,attr"`
RefID string `xml:"RefId,attr"`
Property []Property `xml:"Property"`
ExtendData []string `xml:"ExtendData"`
Data []ExtensionData `xml:"Data"`
}
// Property 扩展属性
type Property struct {
Name string `xml:"Name,attr"`
Value string `xml:",chardata"`
Type string `xml:"Type,attr"`
}
// ExtensionData 扩展数据
type ExtensionData struct {
Attr []xml.Attr `xml:",any,attr"`
Content string `xml:",innerxml"`
}
// Attachments 附件集合
type Attachments struct {
XMLName xml.Name `xml:"Attachments"`
Path string `xml:",chardata"`
Attachment []Attachment `xml:"Attachment"`
}
// Attachment 附件信息
type Attachment struct {
ID string `xml:"ID,attr"`
Name string `xml:"Name,attr"`
Format string `xml:"Format,attr"`
CreationDate string `xml:"CreationDate,attr"`
ModDate string `xml:"ModDate,attr"`
Size *float64 `xml:"Size,attr"`
Visible bool `xml:"Visible,attr"`
Usage string `xml:"Usage,attr"`
FileLoc string `xml:"FileLoc"`
}
// CustomTags 自定义标引集合
type CustomTags struct {
XMLName xml.Name `xml:"CustomTags"`
Path string `xml:",chardata"`
CustomTag []CustomTag `xml:"CustomTag"`
}
// CustomTag 自定义标引
type CustomTag struct {
TypeID string `xml:"TypeID,attr"`
NameSpace string `xml:"NameSpace,attr"`
SchemaLoc string `xml:"SchemaLoc"`
FileLoc string `xml:"FileLoc"`
}
// CommonData 文档公共数据
type CommonData struct {
MaxUnitID int `xml:"MaxUnitID"`
PageArea PageArea `xml:"PageArea"`
PublicRes []string `xml:"PublicRes"`
DocumentRes []string `xml:"DocumentRes"`
TemplatePage []TemplatePage `xml:"TemplatePage"`
DefaultCS int `xml:"DefaultCS"`
}
// PageArea 页面区域定义
type PageArea struct {
PhysicalBox string `xml:"PhysicalBox"`
ApplicationBox string `xml:"ApplicationBox"`
ContentBox string `xml:"ContentBox"`
BleedBox string `xml:"BleedBox"`
}
// Pages 页面引用集合
type Pages struct {
Page []Page `xml:"Page"`
}
// Page 页面引用
type Page struct {
ID string `xml:"ID,attr"`
BaseLoc string `xml:"BaseLoc,attr"`
}
// TemplatePage 模板页
type TemplatePage struct {
ID string `xml:"ID,attr"`
Name string `xml:"Name,attr"`
BaseLoc string `xml:"BaseLoc,attr"`
ZOrder string `xml:"ZOrder,attr"`
}
// Bookmarks 书签集合
type Bookmarks struct {
Bookmark []Bookmark `xml:"Bookmark"`
}
// Bookmark 书签
type Bookmark struct {
Name string `xml:"Name,attr"`
Dest Dest `xml:"Dest"`
}
// Outlines 大纲集合
type Outlines struct {
OutlineElem []OutlineElem `xml:"OutlineElem"`
}
// OutlineElem 大纲节点
type OutlineElem struct {
Title string `xml:"Title,attr"`
Count int `xml:"Count,attr"`
Expanded bool `xml:"Expanded,attr"`
Actions []Action `xml:"Actions>Action"`
OutlineElem []OutlineElem `xml:"OutlineElem"`
}
// OutlineInfo 目录节点信息,Page从1开始,0表示没有有效的文档内目标页
type OutlineInfo struct {
Title string `json:"title"`
Expanded bool `json:"expanded"`
Page int `json:"page,omitempty"`
ActionCount int `json:"actionCount,omitempty"`
Children []OutlineInfo `json:"children,omitempty"`
}
// Permissions 权限声明
type Permissions struct {
Edit bool `xml:"Edit"`
Annot bool `xml:"Annot"`
Export bool `xml:"Export"`
Signature bool `xml:"Signature"`
Watermark bool `xml:"Watermark"`
PrintScreen bool `xml:"PrintScreen"`
Print bool `xml:"-"`
Copies int `xml:"-"`
Copy bool `xml:"CopyText"`
ContentRegist bool `xml:"ContentRegist"`
ValidPeriod *ValidPeriod `xml:"ValidPeriod"`
}
// ValidPeriod 文档访问有效期
type ValidPeriod struct {
StartDate string `xml:"StartDate,attr"`
EndDate string `xml:"EndDate,attr"`
}
// print 打印权限节点
type print struct {
Printable *bool `xml:"Printable,attr"`
Copies *int `xml:"Copies,attr"`
}
// PageIndex 按标识查找页面,优先匹配原文和标准十进制写法,等价编号有歧义时不匹配
// 入参: id 页面标识
// 返回: int 从0开始的页面索引,未找到时为-1
func (doc *Document) PageIndex(id string) int {
if doc == nil {
return -1
}
index, key := -1, editorResourceID(id)
for i, page := range doc.Pages.Page {
if page.ID == id {
return i
}
if key != "" && page.ID == key {
index = i
}
}
if index >= 0 || key == "" {
return index
}
for i, page := range doc.Pages.Page {
if editorResourceID(page.ID) == key {
if index >= 0 {
return -1
}
index = i
}
}
return index
}
// OutlineInfos 获取目录层级和目标页码
// 返回: []OutlineInfo 目录节点信息
func (doc *Document) OutlineInfos() []OutlineInfo {
if len(doc.Outlines.OutlineElem) == 0 {
return nil
}
bookmarks := make(map[string]Dest, len(doc.Bookmarks.Bookmark))
for _, bookmark := range doc.Bookmarks.Bookmark {
bookmarks[bookmark.Name] = bookmark.Dest
}
pages := make(map[string]int, len(doc.Pages.Page))
for index, page := range doc.Pages.Page {
pages[page.ID] = index + 1
}
return outlineInfos(doc.Outlines.OutlineElem, bookmarks, pages)
}
// UnmarshalXML 解析文档并应用权限默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (doc *Document) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type document Document
value := document{Permissions: defaultPermissions()}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*doc = Document(value)
return nil
}
// FileName 获取另存文件名,优先使用带扩展名的显示名称,剔除目录及文件名非法字符
// 返回: string 文件名
func (a Attachment) FileName() string {
name := path.Base(strings.ReplaceAll(a.Name, "\\", "/"))
file := path.Base(strings.ReplaceAll(a.FileLoc, "\\", "/"))
if name == "." || name == "/" || path.Ext(name) == "" && path.Ext(file) != "" {
name = file
}
if name == "." || name == "/" {
name = "attachment"
}
return strings.Map(func(r rune) rune {
if r < 32 || strings.ContainsRune(`<>:"/\|?*`, r) {
return '_'
}
return r
}, name)
}
// UnmarshalXML 解析附件并应用默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (a *Attachment) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type attachment Attachment
value := attachment{Visible: true, Usage: "none"}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*a = Attachment(value)
return nil
}
// UnmarshalXML 解析大纲节点并应用默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (o *OutlineElem) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type outlineElem OutlineElem
value := outlineElem{Expanded: true}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*o = OutlineElem(value)
return nil
}
// UnmarshalXML 解析文档权限并应用默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (p *Permissions) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value struct {
Edit *bool `xml:"Edit"`
Annot *bool `xml:"Annot"`
Export *bool `xml:"Export"`
Signature *bool `xml:"Signature"`
Watermark *bool `xml:"Watermark"`
PrintScreen *bool `xml:"PrintScreen"`
Copy *bool `xml:"CopyText"`
ContentRegist *bool `xml:"ContentRegist"`
Print *print `xml:"Print"`
ValidPeriod *ValidPeriod `xml:"ValidPeriod"`
}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*p = defaultPermissions()
if value.Edit != nil {
p.Edit = *value.Edit
}
if value.Annot != nil {
p.Annot = *value.Annot
}
if value.Export != nil {
p.Export = *value.Export
}
if value.Signature != nil {
p.Signature = *value.Signature
}
if value.Watermark != nil {
p.Watermark = *value.Watermark
}
if value.PrintScreen != nil {
p.PrintScreen = *value.PrintScreen
}
if value.Copy != nil {
p.Copy = *value.Copy
}
if value.ContentRegist != nil {
p.ContentRegist = *value.ContentRegist
}
if value.Print != nil {
if value.Print.Printable != nil {
p.Print = *value.Print.Printable
}
if value.Print.Copies != nil {
p.Copies = *value.Print.Copies
}
}
p.ValidPeriod = value.ValidPeriod
return nil
}
// outlineInfos 转换目录节点信息
// 入参: outlines 大纲节点, bookmarks 书签, pages 页码索引
// 返回: []OutlineInfo 目录节点信息
func outlineInfos(outlines []OutlineElem, bookmarks map[string]Dest, pages map[string]int) []OutlineInfo {
infos := make([]OutlineInfo, 0, len(outlines))
for _, outline := range outlines {
info := OutlineInfo{Title: outline.Title, Expanded: outline.Expanded, ActionCount: len(outline.Actions)}
for _, action := range outline.Actions {
if action.Goto != nil {
if dest := gotoDest(action.Goto, bookmarks); dest != nil {
info.Page, _ = resourceValue(pages, dest.PageID)
if info.Page != 0 {
break
}
}
}
}
if len(outline.OutlineElem) > 0 {
info.Children = outlineInfos(outline.OutlineElem, bookmarks, pages)
}
infos = append(infos, info)
}
return infos
}
// defaultPermissions 获取默认文档权限
// 返回: Permissions 文档权限
func defaultPermissions() Permissions {
return Permissions{
Edit: true,
Annot: true,
Export: true,
Signature: true,
Watermark: true,
PrintScreen: true,
Print: true,
Copies: -1,
Copy: true,
ContentRegist: true,
}
}