diff --git a/ofdgo_compression_fonts.go b/ofdgo_compression_fonts.go index e30cc36..b6037d2 100644 --- a/ofdgo_compression_fonts.go +++ b/ofdgo_compression_fonts.go @@ -27,7 +27,7 @@ func (r *Reader) compressionFontUsage(ctx context.Context, documents map[string] selected := "" if r.OFD.DocBody[r.documentIndex].versioned { var err error - selected, err = r.DocumentRoot() + selected, err = r.DocumentRootContext(ctx) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func (r *Reader) compressionFontUsage(ctx context.Context, documents map[string] if err != nil { return nil, err } - root, err := parseEditorXML(data) + root, err := parseEditorXMLContext(ctx, data) if err != nil { return nil, err } diff --git a/ofdgo_editor_documents.go b/ofdgo_editor_documents.go index bc22829..3a8b375 100644 --- a/ofdgo_editor_documents.go +++ b/ofdgo_editor_documents.go @@ -333,7 +333,7 @@ func (e *Editor) editDocumentsContext(ctx context.Context, edit func(*Reader, [] } before := e.transactionSnapshot() reader.documentIndex, reader.doc = index, nil - if err := reader.initRoot(); err != nil { + if err := reader.initRootContext(ctx); err != nil { return err } current := reader.OFD.DocBody[index] diff --git a/ofdgo_editor_font_binding.go b/ofdgo_editor_font_binding.go index 4aff1d5..b40efbe 100644 --- a/ofdgo_editor_font_binding.go +++ b/ofdgo_editor_font_binding.go @@ -497,7 +497,7 @@ func (e *Editor) commitFontSnapshot(ctx context.Context, reader *Reader) error { return err } clean := &Reader{Zip: reader.Zip, files: maps.Clone(reader.files), documentIndex: reader.DocumentIndex(), encryption: e.encryption, selectedVersion: reader.selectedVersion} - if err := clean.initRoot(); err != nil { + if err := clean.initRootContext(ctx); err != nil { return err } next, err := clean.Editor() @@ -571,13 +571,13 @@ func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidates ...s if files[name] && !structures[name] { continue } - data, err := reader.readFile(name) + data, err := reader.readFileViewContext(ctx, name) if err != nil { return err } - root, err := parseEditorXML(data) + root, err := parseEditorXMLContext(ctx, data) if err != nil { - return nil + return ctx.Err() } base := "" if root.matchesOFD("Res") { @@ -588,6 +588,9 @@ func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidates ...s } var retains func(*editorXML) bool retains = func(node *editorXML) bool { + if ctx.Err() != nil { + return true + } if classifyOFDNamespace(node.name.Space) == ofdXMLUnknown || slices.Contains([]string{"CustomTags", "Extensions", "ExtendData", "Data"}, node.name.Local) { return true } @@ -617,7 +620,7 @@ func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidates ...s return slices.ContainsFunc(node.children, retains) } if retains(root) || len(removed) == 0 { - return nil + return ctx.Err() } } if err := ctx.Err(); err != nil { @@ -625,9 +628,15 @@ func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidates ...s } updates := make(map[string][]byte) for name, file := range versions { + if err := ctx.Err(); err != nil { + return err + } var patches []editorXMLPatch if list := file.root.child("FileList"); list != nil { for _, node := range list.children { + if err := ctx.Err(); err != nil { + return err + } if node.matchesOFD("File") && removed[resolveResourcePath(name, "", strings.TrimSpace(editorImportText(file.data, node)))] { patches = append(patches, editorXMLPatch{start: node.start, end: node.end}) } @@ -640,17 +649,26 @@ func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidates ...s if err := ctx.Err(); err != nil { return err } - if reader.files == nil { - reader.files = make(map[string][]byte) + view := *reader + view.files = maps.Clone(reader.files) + if view.files == nil { + view.files = make(map[string][]byte) } - maps.Copy(reader.files, updates) - for name := range reader.files { + maps.Copy(view.files, updates) + for name := range view.files { + if err := ctx.Err(); err != nil { + return err + } if removed[cleanPackagePath(name)] { - delete(reader.files, name) + delete(view.files, name) } } - if reader.Zip != nil { - reader.Zip = &zip.Reader{Comment: reader.Zip.Comment, File: slices.DeleteFunc(slices.Clone(reader.Zip.File), func(file *zip.File) bool { return removed[cleanPackagePath(file.Name)] })} + if view.Zip != nil { + view.Zip = &zip.Reader{Comment: view.Zip.Comment, File: slices.DeleteFunc(slices.Clone(view.Zip.File), func(file *zip.File) bool { return removed[cleanPackagePath(file.Name)] })} } - return reader.initRoot() + if err := view.initRootContext(ctx); err != nil { + return err + } + *reader = view + return nil } diff --git a/ofdgo_editor_font_cleanup.go b/ofdgo_editor_font_cleanup.go index 142766d..404796a 100644 --- a/ofdgo_editor_font_cleanup.go +++ b/ofdgo_editor_font_cleanup.go @@ -43,13 +43,13 @@ func (e *Editor) compactVersionFonts(ctx context.Context, parts map[string][]byt view.files = make(map[string][]byte) } maps.Copy(view.files, parts) - if err := view.initRoot(); err != nil { + if err := view.initRootContext(ctx); err != nil { return err } candidates, seen := make(map[string]bool), make(map[string]bool) for index := range view.DocumentCount() { view.documentIndex = index - versions, err := view.DocumentVersions() + versions, err := view.DocumentVersionsContext(ctx) if err != nil { return err } diff --git a/ofdgo_editor_font_scan.go b/ofdgo_editor_font_scan.go index 739c662..c61cc7b 100644 --- a/ofdgo_editor_font_scan.go +++ b/ofdgo_editor_font_scan.go @@ -54,7 +54,7 @@ type editorFontDeclaration struct { // 返回: *editorFontDocument 字体引用, error 读取或结构错误 func scanEditorFonts(ctx context.Context, reader *Reader, index int) (*editorFontDocument, error) { scan := &editorFontDocument{reader: reader, index: index, files: make(map[string]*editorFontXML), fonts: make(map[string]*editorFontDeclaration)} - entry, err := reader.documentEntry(index) + entry, err := reader.documentEntryContext(ctx, index) if err != nil { return nil, err } @@ -68,11 +68,11 @@ func scanEditorFonts(ctx context.Context, reader *Reader, index int) (*editorFon if scan.files[name] != nil { continue } - data, err := reader.readFile(name) + data, err := reader.readFileViewContext(ctx, name) if err != nil { return nil, err } - root, err := parseEditorXML(data) + root, err := parseEditorXMLContext(ctx, data) if err != nil { return nil, err } @@ -84,6 +84,9 @@ func scanEditorFonts(ctx context.Context, reader *Reader, index int) (*editorFon } var visit func(*editorXML) error visit = func(node *editorXML) error { + if err := ctx.Err(); err != nil { + return err + } if classifyOFDNamespace(node.name.Space) == ofdXMLUnknown { scan.unsafe = true return nil @@ -176,7 +179,7 @@ func (s *editorFontDocument) apply(ctx context.Context, changes map[string][]byt next.files = make(map[string][]byte) } maps.Copy(next.files, parts) - if err := next.initRoot(); err != nil { + if err := next.initRootContext(ctx); err != nil { return err } if err := ctx.Err(); err != nil { diff --git a/ofdgo_editor_package.go b/ofdgo_editor_package.go index 5cafde4..319cf32 100644 --- a/ofdgo_editor_package.go +++ b/ofdgo_editor_package.go @@ -761,7 +761,7 @@ func (e *Editor) sourceReader(ctx context.Context, progress editorProgress) (*Re } maps.Copy(files, parts) reader := &Reader{Zip: e.source.reader.Zip, files: files, encryption: e.encryption, documentIndex: e.source.reader.documentIndex, selectedVersion: e.source.reader.selectedVersion} - if err := reader.initRoot(); err != nil { + if err := reader.initRootContext(ctx); err != nil { return nil, err } if _, err := reader.Doc(); err != nil { diff --git a/ofdgo_editor_resource_scan.go b/ofdgo_editor_resource_scan.go index 88141c9..4ea602d 100644 --- a/ofdgo_editor_resource_scan.go +++ b/ofdgo_editor_resource_scan.go @@ -44,7 +44,7 @@ func scanEditorResources(ctx context.Context, reader *Reader, parts map[string][ } queue := []string{"OFD.xml"} if reader != nil && reader.OFD.DocBody[reader.documentIndex].versioned { - entry, err := reader.DocumentRoot() + entry, err := reader.DocumentRootContext(ctx) if err != nil { return nil, nil, nil, false, err } diff --git a/ofdgo_editor_resources.go b/ofdgo_editor_resources.go index 1b78a6a..9f99481 100644 --- a/ofdgo_editor_resources.go +++ b/ofdgo_editor_resources.go @@ -107,7 +107,7 @@ func (e *Editor) compactResourceReferences(parts map[string][]byte, reader *Read for _, name := range resources { data, ok := parts[name] if !ok { - data, err = reader.readFile(name) + data, err = reader.readFileViewContext(ctx, name) if err != nil { return nil, err } @@ -122,7 +122,7 @@ func (e *Editor) compactResourceReferences(parts map[string][]byte, reader *Read if multiple || prune { selected := "" if reader != nil && reader.OFD.DocBody[reader.documentIndex].versioned { - selected, err = reader.DocumentRoot() + selected, err = reader.DocumentRootContext(ctx) if err != nil { return nil, err } diff --git a/ofdgo_editor_versions.go b/ofdgo_editor_versions.go index ac55781..934744d 100644 --- a/ofdgo_editor_versions.go +++ b/ofdgo_editor_versions.go @@ -129,7 +129,7 @@ func (e *Editor) AddVersionContext(ctx context.Context, name string) (string, er break } } - entry, err := reader.DocumentRoot() + entry, err := reader.DocumentRootContext(ctx) if err != nil { return 0, err } @@ -384,7 +384,7 @@ func (r *Reader) versionSnapshotFiles(ctx context.Context) (map[string]bool, err if err := ctx.Err(); err != nil { return nil, err } - entry, err := r.DocumentRoot() + entry, err := r.DocumentRootContext(ctx) if err != nil { return nil, err } @@ -444,7 +444,7 @@ func (r *Reader) retireVersionFiles(ctx context.Context, candidates map[string]b return err } view := *r - if err := view.initRoot(); err != nil { + if err := view.initRootContext(ctx); err != nil { return err } indexes := make([]int, view.DocumentCount()) diff --git a/ofdgo_reader.go b/ofdgo_reader.go index 103c3a3..b96b4b4 100644 --- a/ofdgo_reader.go +++ b/ofdgo_reader.go @@ -457,17 +457,30 @@ func (r *Reader) Extensions() ([]Extension, error) { // initRoot 读取根节点信息 // 返回: error 错误信息 func (r *Reader) initRoot() error { - if err := r.indexPackage(); err != nil { + return r.initRootContext(context.Background()) +} + +// initRootContext 重建包索引与主入口,取消或失败时保留原阅读状态 +// 入参: ctx 取消上下文 +// 返回: error 索引、读取、解析或取消错误 +func (r *Reader) initRootContext(ctx context.Context) error { + view := *r + if err := view.indexPackageContext(ctx); err != nil { return err } - data, err := r.readFile("OFD.xml") + data, err := view.readFileViewContext(ctx, "OFD.xml") if err != nil { return fmt.Errorf("failed to read ofd.xml: %w", err) } var ofd OFD - if err := xml.Unmarshal(data, &ofd); err != nil { + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + if err := decoder.Decode(&ofd); err != nil { return fmt.Errorf("failed to unmarshal ofd.xml: %w", err) } + if err := ctx.Err(); err != nil { + return err + } + r.fileIndex, r.fileNames = view.fileIndex, view.fileNames r.OFD = &ofd r.doc, r.versionInfo = nil, nil r.documentRoot, r.RootDir = "", "" @@ -490,14 +503,27 @@ func (r *Reader) initRoot() error { // indexPackage 按大小写敏感路径建立包索引,拒绝规范化后重复的ZIP条目 // 返回: error 错误信息 func (r *Reader) indexPackage() error { + return r.indexPackageContext(context.Background()) +} + +// indexPackageContext 建立大小写敏感的包索引,取消或路径校验失败时保留原索引 +// 入参: ctx 取消上下文 +// 返回: error 路径或取消错误 +func (r *Reader) indexPackageContext(ctx context.Context) error { + if err := ctx.Err(); err != nil { + return err + } count := 0 if r.Zip != nil { count = len(r.Zip.File) } - r.fileIndex = make(map[string]*zip.File, count) - r.fileNames = make(map[string]bool, count+len(r.files)) + index := make(map[string]*zip.File, count) + names := make(map[string]bool, count+len(r.files)) if r.Zip != nil { for _, f := range r.Zip.File { + if err := ctx.Err(); err != nil { + return err + } if f.FileInfo().IsDir() { continue } @@ -505,19 +531,26 @@ func (r *Reader) indexPackage() error { if !validPackagePath(name) { return fmt.Errorf("invalid package path: %q", f.Name) } - if previous, ok := r.fileIndex[name]; ok { + if previous, ok := index[name]; ok { return fmt.Errorf("ambiguous package paths: %q and %q", previous.Name, f.Name) } - r.fileIndex[name] = f - r.fileNames[name] = true + index[name] = f + names[name] = true } } for name := range r.files { + if err := ctx.Err(); err != nil { + return err + } if name != cleanPackagePath(name) || !validPackagePath(name) { return fmt.Errorf("invalid package path: %q", name) } - r.fileNames[name] = true + names[name] = true } + if err := ctx.Err(); err != nil { + return err + } + r.fileIndex, r.fileNames = index, names return nil } diff --git a/ofdgo_version_edit.go b/ofdgo_version_edit.go index 542f020..de95987 100644 --- a/ofdgo_version_edit.go +++ b/ofdgo_version_edit.go @@ -203,7 +203,7 @@ func (r *Reader) versionResourceFiles(ctx context.Context, parts map[string][]by if r == nil || !r.OFD.DocBody[r.documentIndex].versioned { return nil, nil } - entry, err := r.DocumentRoot() + entry, err := r.DocumentRootContext(ctx) if err != nil { return nil, err } @@ -292,7 +292,7 @@ func (r *Reader) versionOutputChanges(ctx context.Context, changes map[string][] if err := ctx.Err(); err != nil { return nil, err } - entry, err := r.DocumentRoot() + entry, err := r.DocumentRootContext(ctx) if err != nil { return nil, err } diff --git a/ofdgo_version_reader.go b/ofdgo_version_reader.go index c7ca97f..c391f3d 100644 --- a/ofdgo_version_reader.go +++ b/ofdgo_version_reader.go @@ -14,12 +14,26 @@ package ofdgo -import "fmt" +import ( + "context" + "fmt" +) // DocumentRoot 获取当前阅读入口的包内路径,不改写OFD中的主入口 // 未显式选择时使用唯一的Current版本;没有默认版本时使用主入口,多个默认版本返回错误 // 返回: string 阅读入口, error 版本选择或描述错误 func (r *Reader) DocumentRoot() (string, error) { + return r.DocumentRootContext(context.Background()) +} + +// DocumentRootContext 获取当前阅读入口,解析版本列表和文件清单时响应取消 +// 取消或失败时不缓存本次选择结果,入口选择规则与DocumentRoot相同 +// 入参: ctx 取消上下文 +// 返回: string 阅读入口, error 版本选择、读取或取消错误 +func (r *Reader) DocumentRootContext(ctx context.Context) (string, error) { + if err := ctx.Err(); err != nil { + return "", err + } if r.documentRoot != "" { return r.documentRoot, nil } @@ -34,12 +48,15 @@ func (r *Reader) DocumentRoot() (string, error) { } return root, err } - entries, err := r.documentVersionEntries() + entries, err := r.documentVersionEntriesContext(ctx) if err != nil { return "", err } var selected *DocumentVersionInfo for i := range entries { + if err := ctx.Err(); err != nil { + return "", err + } entry := &entries[i] if r.selectedVersion != nil && entry.ID == *r.selectedVersion || r.selectedVersion == nil && entry.Current { if selected != nil { @@ -58,11 +75,14 @@ func (r *Reader) DocumentRoot() (string, error) { } return root, err } - info, err := r.readDocumentVersion(selected.ID, selected.Location) + info, err := r.readDocumentVersionContext(ctx, selected.ID, selected.Location) if err != nil { return "", fmt.Errorf("version %s: %w", selected.ID, err) } info.Index, info.Current = selected.Index, selected.Current + if err := ctx.Err(); err != nil { + return "", err + } r.documentRoot, r.versionInfo = info.DocRoot, &info return r.documentRoot, nil } @@ -98,13 +118,20 @@ func (r *Reader) DocumentVersion(id string) (*Reader, error) { // 入参: index 文档索引 // 返回: string 包内路径, error 选择错误 func (r *Reader) documentEntry(index int) (string, error) { + return r.documentEntryContext(context.Background(), index) +} + +// documentEntryContext 解析指定文档的阅读入口,当前文档保留显式选择 +// 入参: ctx 取消上下文, index 文档索引 +// 返回: string 包内路径, error 选择、读取或取消错误 +func (r *Reader) documentEntryContext(ctx context.Context, index int) (string, error) { if index == r.documentIndex { - return r.DocumentRoot() + return r.DocumentRootContext(ctx) } view := *r view.documentIndex, view.documentRoot = index, "" view.selectedVersion, view.versionInfo = nil, nil - return view.DocumentRoot() + return view.DocumentRootContext(ctx) } // versionSignatures 按OFD入口解析包级签名路径,选中版本仅使用文件清单内的签名入口 diff --git a/ofdgo_writer.go b/ofdgo_writer.go index 0434fe9..c1c19a6 100644 --- a/ofdgo_writer.go +++ b/ofdgo_writer.go @@ -273,7 +273,7 @@ func (e *Editor) readerContext(ctx context.Context, progress editorProgress) (*R }, progress); err != nil { return nil, err } - if err := r.initRoot(); err != nil { + if err := r.initRootContext(ctx); err != nil { return nil, err } if _, err := r.Doc(); err != nil {