feat(文件归并): 归并同类文件清理辅助代码
build.yaml / build (push) Successful in 51s

This commit is contained in:
xiaoqidun committed 2026-10-14 09:38:41 +08:00
1 parent 961bc52038
commit d07972346b
19 files changed
+835 -1042

No files matched your search

+203
View File
@@ -15,14 +15,22 @@
package ofdgo
import (
"bufio"
"bytes"
"context"
"encoding/xml"
"io"
"maps"
"slices"
"strings"
)
// editorResourceScope 记录资源索引的文档归属链,引用集合只读
type editorResourceScope struct {
parent int
refs *editorResourceRefs
}
// scanEditorResources 优先扫描结构依赖,原始数据不按XML解析,孤立文件仍保留引用检查
// 入参: ctx 取消上下文, reader 原包, parts 输出改动, generated 自产引用, names 扫描范围, progress 扫描进度, collect 是否保留各文件的资源引用
// 返回: *editorResourceRefs 全部引用, []string 资源索引, map[string]*editorResourceRefs 文件引用, bool 是否可安全优化, error 读取或取消错误
@@ -147,3 +155,198 @@ func scanEditorResources(ctx context.Context, reader *Reader, parts map[string][
}
return refs, resources, references, true, ctx.Err()
}
// editorResourceElement 返回标准资源集合中的声明元素名称
// 入参: group 资源集合名称
// 返回: string 声明元素名称,未知集合返回空值
func editorResourceElement(group string) string {
switch group {
case "Fonts":
return "Font"
case "MultiMedias":
return "MultiMedia"
case "ColorSpaces":
return "ColorSpace"
case "DrawParams":
return "DrawParam"
case "CompositeGraphicUnits":
return "CompositeGraphicUnit"
}
return ""
}
// editorResourceDefinitions 单次流式读取资源声明依赖,保留历史索引和非图像媒体
// 入参: ctx 取消上下文, data 资源索引内容, name 文件路径, source 扫描结果, preserved 是否保留全部声明
// 返回: *editorResourceRefs 含声明依赖的引用,无法安全分析时为空, error 解析或取消错误
func editorResourceDefinitions(ctx context.Context, data []byte, name string, source *editorResourceRefs, preserved bool) (*editorResourceRefs, error) {
result := *source
result.ids, result.fonts = make(map[string]bool), nil
result.definitions = make(map[string]*editorResourceRefs)
outer := &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)}
scan := editorReferenceScan{ctx: ctx, refs: outer, name: name, safe: true}
input := bufio.NewReader(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx})
if found, err := editorXMLPrefix(input); err != nil || !found {
return nil, err
}
decoder := xml.NewDecoder(input)
for {
if err := ctx.Err(); err != nil {
return nil, err
}
token, err := decoder.Token()
if err == io.EOF {
if !scan.seen || len(scan.stack) != 0 {
return nil, nil
}
return &result, ctx.Err()
}
if err != nil {
return nil, err
}
depth := len(scan.stack)
if node, ok := token.(xml.StartElement); ok {
if depth == 0 && node.Name.Local != "Res" || depth == 1 && editorResourceElement(node.Name.Local) == "" {
return nil, nil
}
if depth == 2 {
kind := editorResourceElement(scan.stack[1])
var id, mediaType string
for _, attr := range node.Attr {
if attr.Name.Space == "" {
switch attr.Name.Local {
case "ID":
id = editorResourceID(attr.Value)
case "Type":
mediaType = attr.Value
}
}
}
if node.Name.Local != kind || id == "" || result.definitions[id] != nil {
return nil, nil
}
refs := &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)}
result.definitions[id] = refs
scan.refs = refs
if preserved || kind == "MultiMedia" && mediaType != "Image" {
result.ids[id] = true
}
}
}
if !scan.accept(token) {
return nil, ctx.Err()
}
if _, end := token.(xml.EndElement); end && depth == 3 {
scan.refs = outer
}
}
}
// resourceReferenceScopes 按文档归集引用,归属相同的资源索引复用只读结果
// 入参: ctx 取消上下文, documents 文档入口, resources 资源索引, references 文件引用, selected 限定的文档入口,空值统计全部文档
// 返回: map[string]*editorResourceRefs 各资源索引的引用,依赖不完整或引用无法归属时为空, error 取消错误
func resourceReferenceScopes(ctx context.Context, documents map[string]bool, resources []string, references map[string]*editorResourceRefs, selected string) (map[string]*editorResourceRefs, error) {
indexes := make(map[string]bool, len(resources))
for _, name := range resources {
indexes[name] = true
}
used := make(map[string]bool)
owners := make(map[string]int)
groups := []editorResourceScope{{}}
groupIDs := make(map[editorResourceScope]int)
for _, entry := range slices.Sorted(maps.Keys(documents)) {
scope := map[string]bool{entry: true}
queue := []string{entry}
refs := &editorResourceRefs{ids: make(map[string]bool), fonts: make(map[string]*editorFontUsage)}
definitions := make(map[string]*editorResourceRefs)
for position := 0; position < len(queue); position++ {
if err := ctx.Err(); err != nil {
return nil, err
}
name := queue[position]
source := references[name]
if source == nil {
return nil, ctx.Err()
}
used[name] = true
if selected == "" || selected == entry {
refs.merge(&editorResourceRefs{ids: source.ids, fonts: source.fonts})
for id, definition := range source.definitions {
if definitions[id] != nil {
return nil, nil
}
definitions[id] = definition
}
}
for target, role := range source.roles {
if role&packageReferenceXML != 0 && !scope[target] {
scope[target] = true
queue = append(queue, target)
}
}
}
if selected != "" && selected != entry {
continue
}
var pending []string
if len(definitions) != 0 {
pending = slices.Collect(maps.Keys(refs.ids))
}
for position := 0; position < len(pending); position++ {
if err := ctx.Err(); err != nil {
return nil, err
}
if definition := definitions[pending[position]]; definition != nil {
for id := range definition.ids {
if !refs.ids[id] {
pending = append(pending, id)
}
}
refs.merge(&editorResourceRefs{ids: definition.ids, fonts: definition.fonts})
}
}
for name := range scope {
if err := ctx.Err(); err != nil {
return nil, err
}
if !indexes[name] {
continue
}
key := editorResourceScope{parent: owners[name], refs: refs}
id := groupIDs[key]
if id == 0 {
id = len(groups)
groups = append(groups, key)
groupIDs[key] = id
}
owners[name] = id
}
}
for name, refs := range references {
if !used[name] && (len(refs.ids) != 0 || len(refs.fonts) != 0 || indexes[name]) {
return nil, nil
}
}
result := make(map[string]*editorResourceRefs, len(owners))
merged := make(map[int]*editorResourceRefs)
for name, id := range owners {
if err := ctx.Err(); err != nil {
return nil, err
}
refs := merged[id]
if refs == nil {
refs = groups[id].refs
if groups[id].parent != 0 {
refs = &editorResourceRefs{ids: make(map[string]bool), fonts: make(map[string]*editorFontUsage)}
for next := id; next != 0; next = groups[next].parent {
if err := ctx.Err(); err != nil {
return nil, err
}
refs.merge(groups[next].refs)
}
}
merged[id] = refs
}
result[name] = refs
}
return result, ctx.Err()
}