mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:44:23 +08:00
1 parent
3f797e656b
commit
91e5b57e24
43 files changed
+2018
-384
No files matched your search
+376
-41
@@ -19,15 +19,17 @@ import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// pruneStructureReferences 清理本次编辑删除的公文图元引用,未知标引和原有悬空引用保持原样
|
||||
// pruneStructureReferences 迁移公文图元编号并清理已删除的引用,保留未知标引和原有悬空引用
|
||||
// 改动进入输出快照,由既有文档及版本隔离流程维护共享文件
|
||||
// 入参: parts 输出覆盖条目
|
||||
// 入参: parts 输出覆盖条目, generated 本次编码结果及扫描记录
|
||||
// 返回: error 读取、解析或取消错误
|
||||
func (e *Editor) pruneStructureReferences(parts map[string][]byte) error {
|
||||
func (e *Editor) pruneStructureReferences(parts map[string][]byte, generated map[string]editorGeneratedReferences) error {
|
||||
if e.source.document.CustomTags.Path == "" && len(e.source.document.CustomTags.CustomTag) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -35,43 +37,144 @@ func (e *Editor) pruneStructureReferences(parts map[string][]byte) error {
|
||||
if e.output != nil {
|
||||
ctx = e.output.ctx
|
||||
}
|
||||
tags, err := e.source.reader.CustomTagsContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !slices.ContainsFunc(tags, func(tag CustomTag) bool {
|
||||
return structureOfficial(tag, tag.NameSpace)
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
pages := make(map[string]bool, len(e.pages))
|
||||
for _, page := range e.pages {
|
||||
pages[editorResourceID(page.ID)] = true
|
||||
}
|
||||
removedPages := make(map[string]bool)
|
||||
removedObjects := make(map[string]map[string]bool)
|
||||
pageRemaps := make(map[string]map[string][]string)
|
||||
remainingObjects := make(map[string]map[string]bool)
|
||||
var snapshot *Reader
|
||||
var snapshotPages map[string]Page
|
||||
sources := make(map[string]*editorSourcePage, len(e.source.pages)+len(e.source.structureObjects))
|
||||
for id, source := range e.source.pages {
|
||||
key := editorResourceID(id)
|
||||
sources[editorResourceID(id)] = source
|
||||
}
|
||||
for id := range e.source.structureObjects {
|
||||
if _, ok := sources[id]; !ok {
|
||||
sources[id] = nil
|
||||
}
|
||||
}
|
||||
for key, source := range sources {
|
||||
if !pages[key] {
|
||||
removedPages[key] = true
|
||||
continue
|
||||
}
|
||||
data, changed := parts[e.source.reader.ResPath(source.ref.BaseLoc)]
|
||||
if !changed || source.original == nil {
|
||||
known := e.source.structureObjects[key]
|
||||
changed := false
|
||||
if source != nil {
|
||||
_, changed = parts[e.source.reader.ResPath(source.ref.BaseLoc)]
|
||||
changed = changed && source.original != nil
|
||||
}
|
||||
if !changed && len(known) == 0 {
|
||||
continue
|
||||
}
|
||||
root, err := parseEditorXMLContext(ctx, data)
|
||||
if snapshot == nil {
|
||||
reader := e.source.reader
|
||||
files := make(map[string][]byte, len(reader.files)+len(parts))
|
||||
maps.Copy(files, reader.files)
|
||||
maps.Copy(files, parts)
|
||||
snapshot = &Reader{Zip: reader.Zip, files: files, encryption: e.encryption, documentIndex: reader.documentIndex, selectedVersion: reader.selectedVersion}
|
||||
if err := snapshot.initRootContext(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
doc, err := snapshot.docStructureContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snapshotPages = make(map[string]Page, len(doc.Pages.Page))
|
||||
for _, page := range doc.Pages.Page {
|
||||
snapshotPages[editorResourceID(page.ID)] = page
|
||||
}
|
||||
}
|
||||
original := make(map[string]bool)
|
||||
if source != nil {
|
||||
var err error
|
||||
original, err = structurePageObjectIDs(ctx, e.source.reader, source.ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
maps.Copy(original, known)
|
||||
page := snapshotPages[key]
|
||||
name := snapshot.ResPath(page.BaseLoc)
|
||||
scanned := generated[name]
|
||||
remaining := scanned.objects
|
||||
if remaining == nil || !scanned.matches(parts[name]) {
|
||||
var err error
|
||||
remaining, err = structurePageObjectIDs(ctx, snapshot, page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if snapshot.doc.Annotations != "" {
|
||||
if _, err := snapshot.DocContext(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
visitor := &structureObjectVisitor{objects: maps.Clone(remaining)}
|
||||
renderer := NewRenderer(snapshot)
|
||||
renderer.pageContext = ctx
|
||||
for _, annotation := range snapshot.Annots[page.ID] {
|
||||
if err := renderer.walkAnnotation(annotation, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
remaining = visitor.objects
|
||||
}
|
||||
remainingObjects[key] = remaining
|
||||
links, err := structureRemapLinks(ctx, e.structureRemaps[key])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remaining := make(map[string]bool)
|
||||
var scan func(*editorXML)
|
||||
scan = func(node *editorXML) {
|
||||
if classifyOFDNamespace(node.name.Space) == ofdXMLUnknown {
|
||||
return
|
||||
}
|
||||
if id := editorResourceID(node.attr("ID")); id != "" {
|
||||
remaining[id] = true
|
||||
}
|
||||
for _, child := range node.children {
|
||||
scan(child)
|
||||
related := make(map[string][]string)
|
||||
for from, targets := range links {
|
||||
for _, to := range targets {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
related[from] = append(related[from], to)
|
||||
related[to] = append(related[to], from)
|
||||
}
|
||||
}
|
||||
scan(root)
|
||||
for id := range source.nodes {
|
||||
id = editorResourceID(id)
|
||||
if id != "" && !remaining[id] {
|
||||
for id := range remaining {
|
||||
if len(related[id]) != 0 {
|
||||
original[id] = true
|
||||
}
|
||||
}
|
||||
pending := slices.Collect(maps.Keys(original))
|
||||
for len(pending) != 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
id := pending[len(pending)-1]
|
||||
pending = pending[:len(pending)-1]
|
||||
for _, next := range related[id] {
|
||||
if !original[next] {
|
||||
original[next] = true
|
||||
pending = append(pending, next)
|
||||
}
|
||||
}
|
||||
}
|
||||
for id := range original {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(links[id]) != 0 {
|
||||
if pageRemaps[key] == nil {
|
||||
pageRemaps[key] = make(map[string][]string)
|
||||
}
|
||||
pageRemaps[key][id] = links[id]
|
||||
}
|
||||
if !remaining[id] {
|
||||
if removedObjects[key] == nil {
|
||||
removedObjects[key] = make(map[string]bool)
|
||||
}
|
||||
@@ -79,52 +182,281 @@ func (e *Editor) pruneStructureReferences(parts map[string][]byte) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(removedPages) == 0 && len(removedObjects) == 0 {
|
||||
if len(removedPages) == 0 && len(removedObjects) == 0 && len(pageRemaps) == 0 {
|
||||
return nil
|
||||
}
|
||||
tags, err := e.source.reader.CustomTags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protected := make(map[string]bool)
|
||||
for _, tag := range tags {
|
||||
if !structureOfficial(tag, tag.NameSpace) {
|
||||
protected[e.source.reader.ResPath(tag.FileLoc)] = true
|
||||
}
|
||||
}
|
||||
isolated := make(map[string]string)
|
||||
originals := make(map[string][]byte)
|
||||
redirects := make(map[int]string)
|
||||
for index, tag := range tags {
|
||||
if !structureOfficial(tag, tag.NameSpace) {
|
||||
continue
|
||||
}
|
||||
name := e.source.reader.ResPath(tag.FileLoc)
|
||||
data, ok := parts[name]
|
||||
data, ok := originals[name]
|
||||
if !ok {
|
||||
data, err = e.source.reader.readFileViewContext(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
data, ok = parts[name]
|
||||
if !ok {
|
||||
data, err = e.source.reader.readFileViewContext(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
originals[name] = data
|
||||
}
|
||||
remaps := pageRemaps
|
||||
if starts := e.source.structureStarts[index]; len(starts) != 0 {
|
||||
remaps = maps.Clone(pageRemaps)
|
||||
for page, start := range starts {
|
||||
links, err := structureRemapLinks(ctx, e.structureRemaps[page][start:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remaps[page] = links
|
||||
}
|
||||
}
|
||||
updated, err := rewriteStructureReferences(ctx, data, tag, func(page string, ids []string) (string, []string) {
|
||||
mappedObjects := make(map[string]map[string][]string)
|
||||
updated, err := rewriteStructureReferences(ctx, data, tag, func(page string, ids []string) (string, []string, error) {
|
||||
key := editorResourceID(page)
|
||||
if removedPages[key] {
|
||||
return page, nil
|
||||
return page, nil, nil
|
||||
}
|
||||
kept := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if !removedObjects[key][editorResourceID(id)] {
|
||||
canonical := editorResourceID(id)
|
||||
if len(remaps[key][canonical]) != 0 {
|
||||
mapped, ok := mappedObjects[key][canonical]
|
||||
if !ok {
|
||||
var err error
|
||||
mapped, err = structureMappedIDs(ctx, canonical, remaps[key], remainingObjects[key])
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if mappedObjects[key] == nil {
|
||||
mappedObjects[key] = make(map[string][]string)
|
||||
}
|
||||
mappedObjects[key][canonical] = mapped
|
||||
}
|
||||
kept = append(kept, mapped...)
|
||||
} else if !removedObjects[key][canonical] {
|
||||
kept = append(kept, id)
|
||||
}
|
||||
}
|
||||
return page, kept
|
||||
return page, kept, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(data, updated) {
|
||||
if protected[name] {
|
||||
target := isolated[name]
|
||||
if target == "" {
|
||||
target = packageAvailableName(e.source.reader, parts, path.Join(e.packageDirectory(), "Tags", "Tag.xml"))
|
||||
isolated[name] = target
|
||||
}
|
||||
redirects[index] = target
|
||||
name = target
|
||||
}
|
||||
parts[name] = updated
|
||||
}
|
||||
}
|
||||
if len(redirects) != 0 {
|
||||
return e.redirectStructureEntries(parts, redirects)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// redirectStructureEntries 一次改写输出快照中的标引路径,保留同次页面及索引改动
|
||||
// 入参: parts 输出覆盖条目, targets 标引入口序号及新内容路径
|
||||
// 返回: error 索引读取或路径改写错误
|
||||
func (e *Editor) redirectStructureEntries(parts map[string][]byte, targets map[int]string) error {
|
||||
name, data, _, entries, err := editorStructureIndex(e.source, parts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
patches := make([]editorXMLPatch, 0, len(targets))
|
||||
for index, target := range targets {
|
||||
if index < 0 || index >= len(entries) {
|
||||
return fmt.Errorf("custom tag structure not found: %d", index)
|
||||
}
|
||||
loc := entries[index].child("FileLoc")
|
||||
if loc == nil {
|
||||
return fmt.Errorf("custom tag requires FileLoc")
|
||||
}
|
||||
var value bytes.Buffer
|
||||
if err := xml.EscapeText(&value, []byte("/"+target)); err != nil {
|
||||
return err
|
||||
}
|
||||
patches = append(patches, editorXMLContent(data, loc, value.Bytes()))
|
||||
}
|
||||
parts[name] = editorPatchXML(data, patches)
|
||||
return nil
|
||||
}
|
||||
|
||||
// remapStructureObjects 将图元修改和标引编号迁移作为一次操作提交,映射只作用于当前页
|
||||
// 入参: page 页面索引, remaps 按修改顺序记录的编号映射, edit 图元修改
|
||||
// 返回: error 图元修改错误
|
||||
func (e *Editor) remapStructureObjects(page int, remaps []map[string]string, edit func() error) error {
|
||||
if len(remaps) == 0 || e.source == nil || e.source.document.CustomTags.Path == "" && len(e.source.document.CustomTags.CustomTag) == 0 {
|
||||
return edit()
|
||||
}
|
||||
return e.Transaction(func(editor *Editor) error {
|
||||
if err := edit(); err != nil {
|
||||
return err
|
||||
}
|
||||
if editor.structureRemaps == nil {
|
||||
editor.structureRemaps = make(map[string][]map[string]string)
|
||||
}
|
||||
key := editorResourceID(editor.pages[page].ID)
|
||||
next := slices.Clone(editor.structureRemaps[key])
|
||||
for _, ids := range remaps {
|
||||
if len(ids) != 0 {
|
||||
next = append(next, maps.Clone(ids))
|
||||
}
|
||||
}
|
||||
editor.structureRemaps[key] = next
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// structureRemapStarts 记录各页面已应用的编号迁移位置,不与后续编辑共享可变状态
|
||||
// 返回: map[string]int 页面编号及已应用的迁移记录数
|
||||
func (e *Editor) structureRemapStarts() map[string]int {
|
||||
starts := make(map[string]int, len(e.structureRemaps))
|
||||
for page, remaps := range e.structureRemaps {
|
||||
starts[page] = len(remaps)
|
||||
}
|
||||
return starts
|
||||
}
|
||||
|
||||
// structureRemapLinks 合并按操作顺序记录的编号迁移,保留一对多关系并排除重复边
|
||||
// 入参: ctx 取消上下文, remaps 图元编号迁移记录
|
||||
// 返回: map[string][]string 原编号及其迁移目标, error 取消错误
|
||||
func structureRemapLinks(ctx context.Context, remaps []map[string]string) (map[string][]string, error) {
|
||||
links := make(map[string][]string)
|
||||
seen := make(map[[2]string]bool)
|
||||
for _, ids := range remaps {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for from, to := range ids {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := [2]string{from, to}
|
||||
if from == to || seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
links[from] = append(links[from], to)
|
||||
}
|
||||
}
|
||||
return links, ctx.Err()
|
||||
}
|
||||
|
||||
// structureMappedIDs 沿编号迁移关系收集当前仍可达的图元,同一来源的结果不重复
|
||||
// 入参: ctx 取消上下文, id 原编号, links 编号迁移关系, remaining 当前页面可达图元
|
||||
// 返回: []string 保留的原编号及迁移后的编号, error 取消错误
|
||||
func structureMappedIDs(ctx context.Context, id string, links map[string][]string, remaining map[string]bool) ([]string, error) {
|
||||
var result []string
|
||||
pending := []string{id}
|
||||
seen := make(map[string]bool)
|
||||
for len(pending) != 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id = pending[len(pending)-1]
|
||||
pending = pending[:len(pending)-1]
|
||||
if seen[id] {
|
||||
continue
|
||||
}
|
||||
seen[id] = true
|
||||
if remaining[id] {
|
||||
result = append(result, id)
|
||||
}
|
||||
for i := len(links[id]) - 1; i >= 0; i-- {
|
||||
pending = append(pending, links[id][i])
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// structurePageObjectIDs 收集页面、模板、复合资源及注释外观中的图元标识,不加载字体或图像
|
||||
// 入参: ctx 取消上下文, reader 资源已加载的阅读器, page 页面引用
|
||||
// 返回: map[string]bool 可达图元标识, error 页面读取或遍历错误
|
||||
func structurePageObjectIDs(ctx context.Context, reader *Reader, page Page) (map[string]bool, error) {
|
||||
if _, err := reader.DocContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := reader.PageContentContext(ctx, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
renderer := NewRenderer(reader, WithAnnotations(true))
|
||||
renderer.pageContext = ctx
|
||||
visitor := &structureObjectVisitor{objects: make(map[string]bool)}
|
||||
if err := renderer.WalkPage(content, visitor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return visitor.objects, nil
|
||||
}
|
||||
|
||||
// annotationStructureSource 记录被修改注释原有的外观图元,供保存时清理失效标引
|
||||
// 入参: page 页面索引, wanted 待修改注释标识
|
||||
// 返回: *editorSource 保留已知图元的包快照, error 读取或遍历错误
|
||||
func (e *Editor) annotationStructureSource(page int, wanted map[string]bool) (*editorSource, error) {
|
||||
base := e.source
|
||||
if base.document.CustomTags.Path == "" && len(base.document.CustomTags.CustomTag) == 0 {
|
||||
return base, nil
|
||||
}
|
||||
tags, err := base.reader.CustomTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !slices.ContainsFunc(tags, func(tag CustomTag) bool { return structureOfficial(tag, tag.NameSpace) }) {
|
||||
return base, nil
|
||||
}
|
||||
if _, err := base.reader.Doc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if source := base.pages[e.pages[page].ID]; source != nil {
|
||||
if _, err := base.reader.PageContent(source.ref); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
visitor := &structureObjectVisitor{objects: make(map[string]bool)}
|
||||
renderer := NewRenderer(base.reader)
|
||||
for _, annotation := range base.reader.Annots[e.pages[page].ID] {
|
||||
if wanted[editorResourceID(annotation.ID)] {
|
||||
if err := renderer.walkAnnotation(annotation, visitor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
next := *base
|
||||
next.structureObjects = maps.Clone(base.structureObjects)
|
||||
if next.structureObjects == nil {
|
||||
next.structureObjects = make(map[string]map[string]bool)
|
||||
}
|
||||
key := editorResourceID(e.pages[page].ID)
|
||||
maps.Copy(visitor.objects, next.structureObjects[key])
|
||||
next.structureObjects[key] = visitor.objects
|
||||
return &next, nil
|
||||
}
|
||||
|
||||
// rewriteStructureReferences 仅改写已识别公文命名空间的叶子引用,保留其他XML字节
|
||||
// 入参: ctx 取消上下文, data 原始标引, tag 标引入口, rewrite 引用映射,空标识列表表示删除引用
|
||||
// 返回: []byte 修改后的标引, error 解析或取消错误
|
||||
func rewriteStructureReferences(ctx context.Context, data []byte, tag CustomTag, rewrite func(string, []string) (string, []string)) ([]byte, error) {
|
||||
func rewriteStructureReferences(ctx context.Context, data []byte, tag CustomTag, rewrite func(string, []string) (string, []string, error)) ([]byte, error) {
|
||||
root, err := parseEditorXMLContext(ctx, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -152,7 +484,10 @@ func rewriteStructureReferences(ctx context.Context, data []byte, tag CustomTag,
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
page, next := rewrite(node.attr("PageRef"), ids)
|
||||
page, next, err := rewrite(node.attr("PageRef"), ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(next) == 0 {
|
||||
patches = append(patches, editorXMLPatch{start: node.start, end: node.end})
|
||||
return nil
|
||||
@@ -190,7 +525,7 @@ func rewriteStructureReferences(ctx context.Context, data []byte, tag CustomTag,
|
||||
// structures 迁移所选页面的公文标引,按已迁移图元映射引用,不猜测其他行业的引用规则
|
||||
// 返回: []byte 标引索引条目, error 读取、解析或取消错误
|
||||
func (m *editorPageImport) structures() ([]byte, error) {
|
||||
tags, err := m.reader.CustomTags()
|
||||
tags, err := m.reader.CustomTagsContext(m.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -204,10 +539,10 @@ func (m *editorPageImport) structures() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
count := 0
|
||||
updated, err := rewriteStructureReferences(m.ctx, data, tag, func(page string, ids []string) (string, []string) {
|
||||
updated, err := rewriteStructureReferences(m.ctx, data, tag, func(page string, ids []string) (string, []string, error) {
|
||||
key := editorImportKey(page)
|
||||
if !m.pages[key] {
|
||||
return page, nil
|
||||
return page, nil, nil
|
||||
}
|
||||
var mapped []string
|
||||
for _, id := range ids {
|
||||
@@ -216,7 +551,7 @@ func (m *editorPageImport) structures() ([]byte, error) {
|
||||
}
|
||||
}
|
||||
count += len(mapped)
|
||||
return m.ids[key], mapped
|
||||
return m.ids[key], mapped, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in new issue
Block a user