mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
59e3a7d36e
commit
6926ceafc9
3 files changed
+171
-12
No files matched your search
@@ -67,13 +67,14 @@ func scanEditorFonts(ctx context.Context, reader *Reader, index int) (*editorFon
|
|||||||
// 入参: ctx 取消上下文, reader 包快照, index 文档索引, inspect 可选的文件节点访问器
|
// 入参: ctx 取消上下文, reader 包快照, index 文档索引, inspect 可选的文件节点访问器
|
||||||
// 返回: *editorFontDocument 字体引用, error 读取、结构或访问错误
|
// 返回: *editorFontDocument 字体引用, error 读取、结构或访问错误
|
||||||
func scanEditorFontFiles(ctx context.Context, reader *Reader, index int, inspect func(*editorFontXML, *editorXML) error) (*editorFontDocument, error) {
|
func scanEditorFontFiles(ctx context.Context, reader *Reader, index int, inspect func(*editorFontXML, *editorXML) error) (*editorFontDocument, error) {
|
||||||
return scanEditorFontFilesMode(ctx, reader, index, inspect, true)
|
return scanEditorFontFilesMode(ctx, reader, index, inspect, true, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanEditorFontFilesMode 按需保留文字节点及原文,只读访问在扫描后释放非资源文件原文
|
// scanEditorFontFilesMode 按需保留文字节点及原文,只读访问在扫描后释放非资源文件原文
|
||||||
// 入参: ctx 取消上下文, reader 包快照, index 文档索引, inspect 可选的文件节点访问器, retain 是否保留编辑数据
|
// 入参: ctx 取消上下文, reader 包快照, index 文档索引, inspect 可选的文件节点访问器, retain 是否保留编辑数据
|
||||||
|
// stream 为可选的只读访问器,依次返回是否已处理、是否不安全、依赖路径及错误
|
||||||
// 返回: *editorFontDocument 扫描结果, error 读取、结构或访问错误
|
// 返回: *editorFontDocument 扫描结果, error 读取、结构或访问错误
|
||||||
func scanEditorFontFilesMode(ctx context.Context, reader *Reader, index int, inspect func(*editorFontXML, *editorXML) error, retain bool) (*editorFontDocument, error) {
|
func scanEditorFontFilesMode(ctx context.Context, reader *Reader, index int, inspect func(*editorFontXML, *editorXML) error, retain bool, stream func(*editorFontXML) (bool, bool, []string, error)) (*editorFontDocument, error) {
|
||||||
scan := &editorFontDocument{reader: reader, index: index, files: make(map[string]*editorFontXML), fonts: make(map[string]*editorFontDeclaration), texts: make(map[string][]editorFontText)}
|
scan := &editorFontDocument{reader: reader, index: index, files: make(map[string]*editorFontXML), fonts: make(map[string]*editorFontDeclaration), texts: make(map[string][]editorFontText)}
|
||||||
entry, err := reader.documentEntryContext(ctx, index)
|
entry, err := reader.documentEntryContext(ctx, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -93,11 +94,24 @@ func scanEditorFontFilesMode(ctx context.Context, reader *Reader, index int, ins
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
file := &editorFontXML{name: name, data: data}
|
||||||
|
if !retain && stream != nil {
|
||||||
|
handled, unsafe, links, err := stream(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if handled {
|
||||||
|
scan.files[name] = file
|
||||||
|
scan.unsafe = scan.unsafe || unsafe
|
||||||
|
queue = append(queue, links...)
|
||||||
|
file.data = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
root, err := parseEditorXMLContext(ctx, data)
|
root, err := parseEditorXMLContext(ctx, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
file := &editorFontXML{name: name, data: data}
|
|
||||||
scan.files[name] = file
|
scan.files[name] = file
|
||||||
base := ""
|
base := ""
|
||||||
if root.matchesOFD("Res") {
|
if root.matchesOFD("Res") {
|
||||||
|
|||||||
+26
-8
@@ -42,22 +42,40 @@ type editorXMLPatch struct {
|
|||||||
|
|
||||||
// editorXMLReader 在内存XML上直接读取字节,按块检查长词元的取消信号
|
// editorXMLReader 在内存XML上直接读取字节,按块检查长词元的取消信号
|
||||||
type editorXMLReader struct {
|
type editorXMLReader struct {
|
||||||
*bytes.Reader
|
data []byte
|
||||||
ctx context.Context
|
offset int
|
||||||
remaining uint16
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadByte 读取XML词元字节,每4096字节检查取消状态
|
// ReadByte 读取XML词元字节,每4096字节检查取消状态
|
||||||
// 返回: byte 当前字节, error 取消或读取错误
|
// 返回: byte 当前字节, error 取消或读取错误
|
||||||
func (r *editorXMLReader) ReadByte() (byte, error) {
|
func (r *editorXMLReader) ReadByte() (byte, error) {
|
||||||
if r.remaining == 0 {
|
if r.offset&4095 == 0 {
|
||||||
if err := r.ctx.Err(); err != nil {
|
if err := r.ctx.Err(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
r.remaining = 4096
|
|
||||||
}
|
}
|
||||||
r.remaining--
|
if r.offset >= len(r.data) {
|
||||||
return r.Reader.ReadByte()
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
b := r.data[r.offset]
|
||||||
|
r.offset++
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read 读取XML原文字节并检查取消状态
|
||||||
|
// 入参: p 接收缓冲区
|
||||||
|
// 返回: int 已读字节数, error 取消或读取错误
|
||||||
|
func (r *editorXMLReader) Read(p []byte) (int, error) {
|
||||||
|
if err := r.ctx.Err(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if r.offset >= len(r.data) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n := copy(p, r.data[r.offset:])
|
||||||
|
r.offset += n
|
||||||
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorXMLPrefix 跳过UTF-8字节序标记和XML空白,探测后保留首个内容字节
|
// editorXMLPrefix 跳过UTF-8字节序标记和XML空白,探测后保留首个内容字节
|
||||||
@@ -106,7 +124,7 @@ func parseEditorXML(data []byte) (*editorXML, error) {
|
|||||||
func parseEditorXMLContext(ctx context.Context, data []byte) (*editorXML, error) {
|
func parseEditorXMLContext(ctx context.Context, data []byte) (*editorXML, error) {
|
||||||
var input io.Reader = bytes.NewReader(data)
|
var input io.Reader = bytes.NewReader(data)
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
input = &editorXMLReader{Reader: bytes.NewReader(data), ctx: ctx}
|
input = &editorXMLReader{data: data, ctx: ctx}
|
||||||
}
|
}
|
||||||
d := xml.NewDecoder(input)
|
d := xml.NewDecoder(input)
|
||||||
var root *editorXML
|
var root *editorXML
|
||||||
|
|||||||
+128
-1
@@ -17,6 +17,7 @@ package ofdgo
|
|||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
@@ -64,6 +65,15 @@ type resourceCatalog struct {
|
|||||||
images []editorFontText
|
images []editorFontText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resourcePageFrame 保存流式页面扫描的图元归属及直接文本位置
|
||||||
|
type resourcePageFrame struct {
|
||||||
|
name string
|
||||||
|
object string
|
||||||
|
start int
|
||||||
|
role string
|
||||||
|
link bool
|
||||||
|
}
|
||||||
|
|
||||||
// Resources 列出当前文档及所选版本的资源,标记缺失文件,不读取二进制内容或执行媒体动作
|
// Resources 列出当前文档及所选版本的资源,标记缺失文件,不读取二进制内容或执行媒体动作
|
||||||
// 重复查询复用已完成的扫描,返回值及引用位置与阅读器相互独立
|
// 重复查询复用已完成的扫描,返回值及引用位置与阅读器相互独立
|
||||||
// 入参: ctx 取消上下文
|
// 入参: ctx 取消上下文
|
||||||
@@ -280,7 +290,9 @@ func (r *Reader) resourceCatalogMode(ctx context.Context, retain bool) (*resourc
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return visit(root, "")
|
return visit(root, "")
|
||||||
}, retain)
|
}, retain, func(file *editorFontXML) (bool, bool, []string, error) {
|
||||||
|
return scanResourcePage(ctx, file, pages, refs)
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -327,6 +339,121 @@ func (r *Reader) resourceCatalogMode(ctx context.Context, retain bool) (*resourc
|
|||||||
return catalog, ctx.Err()
|
return catalog, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scanResourcePage 流式收集常规页面引用,特殊结构回退到完整节点扫描
|
||||||
|
// 入参: ctx 取消上下文, file 页面原文, pages 文件页码, refs 已收集引用
|
||||||
|
// 返回: bool 是否已处理, bool 引用是否不安全, []string 依赖路径, error 解析或取消错误
|
||||||
|
func scanResourcePage(ctx context.Context, file *editorFontXML, pages map[string]int, refs map[string][]ResourceReference) (bool, bool, []string, error) {
|
||||||
|
d := xml.NewDecoder(&editorXMLReader{data: file.data, ctx: ctx})
|
||||||
|
var stack []resourcePageFrame
|
||||||
|
var links []string
|
||||||
|
local := make(map[string][]ResourceReference)
|
||||||
|
page, exists := pages[file.name]
|
||||||
|
if !exists {
|
||||||
|
page = -1
|
||||||
|
}
|
||||||
|
seen, unsafe := false, false
|
||||||
|
add := func(role, id, object string) {
|
||||||
|
key := editorResourceID(id)
|
||||||
|
if role == "AttachID" {
|
||||||
|
key = "Attachment/" + id
|
||||||
|
}
|
||||||
|
if key != "" {
|
||||||
|
local[key] = append(local[key], ResourceReference{File: file.name, Page: page, Object: object, Role: role})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return true, false, nil, err
|
||||||
|
}
|
||||||
|
start := int(d.InputOffset())
|
||||||
|
token, err := d.Token()
|
||||||
|
if err == io.EOF {
|
||||||
|
if !seen {
|
||||||
|
return false, false, nil, nil
|
||||||
|
}
|
||||||
|
for key, values := range local {
|
||||||
|
refs[key] = append(refs[key], values...)
|
||||||
|
}
|
||||||
|
return true, unsafe, links, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return true, false, nil, err
|
||||||
|
}
|
||||||
|
switch token := token.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
if len(stack) == 0 {
|
||||||
|
if seen {
|
||||||
|
return true, false, nil, fmt.Errorf("multiple XML roots")
|
||||||
|
}
|
||||||
|
if token.Name.Local != "Page" {
|
||||||
|
return false, false, nil, nil
|
||||||
|
}
|
||||||
|
seen = true
|
||||||
|
}
|
||||||
|
if classifyOFDNamespace(token.Name.Space) == ofdXMLUnknown || slices.Contains([]string{"Attachments", "Signatures", "CustomTags", "Extensions", "ExtendData", "Data"}, token.Name.Local) {
|
||||||
|
return false, false, nil, nil
|
||||||
|
}
|
||||||
|
for _, attr := range token.Attr {
|
||||||
|
if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != "http://www.w3.org/XML/1998/namespace" {
|
||||||
|
return false, false, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame := resourcePageFrame{name: token.Name.Local, start: start}
|
||||||
|
parent := ""
|
||||||
|
if len(stack) != 0 {
|
||||||
|
outer := stack[len(stack)-1]
|
||||||
|
if outer.role != "" || outer.link {
|
||||||
|
return false, false, nil, nil
|
||||||
|
}
|
||||||
|
frame.object, parent = outer.object, outer.name
|
||||||
|
}
|
||||||
|
node := editorXML{name: token.Name, attrs: token.Attr}
|
||||||
|
if slices.Contains([]string{"TextObject", "PathObject", "ImageObject", "CompositeObject"}, frame.name) {
|
||||||
|
frame.object = node.attr("ID")
|
||||||
|
}
|
||||||
|
if frame.name == "TextObject" || frame.name == "Text" && parent == "Area" {
|
||||||
|
unsafe = unsafe || editorResourceID(node.attr("Font")) == ""
|
||||||
|
}
|
||||||
|
for _, attr := range token.Attr {
|
||||||
|
if attr.Name.Space == "" && (editorResourceReferenceKind(attr.Name.Local, false) == editorReferenceID || attr.Name.Local == "AttachID") {
|
||||||
|
add(attr.Name.Local, attr.Value, frame.object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if slices.Contains([]string{"Thumbnail", "Substitution", "DefaultCS"}, frame.name) {
|
||||||
|
frame.role = frame.name
|
||||||
|
}
|
||||||
|
value := ""
|
||||||
|
switch frame.name {
|
||||||
|
case "PublicRes", "DocumentRes", "PageRes", "Annotations":
|
||||||
|
frame.link = true
|
||||||
|
case "Page", "TemplatePage", "CompositeGraphicUnit":
|
||||||
|
value = node.attr("BaseLoc")
|
||||||
|
case "PageAnnot":
|
||||||
|
value = node.attr("FileLoc")
|
||||||
|
case "DrawParam":
|
||||||
|
value = node.attr("Link")
|
||||||
|
}
|
||||||
|
if value != "" {
|
||||||
|
links = append(links, resolveResourcePath(file.name, "", value))
|
||||||
|
}
|
||||||
|
stack = append(stack, frame)
|
||||||
|
case xml.EndElement:
|
||||||
|
frame := stack[len(stack)-1]
|
||||||
|
stack = stack[:len(stack)-1]
|
||||||
|
if frame.role != "" || frame.link {
|
||||||
|
node := editorXML{start: frame.start, end: int(d.InputOffset())}
|
||||||
|
value := strings.TrimSpace(editorImportText(file.data, &node))
|
||||||
|
if frame.role != "" {
|
||||||
|
add(frame.role, value, frame.object)
|
||||||
|
}
|
||||||
|
if frame.link && value != "" {
|
||||||
|
links = append(links, resolveResourcePath(file.name, "", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add 加入资源声明,拒绝同一类型内的重复标识
|
// add 加入资源声明,拒绝同一类型内的重复标识
|
||||||
// 入参: item 资源信息, node 声明位置
|
// 入参: item 资源信息, node 声明位置
|
||||||
// 返回: error 标识冲突
|
// 返回: error 标识冲突
|
||||||
|
|||||||
Reference in new issue
Block a user