Files
ofdgo/ofdgo_reader_dependencies.go
T
2026-10-09 23:15:06 +08:00

150 lines
4.5 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 (
"bufio"
"bytes"
"context"
"encoding/xml"
"fmt"
"io"
"strings"
)
// documentFiles 按标准路径遍历独立文档依赖,签名摘要引用不作为内容依赖
// 入参: ctx 取消上下文, index 文档索引
// 返回: map[string]bool 大小写折叠的文件路径, error 读取或取消错误
func (r *Reader) documentFiles(ctx context.Context, index int) (map[string]bool, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if index < 0 || index >= r.DocumentCount() {
return nil, fmt.Errorf("document index out of range: %d", index)
}
queue := []string{cleanPackagePath(r.OFD.DocBody[index].DocRoot)}
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", index)
if body == nil {
return nil, fmt.Errorf("document body not found: %d", index)
}
var collect func(*editorXML)
collect = func(node *editorXML) {
if node.name.Local == "Cover" || node.name.Local == "Signatures" || node.name.Local == "DocRoot" {
if value := strings.TrimSpace(editorImportText(data, node)); value != "" {
queue = append(queue, resolveResourcePath("OFD.xml", "", value))
}
}
if node.name.Local == "Version" && node.attr("BaseLoc") != "" {
queue = append(queue, resolveResourcePath("OFD.xml", "", node.attr("BaseLoc")))
}
for _, child := range node.children {
collect(child)
}
}
collect(body)
seen := make(map[string]bool)
for len(queue) > 0 {
if err := ctx.Err(); err != nil {
return nil, err
}
name := queue[0]
queue = queue[1:]
key := strings.ToLower(name)
if seen[key] {
continue
}
seen[key] = true
actual, exists := r.fileNamesFold[key]
if !exists {
continue
}
input, err := r.openFile(actual)
if err != nil {
return nil, err
}
buffer := bufio.NewReader(imageInput{ReadCloser: input, context: ctx})
if prefix, _ := buffer.Peek(3); bytes.Equal(prefix, []byte{0xef, 0xbb, 0xbf}) {
buffer.Discard(3)
}
header, _ := buffer.Peek(128)
for len(header) > 0 && len(bytes.TrimSpace(header)) == 0 {
buffer.Discard(len(header))
header, _ = buffer.Peek(128)
}
if !bytes.HasPrefix(bytes.TrimSpace(header), []byte("<")) {
input.Close()
continue
}
decoder := xml.NewDecoder(buffer)
base, resource, depth := "", false, 0
var text strings.Builder
for {
token, err := decoder.Token()
if err == io.EOF {
break
}
if err != nil {
input.Close()
return nil, err
}
switch node := token.(type) {
case xml.StartElement:
if depth == 0 && classifyOFDNamespace(node.Name.Space) == ofdXMLUnknown {
input.Close()
goto nextFile
}
if classifyOFDNamespace(node.Name.Space) == ofdXMLUnknown {
input.Close()
return nil, fmt.Errorf("cannot determine document dependencies in %s", actual)
}
if depth == 0 {
resource = node.Name.Local == "Res"
}
for _, attr := range node.Attr {
if resource && depth == 0 && attr.Name.Local == "BaseLoc" {
base = attr.Value
} else if attr.Name.Local == "BaseLoc" || attr.Name.Local == "FileLoc" || node.Name.Local == "DrawParam" && attr.Name.Local == "Link" || node.Name.Local == "ColorSpace" && attr.Name.Local == "Profile" {
queue = append(queue, resolveResourcePath(actual, base, attr.Value))
}
}
depth++
text.Reset()
case xml.CharData:
text.Write(node)
case xml.EndElement:
switch node.Name.Local {
case "DocRoot", "Signatures", "Cover", "PublicRes", "DocumentRes", "PageRes", "Annotations", "Attachments", "FileLoc", "Profile", "SignedValue", "SchemaLoc", "ExtendData", "File", "FontFile", "MediaFile", "CustomTags", "Extensions":
if value := strings.TrimSpace(text.String()); value != "" {
queue = append(queue, resolveResourcePath(actual, base, value))
}
}
depth--
text.Reset()
}
}
input.Close()
nextFile:
}
return seen, ctx.Err()
}