mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
584 lines
22 KiB
Go
584 lines
22 KiB
Go
// 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"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
// Structure 获取当前编辑状态的标引,包括页面和图元删除后的引用调整
|
|
// 返回: []Structure 标引结构, error 快照或解析错误
|
|
func (e *Editor) Structure() ([]Structure, error) {
|
|
r, err := e.Reader()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer r.Close()
|
|
return r.Structure()
|
|
}
|
|
|
|
// AddStructure 添加独立标引及根节点,FileLoc由库分配,不接收外部内容路径
|
|
// SchemaLoc仅允许引用包内已有文件,不访问网络或验证业务Schema
|
|
// 入参: tag 类型、命名空间及可选Schema位置, root 根节点
|
|
// 返回: int 从0开始的标引索引, error 校验或提交错误
|
|
func (e *Editor) AddStructure(tag CustomTag, root StructureNode) (int, error) {
|
|
data, err := encodeStructureNode(root)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return e.addStructureXML(context.Background(), tag, data)
|
|
}
|
|
|
|
// AddStructureXML 添加XML标引原文,保留处理指令、注释、前缀及混合文本
|
|
// FileLoc由库分配,SchemaLoc仅引用包内已有文件,返回后不再引用输入数据
|
|
// 入参: tag 标引入口, data 完整XML
|
|
// 返回: int 从0开始的标引索引, error 解析、引用或提交错误
|
|
func (e *Editor) AddStructureXML(tag CustomTag, data []byte) (int, error) {
|
|
return e.AddStructureXMLContext(context.Background(), tag, data)
|
|
}
|
|
|
|
// AddStructureXMLContext 校验并保存XML标引原文,取消或失败时不提交编辑状态
|
|
// 入参: ctx 取消上下文, tag 标引入口, data 完整XML
|
|
// 返回: int 从0开始的标引索引, error 解析、引用、提交或取消错误
|
|
func (e *Editor) AddStructureXMLContext(ctx context.Context, tag CustomTag, data []byte) (int, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return 0, err
|
|
}
|
|
return e.addStructureXML(ctx, tag, bytes.Clone(data))
|
|
}
|
|
|
|
// addStructureXML 接管独立XML数据并提交标引,复用编码或解码结果而不再次复制
|
|
// 入参: ctx 取消上下文, tag 标引入口, data 独立XML数据,提交后不再修改
|
|
// 返回: int 标引索引, error 校验、提交或取消错误
|
|
func (e *Editor) addStructureXML(ctx context.Context, tag CustomTag, data []byte) (int, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return 0, err
|
|
}
|
|
if strings.TrimSpace(tag.TypeID) == "" || tag.FileLoc != "" {
|
|
return 0, fmt.Errorf("custom tag requires TypeID and an automatically assigned FileLoc")
|
|
}
|
|
if !structureXMLText(tag.TypeID) || !structureXMLText(tag.NameSpace) || !structureXMLText(tag.SchemaLoc) {
|
|
return 0, fmt.Errorf("invalid custom tag metadata")
|
|
}
|
|
tree, err := parseStructure(ctx, data, tag)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
objects, err := e.validateStructureReferencesContext(ctx, tree, tag, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
base := e.source
|
|
if base == nil {
|
|
base, err = e.importBaseContext(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if tag.SchemaLoc != "" {
|
|
name := base.reader.ResPath(tag.SchemaLoc)
|
|
if _, err := base.reader.readFileViewContext(ctx, name); err != nil {
|
|
return 0, err
|
|
}
|
|
tag.SchemaLoc = "/" + name
|
|
}
|
|
name, index, container, entries, err := editorStructureIndexContext(ctx, base, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
tag.FileLoc = "/" + e.packageName("Tags/Tag.xml")
|
|
var attrs ofdAttrs
|
|
attrs.add("TypeID", tag.TypeID)
|
|
attrs.add("NameSpace", tag.NameSpace)
|
|
var content []byte
|
|
if tag.SchemaLoc != "" {
|
|
content = append(content, container.textXML("SchemaLoc", tag.SchemaLoc)...)
|
|
}
|
|
content = append(content, container.textXML("FileLoc", tag.FileLoc)...)
|
|
entry, err := container.containerXML("CustomTag", attrs, content)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
parts := map[string][]byte{strings.TrimPrefix(tag.FileLoc, "/"): data}
|
|
if container.open != container.end {
|
|
entry = append(bytes.Clone(index[container.open:container.close]), entry...)
|
|
}
|
|
parts[name] = editorPatchXML(index, []editorXMLPatch{editorXMLContent(index, container, entry)})
|
|
if base.document.CustomTags.Path == "" && name != base.reader.documentRoot {
|
|
docName := base.reader.documentRoot
|
|
docData, err := base.reader.readFileViewContext(ctx, docName)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
doc, err := parseEditorXMLContext(ctx, docData)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
parts[docName], err = editorXMLSetText(docData, doc, [][2]string{{"CustomTags", "/" + name}})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return len(entries), e.commitStructurePartsContext(ctx, base, parts, objects, len(entries))
|
|
}
|
|
|
|
// UpdateStructureXML 替换标引XML原文,保留入口属性并隔离共享内容文件
|
|
// 入参: index 标引索引, data 完整XML,返回后不再引用输入数据
|
|
// 返回: error 索引、解析、引用或提交错误
|
|
func (e *Editor) UpdateStructureXML(index int, data []byte) error {
|
|
return e.UpdateStructureXMLContext(context.Background(), index, data)
|
|
}
|
|
|
|
// UpdateStructureXMLContext 校验并原子替换标引原文,相同内容不产生撤销记录
|
|
// 入参: ctx 取消上下文, index 标引索引, data 完整XML
|
|
// 返回: error 索引、解析、引用、提交或取消错误
|
|
func (e *Editor) UpdateStructureXMLContext(ctx context.Context, index int, data []byte) error {
|
|
return e.editStructureContext(ctx, index, func([]byte, *editorXML, CustomTag) ([]byte, error) {
|
|
return bytes.Clone(data), nil
|
|
})
|
|
}
|
|
|
|
// DeleteStructure 删除标引入口,不删除正文或其他入口共享的资源
|
|
// 入参: index 从0开始的标引索引
|
|
// 返回: error 索引或提交错误
|
|
func (e *Editor) DeleteStructure(index int) error {
|
|
if e.source == nil {
|
|
return fmt.Errorf("custom tag structure not found: %d", index)
|
|
}
|
|
name, data, _, entries, err := editorStructureIndex(e.source, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if index < 0 || index >= len(entries) {
|
|
return fmt.Errorf("custom tag structure not found: %d", index)
|
|
}
|
|
node := entries[index]
|
|
base := *e.source
|
|
base.structureStarts = make(map[int]map[string]int, len(e.source.structureStarts))
|
|
for entry, starts := range e.source.structureStarts {
|
|
if entry < index {
|
|
base.structureStarts[entry] = starts
|
|
} else if entry > index {
|
|
base.structureStarts[entry-1] = starts
|
|
}
|
|
}
|
|
return e.commitAnnotationParts(&base, map[string][]byte{name: editorPatchXML(data, []editorXMLPatch{{node.start, node.end, nil}})})
|
|
}
|
|
|
|
// AddStructureNode 在指定父节点末尾添加子树,空路径表示根节点
|
|
// 入参: index 标引索引, parent 从0开始的节点路径, node 新节点及子树
|
|
// 返回: []int 新节点路径, error 校验或提交错误
|
|
func (e *Editor) AddStructureNode(index int, parent []int, node StructureNode) ([]int, error) {
|
|
encoded, err := encodeStructureNode(node)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result []int
|
|
err = e.editStructure(index, func(data []byte, root *editorXML, tag CustomTag) ([]byte, error) {
|
|
target, err := editorStructureNode(root, parent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
structure, err := parseStructure(context.Background(), data, tag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if (structureOfficial(tag, root.name.Space) || structureInvoiceReferences(structure)) && target.name.Space == root.name.Space && target.name.Local == "ObjectRef" {
|
|
return nil, fmt.Errorf("ObjectRef cannot contain child nodes")
|
|
}
|
|
content := bytes.Clone(encoded)
|
|
if target.open != target.end {
|
|
content = append(bytes.Clone(data[target.open:target.close]), content...)
|
|
}
|
|
result = append(slices.Clone(parent), len(target.children))
|
|
return editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, target, content)}), nil
|
|
})
|
|
return result, err
|
|
}
|
|
|
|
// UpdateStructureNode 修改节点名称、命名空间、属性和直接文本,保留子节点、注释及处理指令
|
|
// node的Children和References必须为空,图元关联通过SetStructureReferences维护
|
|
// 入参: index 标引索引, path 从0开始的节点路径, node 节点字段
|
|
// 返回: error 校验或提交错误
|
|
func (e *Editor) UpdateStructureNode(index int, path []int, node StructureNode) error {
|
|
if len(node.Children) != 0 || len(node.References) != 0 {
|
|
return fmt.Errorf("use node operations to edit children or references")
|
|
}
|
|
return e.editStructure(index, func(data []byte, root *editorXML, _ CustomTag) ([]byte, error) {
|
|
target, err := editorStructureNode(root, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
updated, err := updateStructureNodeXML(data, target, node)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return editorPatchXML(data, []editorXMLPatch{{target.start, target.end, updated}}), nil
|
|
})
|
|
}
|
|
|
|
// DeleteStructureNode 删除节点及其子树,根节点须通过DeleteStructure删除
|
|
// 入参: index 标引索引, path 从0开始的非空节点路径
|
|
// 返回: error 路径或提交错误
|
|
func (e *Editor) DeleteStructureNode(index int, path []int) error {
|
|
if len(path) == 0 {
|
|
return fmt.Errorf("delete the structure entry to remove its root")
|
|
}
|
|
return e.editStructure(index, func(data []byte, root *editorXML, _ CustomTag) ([]byte, error) {
|
|
node, err := editorStructureNode(root, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return editorPatchXML(data, []editorXMLPatch{{node.start, node.end, nil}}), nil
|
|
})
|
|
}
|
|
|
|
// SetStructureReferences 替换公文节点的直接ObjectRef,保留其他子节点和原文
|
|
// 按输入顺序绑定图元,允许跨页和重复引用;空列表解除绑定,不修改版式内容
|
|
// 入参: index 标引索引, path 节点路径, references 页面标识和有序图元标识
|
|
// 返回: error 类型、目标校验或提交错误
|
|
func (e *Editor) SetStructureReferences(index int, path []int, references []StructureReference) error {
|
|
return e.editStructure(index, func(data []byte, root *editorXML, tag CustomTag) ([]byte, error) {
|
|
node, err := editorStructureNode(root, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
structure, err := parseStructure(context.Background(), data, tag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !(structureOfficial(tag, root.name.Space) || structureInvoiceReferences(structure)) || node.name.Local == "ObjectRef" {
|
|
return nil, fmt.Errorf("custom tag does not support ObjectRef binding")
|
|
}
|
|
for parent := node; parent != nil; parent = parent.parent {
|
|
if parent.name.Space != root.name.Space {
|
|
return nil, fmt.Errorf("cannot bind objects inside an extension namespace")
|
|
}
|
|
}
|
|
var added []byte
|
|
for _, ref := range references {
|
|
if editorResourceID(ref.PageID) == "" || len(ref.ObjectIDs) == 0 {
|
|
return nil, fmt.Errorf("ObjectRef requires a page and object identifiers")
|
|
}
|
|
for _, id := range ref.ObjectIDs {
|
|
if editorResourceID(id) == "" {
|
|
return nil, fmt.Errorf("invalid custom tag object identifier: %s", id)
|
|
}
|
|
}
|
|
encoded, err := encodeStructureNode(StructureNode{Name: "ObjectRef", Namespace: root.name.Space, Attributes: []StructureAttribute{{Name: "PageRef", Value: ref.PageID}}, Text: strings.Join(ref.ObjectIDs, ",")})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
added = append(added, encoded...)
|
|
}
|
|
var content []byte
|
|
if node.open != node.end {
|
|
var patches []editorXMLPatch
|
|
for _, child := range node.children {
|
|
if child.name.Space == root.name.Space && child.name.Local == "ObjectRef" {
|
|
patches = append(patches, editorXMLPatch{child.start - node.open, child.end - node.open, nil})
|
|
}
|
|
}
|
|
content = editorPatchXML(data[node.open:node.close], patches)
|
|
}
|
|
content = append(content, added...)
|
|
return editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, node, content)}), nil
|
|
})
|
|
}
|
|
|
|
// editorStructureIndex 读取或准备当前文档的标引索引,保留内联入口和未知扩展
|
|
// 入参: base 编辑包快照, parts 本次输出条目,可为nil
|
|
// 返回: string 索引文件路径, []byte 原文, *editorXML 容器, []*editorXML 标引入口, error 读取错误
|
|
func editorStructureIndex(base *editorSource, parts map[string][]byte) (string, []byte, *editorXML, []*editorXML, error) {
|
|
return editorStructureIndexContext(context.Background(), base, parts)
|
|
}
|
|
|
|
// editorStructureIndexContext 读取或准备标引索引,文件读取及解析期间响应取消
|
|
// 入参: ctx 取消上下文, base 编辑包快照, parts 本次输出条目,可为nil
|
|
// 返回: string 索引路径, []byte 原文, *editorXML 容器, []*editorXML 入口, error 读取或取消错误
|
|
func editorStructureIndexContext(ctx context.Context, base *editorSource, parts map[string][]byte) (string, []byte, *editorXML, []*editorXML, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return "", nil, nil, nil, err
|
|
}
|
|
name := base.reader.documentRoot
|
|
if base.document.CustomTags.Path != "" {
|
|
name = base.reader.ResPath(base.document.CustomTags.Path)
|
|
}
|
|
data, ok := parts[name]
|
|
if !ok {
|
|
var err error
|
|
data, err = base.reader.readFileViewContext(ctx, name)
|
|
if err != nil {
|
|
return "", nil, nil, nil, err
|
|
}
|
|
}
|
|
root, err := parseEditorXMLContext(ctx, data)
|
|
if err != nil {
|
|
return "", nil, nil, nil, err
|
|
}
|
|
if base.document.CustomTags.Path == "" {
|
|
container := root.child("CustomTags")
|
|
if container == nil || len(container.children) == 0 {
|
|
name = packageAvailableName(base.reader, nil, base.reader.RootDir+"/CustomTags.xml")
|
|
data, err = root.containerXML("CustomTags", nil, nil)
|
|
if err == nil {
|
|
root, err = parseEditorXMLContext(ctx, data)
|
|
}
|
|
if err != nil {
|
|
return "", nil, nil, nil, err
|
|
}
|
|
} else {
|
|
root = container
|
|
}
|
|
}
|
|
var entries []*editorXML
|
|
for _, child := range root.children {
|
|
if child.matchesOFD("CustomTag") {
|
|
entries = append(entries, child)
|
|
}
|
|
}
|
|
return name, data, root, entries, nil
|
|
}
|
|
|
|
// editorStructureNode 按完整XML子节点索引定位,与Reader.Structure的路径一致
|
|
// 入参: root 根节点, path 从0开始的路径
|
|
// 返回: *editorXML 节点, error 路径错误
|
|
func editorStructureNode(root *editorXML, path []int) (*editorXML, error) {
|
|
for _, index := range path {
|
|
if index < 0 || index >= len(root.children) {
|
|
return nil, fmt.Errorf("custom tag node not found")
|
|
}
|
|
root = root.children[index]
|
|
}
|
|
return root, nil
|
|
}
|
|
|
|
// editStructure 验证完整标引后原子提交,隔离共享入口并保留未修改的XML
|
|
// 入参: index 标引索引, edit 内容变换
|
|
// 返回: error 校验、解析或提交错误
|
|
func (e *Editor) editStructure(index int, edit func([]byte, *editorXML, CustomTag) ([]byte, error)) error {
|
|
return e.editStructureContext(context.Background(), index, edit)
|
|
}
|
|
|
|
// editStructureContext 校验并提交标引编辑,快照读取及引用遍历期间响应取消
|
|
// 入参: ctx 取消上下文, index 标引索引, edit 内容变换
|
|
// 返回: error 校验、解析、提交或取消错误
|
|
func (e *Editor) editStructureContext(ctx context.Context, index int, edit func([]byte, *editorXML, CustomTag) ([]byte, error)) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if e.source == nil {
|
|
return fmt.Errorf("custom tag structure not found: %d", index)
|
|
}
|
|
tags, err := e.source.reader.CustomTagsContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if index < 0 || index >= len(tags) {
|
|
return fmt.Errorf("custom tag structure not found: %d", index)
|
|
}
|
|
tag := tags[index]
|
|
name := e.source.reader.ResPath(tag.FileLoc)
|
|
snapshot, err := e.ReaderContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer snapshot.Close()
|
|
current, err := snapshot.CustomTagsContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if index >= len(current) {
|
|
return fmt.Errorf("custom tag structure not found: %d", index)
|
|
}
|
|
data, err := snapshot.readFileViewContext(ctx, snapshot.ResPath(current[index].FileLoc))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := parseStructure(ctx, data, tag); err != nil {
|
|
return err
|
|
}
|
|
root, err := parseEditorXMLContext(ctx, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
updated, err := edit(data, root, tag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if bytes.Equal(data, updated) {
|
|
return nil
|
|
}
|
|
tree, err := parseStructure(ctx, updated, tag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objects, err := e.validateStructureReferencesContext(ctx, tree, tag, snapshot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
parts := make(map[string][]byte)
|
|
for i, other := range tags {
|
|
if i != index && e.source.reader.ResPath(other.FileLoc) == name {
|
|
indexName, indexData, _, entries, err := editorStructureIndexContext(ctx, e.source, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
name = e.packageName("Tags/Tag.xml")
|
|
loc := entries[index].child("FileLoc")
|
|
if loc == nil {
|
|
return fmt.Errorf("custom tag requires FileLoc")
|
|
}
|
|
var value bytes.Buffer
|
|
_ = xml.EscapeText(&value, []byte("/"+name))
|
|
parts[indexName] = editorPatchXML(indexData, []editorXMLPatch{editorXMLContent(indexData, loc, value.Bytes())})
|
|
break
|
|
}
|
|
}
|
|
parts[name] = updated
|
|
return e.commitStructurePartsContext(ctx, e.source, parts, objects, index)
|
|
}
|
|
|
|
// commitStructureParts 提交标引并记录有效目标及已应用的编号迁移,供后续编辑维护引用
|
|
// 入参: base 原包, parts 修改条目, objects 按页面编号记录的有效图元, index 标引入口序号
|
|
// 返回: error 提交错误
|
|
func (e *Editor) commitStructureParts(base *editorSource, parts map[string][]byte, objects map[string]map[string]bool, index int) error {
|
|
return e.commitStructurePartsContext(context.Background(), base, parts, objects, index)
|
|
}
|
|
|
|
// commitStructurePartsContext 准备标引引用记录并原子提交,取消时保留原编辑状态
|
|
// 入参: ctx 取消上下文, base 原包, parts 修改条目, objects 有效图元, index 标引入口序号
|
|
// 返回: error 提交或取消错误
|
|
func (e *Editor) commitStructurePartsContext(ctx context.Context, base *editorSource, parts map[string][]byte, objects map[string]map[string]bool, index int) error {
|
|
next := *base
|
|
if len(e.structureRemaps) != 0 {
|
|
next.structureStarts = maps.Clone(base.structureStarts)
|
|
if next.structureStarts == nil {
|
|
next.structureStarts = make(map[int]map[string]int)
|
|
}
|
|
next.structureStarts[index] = e.structureRemapStarts()
|
|
}
|
|
next.structureObjects = maps.Clone(base.structureObjects)
|
|
if len(objects) != 0 && next.structureObjects == nil {
|
|
next.structureObjects = make(map[string]map[string]bool)
|
|
}
|
|
for page, ids := range objects {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
known := make(map[string]bool, len(next.structureObjects[page])+len(ids))
|
|
maps.Copy(known, next.structureObjects[page])
|
|
maps.Copy(known, ids)
|
|
next.structureObjects[page] = known
|
|
}
|
|
return e.commitAnnotationPartsContext(ctx, &next, parts)
|
|
}
|
|
|
|
// validateStructureReferences 检查标引目标是否位于当前文档和版本,不依赖字体或图像解码
|
|
// 入参: root 待提交标引树, tag 标引入口, r 当前操作的编辑快照,为nil时按需创建
|
|
// 返回: map[string]map[string]bool 已校验目标, error 页面或图元缺失错误
|
|
func (e *Editor) validateStructureReferences(root *StructureNode, tag CustomTag, r *Reader) (map[string]map[string]bool, error) {
|
|
return e.validateStructureReferencesContext(context.Background(), root, tag, r)
|
|
}
|
|
|
|
// validateStructureReferencesContext 校验当前文档及版本的引用,遍历及快照读取期间响应取消
|
|
// 入参: ctx 取消上下文, root 标引树, tag 标引入口, r 可复用的编辑快照
|
|
// 返回: map[string]map[string]bool 已校验目标, error 引用或取消错误
|
|
func (e *Editor) validateStructureReferencesContext(ctx context.Context, root *StructureNode, tag CustomTag, r *Reader) (map[string]map[string]bool, error) {
|
|
var refs []StructureReference
|
|
var collect func(*StructureNode, bool) error
|
|
collect = func(node *StructureNode, official bool) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
official = official && node.Namespace == root.Namespace
|
|
if official && node.Name == "ObjectRef" && len(node.Children) != 0 {
|
|
return fmt.Errorf("ObjectRef cannot contain child nodes")
|
|
}
|
|
refs = append(refs, node.References...)
|
|
for i := range node.Children {
|
|
if err := collect(&node.Children[i], official); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
if err := collect(root, structureOfficial(tag, root.Namespace) || structureInvoiceReferences(root)); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(refs) == 0 {
|
|
return nil, nil
|
|
}
|
|
if r == nil {
|
|
var err error
|
|
r, err = e.ReaderContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer r.Close()
|
|
}
|
|
doc, err := r.DocContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
renderer := e.newRenderer(r)
|
|
renderer.pageContext = ctx
|
|
pages := signaturePageNumbers(doc)
|
|
checked := make(map[int]map[string]bool)
|
|
result := make(map[string]map[string]bool)
|
|
for _, ref := range refs {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
page, _ := resourceValue(pages, ref.PageID)
|
|
if page == 0 {
|
|
return nil, fmt.Errorf("custom tag page not found: %s", ref.PageID)
|
|
}
|
|
objects := checked[page]
|
|
if objects == nil {
|
|
content, err := r.PageContentByIndexContext(ctx, page-1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
visitor := &structureObjectVisitor{objects: make(map[string]bool)}
|
|
if err := renderer.WalkPage(content, visitor); err != nil {
|
|
return nil, err
|
|
}
|
|
objects = visitor.objects
|
|
checked[page] = objects
|
|
}
|
|
for _, id := range ref.ObjectIDs {
|
|
if !objects[editorResourceID(id)] {
|
|
return nil, fmt.Errorf("custom tag object not found: %s/%s", ref.PageID, id)
|
|
}
|
|
key := editorResourceID(ref.PageID)
|
|
if result[key] == nil {
|
|
result[key] = make(map[string]bool)
|
|
}
|
|
result[key][editorResourceID(id)] = true
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|