feat(版本编辑): 优化版本依赖扫描与编辑内存
build.yaml / build (push) Successful in 49s

This commit is contained in:
xiaoqidun committed 2026-10-11 19:56:09 +08:00
1 parent 34e17383f1
commit 52d7b6aa64
2 files changed
+85 -73

No files matched your search

+37 -2
View File
@@ -235,6 +235,13 @@ func (r *Reader) documentDependencies(ctx context.Context, indexes ...int) (map[
// 入参: ctx 取消上下文, name 文件路径, rejectUnknown 是否拒绝无法识别的扩展节点, add 接收引用路径及类别
// 返回: error 读取或结构错误
func (r *Reader) documentFileReferences(ctx context.Context, name string, rejectUnknown bool, add func(string, int)) error {
return r.scanDocumentFileReferences(ctx, name, rejectUnknown, add, nil)
}
// scanDocumentFileReferences 流式扫描文件依赖,按需记录引用节点位置,不构建完整节点树
// 入参: ctx 取消上下文, name 文件路径, rejectUnknown 是否拒绝未知扩展, add 接收依赖, record 接收引用位置,可为nil
// 返回: error 读取、结构或取消错误
func (r *Reader) scanDocumentFileReferences(ctx context.Context, name string, rejectUnknown bool, add func(string, int), record func(*editorXML, string, string)) error {
input, err := r.openFile(name)
if err != nil {
return err
@@ -242,12 +249,14 @@ func (r *Reader) documentFileReferences(ctx context.Context, name string, reject
defer input.Close()
decoder := xml.NewDecoder(imageInput{ReadCloser: input, context: ctx})
var stack []xml.Name
var positions []*editorXML
root, base := "", ""
var text strings.Builder
for {
if err := ctx.Err(); err != nil {
return err
}
start := int(decoder.InputOffset())
token, err := decoder.Token()
if err == io.EOF {
if root == "" {
@@ -285,6 +294,7 @@ func (r *Reader) documentFileReferences(ctx context.Context, name string, reject
}
continue
}
var position *editorXML
for _, attr := range node.Attr {
if attr.Name.Space != "" || strings.TrimSpace(attr.Value) == "" {
continue
@@ -292,9 +302,22 @@ func (r *Reader) documentFileReferences(ctx context.Context, name string, reject
if len(stack) == 0 && root == "Res" && attr.Name.Local == "BaseLoc" {
base = attr.Value
} else if kind := packageReference(root, parent, node.Name.Local, attr.Name.Local); kind != packageReferenceNone {
add(resolveResourcePath(name, base, attr.Value), kind)
target := resolveResourcePath(name, base, attr.Value)
add(target, kind)
if record != nil {
if position == nil {
position = &editorXML{name: node.Name, attrs: node.Attr, start: start, open: int(decoder.InputOffset())}
}
record(position, attr.Name.Local, target)
}
}
}
if record != nil {
if position == nil && packageReference(root, parent, node.Name.Local, "") != packageReferenceNone {
position = &editorXML{name: node.Name, attrs: node.Attr, start: start, open: int(decoder.InputOffset())}
}
positions = append(positions, position)
}
stack = append(stack, node.Name)
text.Reset()
case xml.CharData:
@@ -302,10 +325,22 @@ func (r *Reader) documentFileReferences(ctx context.Context, name string, reject
text.Write(node)
}
case xml.EndElement:
var position *editorXML
if record != nil {
position = positions[len(positions)-1]
if position != nil {
position.close, position.end = start, int(decoder.InputOffset())
}
positions = positions[:len(positions)-1]
}
if len(stack) >= 2 {
kind := packageReference(root, stack[len(stack)-2].Local, node.Name.Local, "")
if value := strings.TrimSpace(text.String()); kind != packageReferenceNone && value != "" {
add(resolveResourcePath(name, base, value), kind)
target := resolveResourcePath(name, base, value)
add(target, kind)
if record != nil {
record(position, "", target)
}
}
}
stack = stack[:len(stack)-1]
+48 -71
View File
@@ -18,7 +18,6 @@ import (
"bytes"
"context"
"encoding/xml"
"fmt"
"io"
"maps"
"slices"
@@ -26,10 +25,12 @@ import (
"strings"
)
// versionEditFile 保存版本内容XML及标准文件引用,不解析字体、图片或附件数据
// versionEditFile 保存结构文件及引用位置,仅在改写时读取尚未加载的原文
type versionEditFile struct {
data []byte
links []versionEditLink
reader *Reader
name string
data []byte
links []versionEditLink
}
// versionEditLink 记录文件引用的位置与包内目标
@@ -83,10 +84,18 @@ func (r *Reader) versionFileDependencies(ctx context.Context, entry string, part
return used, ctx.Err()
}
// versionEditGraph 沿标准文件引用建立版本依赖图,二进制资源只记录路径
// versionEditGraph 流式建立标准文件依赖图,不保留完整XML节点树或解压后的原包文件
// 入参: ctx 取消上下文, entry 文档入口, parts 待修改条目, generated 本次自产页面及引用
// 返回: *versionEditGraph 依赖图, error 读取、结构或取消错误
func (r *Reader) versionEditGraph(ctx context.Context, entry string, parts map[string][]byte, generated map[string]editorGeneratedReferences) (*versionEditGraph, error) {
view := *r
if len(parts) != 0 {
view.files = maps.Clone(r.files)
if view.files == nil {
view.files = make(map[string][]byte, len(parts))
}
maps.Copy(view.files, parts)
}
graph := &versionEditGraph{files: make(map[string]*versionEditFile), used: make(map[string]bool)}
queue := []string{entry}
queued := map[string]bool{entry: true}
@@ -101,69 +110,19 @@ func (r *Reader) versionEditGraph(ctx context.Context, entry string, parts map[s
graph.files[name], graph.used[name] = &versionEditFile{}, true
continue
}
if !ok {
var err error
data, err = r.readFileViewContext(ctx, name)
if err != nil {
return nil, err
}
}
root, err := parseEditorXMLContext(ctx, data)
if err != nil {
return nil, fmt.Errorf("version file %s: %w", name, err)
}
file := &versionEditFile{data: data}
file := &versionEditFile{reader: &view, name: name, data: view.files[name]}
graph.files[name], graph.used[name] = file, true
base := ""
if root.name.Local == "Res" {
base = root.attr("BaseLoc")
add := func(target string, kind int) {
graph.used[target] = true
if kind == packageReferenceXML && !queued[target] {
queued[target] = true
queue = append(queue, target)
}
}
var visit func(*editorXML) error
visit = func(node *editorXML) error {
if err := ctx.Err(); err != nil {
return err
}
if node != root && !node.matchesOFD(node.name.Local) {
return nil
}
parent := ""
if node.parent != nil {
parent = node.parent.name.Local
}
if parent == "Extension" && node.name.Local == "Data" {
return nil
}
add := func(attribute, value string, kind int) {
if strings.TrimSpace(value) == "" {
return
}
target := resolveResourcePath(name, base, value)
file.links = append(file.links, versionEditLink{node: node, attribute: attribute, target: target})
graph.used[target] = true
if kind == packageReferenceXML && !queued[target] {
queued[target] = true
queue = append(queue, target)
}
}
if kind := packageReference(root.name.Local, parent, node.name.Local, ""); kind != packageReferenceNone {
add("", editorImportText(data, node), kind)
}
for _, attr := range node.attrs {
if attr.Name.Space != "" {
continue
}
if kind := packageReference(root.name.Local, parent, node.name.Local, attr.Name.Local); kind != packageReferenceNone {
add(attr.Name.Local, attr.Value, kind)
}
}
for _, child := range node.children {
if err := visit(child); err != nil {
return err
}
}
return nil
record := func(node *editorXML, attribute, target string) {
file.links = append(file.links, versionEditLink{node: node, attribute: attribute, target: target})
}
if err := visit(root); err != nil {
if err := view.scanDocumentFileReferences(ctx, name, false, add, record); err != nil {
return nil, err
}
}
@@ -484,16 +443,34 @@ func (r *Reader) versionOutputChanges(ctx context.Context, changes map[string][]
return parts, nil
}
// rewrite 更新已隔离文件的引用,保留其余XML及相对路径基准
// rewrite 按需读取并更新已隔离文件的引用,保留其余XML及相对路径基准
// 入参: ctx 取消上下文, paths 原路径到新路径的映射
// 返回: []byte 更新后的XML,未改动时借用原文, error 属性编码错误
// 返回: []byte 更新后的XML,未改动时返回已加载原文或nil, error 读取或属性编码错误
func (file *versionEditFile) rewrite(ctx context.Context, paths map[string]string) ([]byte, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if len(paths) == 0 {
needed := false
for _, link := range file.links {
if err := ctx.Err(); err != nil {
return nil, err
}
if paths[link.target] != "" {
needed = true
break
}
}
if !needed {
return file.data, nil
}
source := file.data
if source == nil {
var err error
source, err = file.reader.readFileViewContext(ctx, file.name)
if err != nil {
return nil, err
}
}
var patches []editorXMLPatch
headers := make(map[*editorXML][]versionEditLink)
for _, link := range file.links {
@@ -504,13 +481,13 @@ func (file *versionEditFile) rewrite(ctx context.Context, paths map[string]strin
continue
}
if link.attribute == "" {
patches = append(patches, editorXMLContent(file.data, link.node, editorFontXMLText("/"+paths[link.target])))
patches = append(patches, editorXMLContent(source, link.node, editorFontXMLText("/"+paths[link.target])))
} else {
headers[link.node] = append(headers[link.node], link)
}
}
for node, links := range headers {
data := file.data[node.start:node.end]
data := source[node.start:node.end]
for _, link := range links {
root, err := parseEditorXMLContext(ctx, data)
if err != nil {
@@ -534,7 +511,7 @@ func (file *versionEditFile) rewrite(ctx context.Context, paths map[string]strin
if len(patches) == 0 {
return file.data, nil
}
return editorPatchXML(file.data, patches), nil
return editorPatchXML(source, patches), ctx.Err()
}
// versionManifest 更新所选版本的入口与文件清单,保留已有文件标识及扩展字段