From a3098212ebf96a8e4d9f546190ea32ae83a4d6cf Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sun, 11 Oct 2026 11:24:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=BC=96=E8=BE=91=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=99=84=E4=BB=B6=E8=AF=AF=E6=94=B9=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8E=8B=E7=BC=A9=E6=89=AB=E6=8F=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_compression_resources.go | 57 ++----------- ofdgo_editor_reference.go | 35 ++++++++ ofdgo_editor_resource_scan.go | 149 +++++++++++++++++++++++++++++++++ ofdgo_editor_resources.go | 52 ++---------- 4 files changed, 198 insertions(+), 95 deletions(-) create mode 100644 ofdgo_editor_resource_scan.go diff --git a/ofdgo_compression_resources.go b/ofdgo_compression_resources.go index 3210efa..4abded0 100644 --- a/ofdgo_compression_resources.go +++ b/ofdgo_compression_resources.go @@ -20,7 +20,6 @@ import ( "encoding/xml" "fmt" "image" - "io" "maps" "path" "slices" @@ -73,12 +72,6 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea } return reader.readFileView(name) } - refs := editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} - var fontReferences map[string]*editorResourceRefs - if reader != nil && reader.DocumentCount() > 1 && e.backends.FontResources != nil { - fontReferences = make(map[string]*editorResourceRefs) - } - var resources []string actual := make(map[string]string) for _, name := range slices.Sorted(maps.Keys(names)) { if err := e.output.ctx.Err(); err != nil { @@ -86,6 +79,7 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea } key := cleanPackagePath(name) if removed[key] || strings.HasSuffix(name, "/") { + delete(names, name) continue } if prior, ok := actual[key]; ok && prior != name { @@ -93,51 +87,14 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea } actual[key] = name if scope != nil && !scope[key] { + delete(names, name) continue } - data, replaced := parts[name] - if known, ok := generated[name]; ok && replaced && known.refs != nil && known.matches(data) { - refs.merge(known.refs) - if fontReferences != nil && len(known.refs.fonts) != 0 { - fontReferences[name] = known.refs - } - continue - } - var input io.ReadCloser - if replaced { - if known := generated[name]; data == nil && known.staged != nil { - input = known.staged.open() - } else { - input = io.NopCloser(bytes.NewReader(data)) - } - } else { - var err error - input, err = reader.openFile(name) - if err != nil { - return err - } - } - scanned := &refs - if fontReferences != nil { - scanned = &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} - } - resource, safe := scanned.scan(imageInput{ReadCloser: input, context: e.output.ctx}, name) - input.Close() - if err := e.output.ctx.Err(); err != nil { - return err - } - if !safe { - return nil - } - if fontReferences != nil { - if resource || len(scanned.fonts) != 0 { - fontReferences[name] = scanned - } - refs.merge(scanned) - } - if resource { - resources = append(resources, name) - } + } + collect := reader != nil && reader.DocumentCount() > 1 && e.backends.FontResources != nil + refs, resources, fontReferences, safe, err := scanEditorResources(e.output.ctx, reader, parts, generated, names, nil, collect) + if err != nil || !safe { + return err } type resourceFile struct{ key, role string } files := make(map[resourceFile]bool) diff --git a/ofdgo_editor_reference.go b/ofdgo_editor_reference.go index f6ba006..e895ced 100644 --- a/ofdgo_editor_reference.go +++ b/ofdgo_editor_reference.go @@ -176,6 +176,11 @@ func (s *editorReferenceScan) accept(token xml.Token) bool { if attr.Name.Local != "ID" { s.refs.reference(s.name, s.base, attr.Name.Local, attr.Value, s.resource) } + root, parent := token.Name.Local, "" + if len(s.stack) != 0 { + root, parent = s.stack[0], s.stack[len(s.stack)-1] + } + s.fileReference(root, parent, token.Name.Local, attr.Name.Local, attr.Value) if s.objects != nil && editorObjectReference(attr.Name.Local) { id := attr.Value if key := editorResourceID(id); key != "" { @@ -196,6 +201,9 @@ func (s *editorReferenceScan) accept(token xml.Token) bool { s.fonts = append(s.fonts, usage) s.text.Reset() s.capture = editorResourceReferenceKind(token.Name.Local, s.resource) != editorReferenceNone || usage != nil && (token.Name.Local == "TextCode" || token.Name.Local == "Glyphs") + if len(s.stack) >= 2 && packageReference(s.stack[0], s.stack[len(s.stack)-2], token.Name.Local, "") != packageReferenceNone { + s.capture = true + } case xml.CharData: if s.capture { s.text.Write(token) @@ -206,6 +214,9 @@ func (s *editorReferenceScan) accept(token xml.Token) bool { return false } s.refs.reference(s.name, s.base, token.Name.Local, s.text.String(), s.resource) + if len(s.stack) >= 2 { + s.fileReference(s.stack[0], s.stack[len(s.stack)-2], token.Name.Local, "", s.text.String()) + } if len(s.stack) == 3 && s.stack[0] == "DocVersion" && s.stack[1] == "FileList" && token.Name.Local == "File" { if name := editorResourceLocation(s.name, "", s.text.String()); name != "" { if s.refs.versions == nil { @@ -239,11 +250,35 @@ func (s *editorReferenceScan) accept(token xml.Token) bool { return s.safe } +// fileReference 按节点位置记录结构文件与原始数据,版本清单不改变文件用途 +// 入参: root 根节点, parent 父节点, name 节点名称, attribute 属性名称, value 引用路径 +func (s *editorReferenceScan) fileReference(root, parent, name, attribute, value string) { + if root == "DocVersion" && parent == "FileList" && name == "File" || strings.TrimSpace(value) == "" { + return + } + kind := packageReference(root, parent, name, attribute) + if kind == packageReferenceNone { + return + } + if s.refs.roles == nil { + s.refs.roles = make(map[string]int) + } + s.refs.roles[resolveResourcePath(s.name, s.base, value)] |= kind +} + // merge 合并本次编码已确认的引用,不共享字体用字的可变集合 // 入参: other 独立页面引用 func (r *editorResourceRefs) merge(other *editorResourceRefs) { maps.Copy(r.ids, other.ids) maps.Copy(r.files, other.files) + if len(other.roles) != 0 { + if r.roles == nil { + r.roles = make(map[string]int) + } + for name, role := range other.roles { + r.roles[name] |= role + } + } if len(other.documents) != 0 { if r.documents == nil { r.documents = make(map[string]bool) diff --git a/ofdgo_editor_resource_scan.go b/ofdgo_editor_resource_scan.go new file mode 100644 index 0000000..5e33bd9 --- /dev/null +++ b/ofdgo_editor_resource_scan.go @@ -0,0 +1,149 @@ +// 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 ( + "bytes" + "context" + "io" + "maps" + "slices" + "strings" +) + +// scanEditorResources 优先扫描结构依赖,原始数据不按XML解析,孤立文件仍保留引用检查 +// 入参: ctx 取消上下文, reader 原包, parts 输出改动, generated 自产引用, names 扫描范围, progress 扫描进度, collect 是否保留各文件的字体引用 +// 返回: *editorResourceRefs 全部引用, []string 资源索引, map[string]*editorResourceRefs 字体引用, bool 是否可安全优化, error 读取或取消错误 +func scanEditorResources(ctx context.Context, reader *Reader, parts map[string][]byte, generated map[string]editorGeneratedReferences, names map[string]bool, progress editorProgress, collect bool) (*editorResourceRefs, []string, map[string]*editorResourceRefs, bool, error) { + refs := &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage), roles: make(map[string]int), documents: make(map[string]bool), versions: make(map[string]bool)} + var resources []string + var references map[string]*editorResourceRefs + if collect { + references = make(map[string]*editorResourceRefs) + } + ordered := slices.Sorted(maps.Keys(names)) + actual := make(map[string]string, len(names)) + for _, name := range ordered { + key := cleanPackagePath(name) + if prior, ok := actual[key]; ok && prior != name { + return nil, nil, nil, false, nil + } + actual[key] = name + } + queue := []string{"OFD.xml"} + if reader != nil && reader.OFD.DocBody[reader.documentIndex].versioned { + entry, err := reader.DocumentRoot() + if err != nil { + return nil, nil, nil, false, err + } + queue = append(queue, entry) + } + seen := make(map[string]bool) + sharedRefs := *refs + position, pending, completed := 0, 0, 0 + for pending < len(queue) || position < len(ordered) { + if err := ctx.Err(); err != nil { + return nil, nil, nil, false, err + } + var key string + if pending < len(queue) { + key = cleanPackagePath(queue[pending]) + pending++ + } else { + key = cleanPackagePath(ordered[position]) + position++ + } + name, included := actual[key] + if !included { + if key != "OFD.xml" || reader == nil { + continue + } + name = key + } + if seen[key] || strings.HasSuffix(name, "/") || refs.roles[key] == packageReferenceData { + continue + } + seen[key] = true + if included { + if err := progress.report("references", completed, len(ordered)); err != nil { + return nil, nil, nil, false, err + } + completed++ + } + data, replaced := parts[name] + known := generated[name] + local := known.refs + resource, safe, shared := false, true, false + if !replaced || local == nil || !known.matches(data) { + shared = included && !collect + if shared { + sharedRefs.roles = nil + local = &sharedRefs + } else { + local = &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} + } + var input io.ReadCloser + if replaced { + if data == nil && known.staged != nil { + input = known.staged.open() + } else { + input = io.NopCloser(bytes.NewReader(data)) + } + } else { + var err error + input, err = reader.openFile(name) + if err != nil { + return nil, nil, nil, false, err + } + } + resource, safe = local.scan(imageInput{ReadCloser: input, context: ctx}, name) + input.Close() + } + if err := ctx.Err(); err != nil { + return nil, nil, nil, false, err + } + if !safe { + return nil, nil, nil, false, nil + } + for _, target := range slices.Sorted(maps.Keys(local.roles)) { + role := local.roles[target] + refs.roles[target] |= role + if role&packageReferenceXML != 0 { + queue = append(queue, target) + } + } + if !included { + continue + } + if !shared { + refs.merge(local) + } + if resource { + resources = append(resources, name) + } + if collect && (resource || len(local.fonts) != 0) { + references[name] = local + } + } + for name, role := range refs.roles { + if role&packageReferenceData != 0 && seen[name] && actual[name] != "" { + return nil, nil, nil, false, nil + } + } + if err := progress.report("references", len(ordered), len(ordered)); err != nil { + return nil, nil, nil, false, err + } + return refs, resources, references, true, ctx.Err() +} diff --git a/ofdgo_editor_resources.go b/ofdgo_editor_resources.go index a630ee3..ca34fb3 100644 --- a/ofdgo_editor_resources.go +++ b/ofdgo_editor_resources.go @@ -42,6 +42,7 @@ type editorResourceRefs struct { fonts map[string]*editorFontUsage versions map[string]bool documents map[string]bool + roles map[string]int } // compactSourceReferences 合并自产页面引用,版本文档按当前版本范围扫描 @@ -83,53 +84,14 @@ func (e *Editor) compactSourceReferences(parts map[string][]byte, progress edito } } } - refs := editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} - var resources []string - ordered := slices.Sorted(maps.Keys(names)) - for i, name := range ordered { - if err := progress.report("references", i, len(ordered)); err != nil { - return nil, err - } - if strings.HasSuffix(name, "/") { - continue - } - data, replaced := parts[name] - if known, ok := generated[name]; ok && replaced && known.refs != nil && known.matches(data) { - refs.merge(known.refs) - continue - } - var input io.ReadCloser - if replaced { - if known := generated[name]; data == nil && known.staged != nil { - input = known.staged.open() - } else { - input = io.NopCloser(bytes.NewReader(data)) - } - } else { - var err error - input, err = reader.openFile(name) - if err != nil { - return nil, err - } - } - if e.output != nil { - input = imageInput{ReadCloser: input, context: e.output.ctx} - } - resource, safe := refs.scan(input, name) - input.Close() - if e.output != nil && e.output.ctx.Err() != nil { - return nil, e.output.ctx.Err() - } - if !safe { - return nil, nil - } - if resource { - resources = append(resources, name) - } - } - if err := progress.report("references", len(ordered), len(ordered)); err != nil { + refs, resources, _, safe, err := scanEditorResources(ctx, reader, parts, generated, names, progress, false) + if err != nil { return nil, err } + if !safe { + return nil, nil + } + ordered := slices.Sorted(maps.Keys(names)) removed := maps.Clone(e.source.retiredAttachments) if removed == nil { removed = make(map[string]bool)