mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
196 lines
6.6 KiB
Go
196 lines
6.6 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 (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// DocumentVersionInfo 文档版本信息,Location、DocRoot及Files中的路径均相对于包根目录
|
|
// Index为版本号,Version为该版本适用的格式版本,CreationDate保留原始日期文本
|
|
type DocumentVersionInfo struct {
|
|
ID string
|
|
Index int32
|
|
Current bool
|
|
Location string
|
|
Version string
|
|
Name string
|
|
CreationDate string
|
|
DocRoot string
|
|
Files []VersionFile
|
|
}
|
|
|
|
// VersionFile 文档版本中的文件标识与包内路径
|
|
type VersionFile struct {
|
|
ID string
|
|
Location string
|
|
}
|
|
|
|
// DocumentVersionCount 获取当前文档的版本数量,不读取版本描述文件
|
|
// 返回: int 版本数量, error 入口读取或结构错误
|
|
func (r *Reader) DocumentVersionCount() (int, error) {
|
|
if r.documentIndex >= 0 && r.documentIndex < r.DocumentCount() && !r.OFD.DocBody[r.documentIndex].versioned {
|
|
return 0, nil
|
|
}
|
|
entries, err := r.documentVersionEntries()
|
|
return len(entries), err
|
|
}
|
|
|
|
// DocumentVersions 按入口顺序读取当前文档的版本信息,不切换文档或读取文件清单中的内容
|
|
// 返回独立副本;没有版本列表时返回nil,不以版本号推测默认版本
|
|
// 返回: []DocumentVersionInfo 版本信息, error 读取或描述错误
|
|
func (r *Reader) DocumentVersions() ([]DocumentVersionInfo, error) {
|
|
entries, err := r.documentVersionEntries()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i, entry := range entries {
|
|
info, err := r.readDocumentVersion(entry.ID, entry.Location)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("version %s: %w", entry.ID, err)
|
|
}
|
|
info.Index, info.Current = entry.Index, entry.Current
|
|
entries[i] = info
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// documentVersionEntries 读取当前文档的版本入口,不提前加载各版本描述
|
|
// 返回: []DocumentVersionInfo 入口属性, error 读取或描述错误
|
|
func (r *Reader) documentVersionEntries() ([]DocumentVersionInfo, error) {
|
|
if r.documentIndex < 0 || r.documentIndex >= r.DocumentCount() {
|
|
return nil, fmt.Errorf("no docbody found")
|
|
}
|
|
data, err := r.readFile("OFD.xml")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
root, err := parseEditorXML(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
body := root.childAt("DocBody", r.documentIndex)
|
|
if body == nil {
|
|
return nil, fmt.Errorf("document body not found: %d", r.documentIndex)
|
|
}
|
|
versions := body.child("Versions")
|
|
if versions == nil {
|
|
return nil, nil
|
|
}
|
|
if body.childAt("Versions", 1) != nil {
|
|
return nil, fmt.Errorf("multiple version lists")
|
|
}
|
|
var result []DocumentVersionInfo
|
|
ids := make(map[string]bool)
|
|
for _, node := range versions.children {
|
|
if !node.matchesOFD("Version") {
|
|
continue
|
|
}
|
|
id := strings.TrimSpace(node.attr("ID"))
|
|
if !ofdXMLIDValid(id) || ids[id] {
|
|
return nil, fmt.Errorf("invalid or duplicate version ID %q", id)
|
|
}
|
|
ids[id] = true
|
|
index, err := strconv.ParseInt(strings.TrimSpace(node.attr("Index")), 10, 32)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid version Index for %s: %w", id, err)
|
|
}
|
|
current := false
|
|
if node.hasAttr("Current") {
|
|
switch strings.TrimSpace(node.attr("Current")) {
|
|
case "true", "1":
|
|
current = true
|
|
case "false", "0":
|
|
default:
|
|
return nil, fmt.Errorf("invalid version Current for %s", id)
|
|
}
|
|
}
|
|
location, err := versionFileLocation("OFD.xml", node.attr("BaseLoc"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("version %s: %w", id, err)
|
|
}
|
|
result = append(result, DocumentVersionInfo{ID: id, Index: int32(index), Current: current, Location: location})
|
|
}
|
|
if len(result) == 0 {
|
|
return nil, fmt.Errorf("empty version list")
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// readDocumentVersion 读取版本描述,按描述文件位置解析文件清单和文档入口
|
|
// 入参: id 入口中的版本标识, location 描述文件路径
|
|
// 返回: DocumentVersionInfo 版本信息, error 读取或描述错误
|
|
func (r *Reader) readDocumentVersion(id, location string) (DocumentVersionInfo, error) {
|
|
var info DocumentVersionInfo
|
|
data, err := r.readFile(location)
|
|
if err != nil {
|
|
return info, err
|
|
}
|
|
root, err := parseEditorXML(data)
|
|
if err != nil {
|
|
return info, err
|
|
}
|
|
if root.name.Local != "DocVersion" || strings.TrimSpace(root.attr("ID")) != id {
|
|
return info, fmt.Errorf("invalid version descriptor %s", location)
|
|
}
|
|
list, docRoot := root.child("FileList"), root.child("DocRoot")
|
|
if list == nil || docRoot == nil {
|
|
return info, fmt.Errorf("missing version FileList or DocRoot in %s", location)
|
|
}
|
|
if root.childAt("FileList", 1) != nil || root.childAt("DocRoot", 1) != nil {
|
|
return info, fmt.Errorf("duplicate version FileList or DocRoot in %s", location)
|
|
}
|
|
entry, err := versionFileLocation(location, editorImportText(data, docRoot))
|
|
if err != nil {
|
|
return info, err
|
|
}
|
|
info = DocumentVersionInfo{ID: id, Location: location, Version: root.attr("Version"), Name: root.attr("Name"), CreationDate: root.attr("CreationDate"), DocRoot: entry}
|
|
ids := map[string]bool{id: true}
|
|
found := false
|
|
for _, node := range list.children {
|
|
if !node.matchesOFD("File") {
|
|
continue
|
|
}
|
|
fileID := strings.TrimSpace(node.attr("ID"))
|
|
if !ofdXMLIDValid(fileID) || ids[fileID] {
|
|
return DocumentVersionInfo{}, fmt.Errorf("invalid or duplicate version file ID %q", fileID)
|
|
}
|
|
ids[fileID] = true
|
|
name, err := versionFileLocation(location, editorImportText(data, node))
|
|
if err != nil {
|
|
return DocumentVersionInfo{}, err
|
|
}
|
|
found = found || name == entry
|
|
info.Files = append(info.Files, VersionFile{ID: fileID, Location: name})
|
|
}
|
|
if !found {
|
|
return DocumentVersionInfo{}, fmt.Errorf("version DocRoot absent from FileList: %s", entry)
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
// versionFileLocation 解析版本描述中的ST_Loc,拒绝空路径和包外路径
|
|
// 入参: owner 所在XML路径, value 引用路径
|
|
// 返回: string 包内路径, error 路径错误
|
|
func versionFileLocation(owner, value string) (string, error) {
|
|
name := resolveResourcePath(owner, "", value)
|
|
if name == "" || !validPackagePath(name) {
|
|
return "", fmt.Errorf("invalid version file location %q", value)
|
|
}
|
|
return name, nil
|
|
}
|