From 52d7b6aa648d4c5b5e7321a477160115a09e8ab6 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sun, 11 Oct 2026 19:56:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=89=88=E6=9C=AC=E7=BC=96=E8=BE=91):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=89=88=E6=9C=AC=E4=BE=9D=E8=B5=96=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=E4=B8=8E=E7=BC=96=E8=BE=91=E5=86=85=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_reader_dependencies.go | 39 +++++++++++- ofdgo_version_edit.go | 119 ++++++++++++++--------------------- 2 files changed, 85 insertions(+), 73 deletions(-) diff --git a/ofdgo_reader_dependencies.go b/ofdgo_reader_dependencies.go index 537777c..9488465 100644 --- a/ofdgo_reader_dependencies.go +++ b/ofdgo_reader_dependencies.go @@ -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] diff --git a/ofdgo_version_edit.go b/ofdgo_version_edit.go index ed6bb39..542f020 100644 --- a/ofdgo_version_edit.go +++ b/ofdgo_version_edit.go @@ -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 更新所选版本的入口与文件清单,保留已有文件标识及扩展字段