mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:31:12 +08:00
1 parent
53769b65ab
commit
492adccb81
35 files changed
+1182
-239
No files matched your search
@@ -0,0 +1,185 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user