diff --git a/ofdgo_compression.go b/ofdgo_compression.go
index 8cec178..2e78da8 100644
--- a/ofdgo_compression.go
+++ b/ofdgo_compression.go
@@ -233,7 +233,7 @@ func (r *Reader) compressionImageSafety(ctx context.Context, images []ImageInfo)
break
}
if element, ok := token.(xml.StartElement); ok {
- if element.Name.Space != "" && element.Name.Space != ofdNamespace && element.Name.Space != "http://www.ofdspec.org" {
+ if classifyOFDNamespace(element.Name.Space) == ofdXMLUnknown {
safe = false
break
}
diff --git a/ofdgo_crypto_package.go b/ofdgo_crypto_package.go
index e94537a..2c67526 100644
--- a/ofdgo_crypto_package.go
+++ b/ofdgo_crypto_package.go
@@ -272,7 +272,7 @@ func (r *Reader) decryptPackage(settings readerOptions) error {
func decodeEncryptionEntries(data, key, iv []byte, provider CryptoProvider) (encryptionEntries, error) {
var entries encryptionEntries
valid := func() bool {
- return entries.XMLName.Space == ofdNamespace && (entries.XMLName.Local == "EncryptEntries" || entries.XMLName.Local == "EncryptedEntries")
+ return entries.XMLName.Space == ofdNamespace2016 && (entries.XMLName.Local == "EncryptEntries" || entries.XMLName.Local == "EncryptedEntries")
}
if err := xml.Unmarshal(data, &entries); err == nil && valid() {
return entries, nil
@@ -468,7 +468,7 @@ func encryptPackageParts(parts map[string][]byte, options EncryptionOptions, pro
seed.Users = append(seed.Users, encryptionUser{Name: recipient.UserName, Role: recipient.UserType, Certificate: base64.StdEncoding.EncodeToString(certificate), Key: base64.StdEncoding.EncodeToString(wrapped), IV: base64.StdEncoding.EncodeToString(iv)})
}
}
- entries := encryptionEntries{XMLName: xml.Name{Space: ofdNamespace, Local: "EncryptEntries"}, ID: id}
+ entries := encryptionEntries{XMLName: xml.Name{Space: ofdNamespace2016, Local: "EncryptEntries"}, ID: id}
cipherParts := make(map[string][]byte, len(parts)+3)
for i, name := range slices.Sorted(maps.Keys(parts)) {
if err := progress.report("encrypt", i, len(parts)); err != nil {
diff --git a/ofdgo_editor.go b/ofdgo_editor.go
index 1787215..b161673 100644
--- a/ofdgo_editor.go
+++ b/ofdgo_editor.go
@@ -199,7 +199,7 @@ func (e *Editor) Page(index int) (*PageContent, error) {
return nil, err
}
result := cloneEditorData(*page)
- result.XMLName = xml.Name{Space: ofdNamespace, Local: "Page"}
+ result.XMLName = xml.Name{Space: ofdNamespace2016, Local: "Page"}
for i := range result.Content.Layer {
layer := &result.Content.Layer[i]
layer.TextObject, layer.PathObject, layer.ImageObject, layer.CompositeGraphicUnit = nil, nil, nil, nil
diff --git a/ofdgo_editor_action.go b/ofdgo_editor_action.go
index c031f19..781fdd6 100644
--- a/ofdgo_editor_action.go
+++ b/ofdgo_editor_action.go
@@ -62,8 +62,10 @@ func (e *Editor) SetDocumentActions(actions []Action) error {
return err
}
patch := editorXMLPatch{root.close, root.close, encoded}
+ context := root
if node := root.child("Actions"); node != nil {
patch.start, patch.end = node.start, node.end
+ context = node
} else {
for _, child := range root.children {
if packageOFDNode(child, child.name.Local) && slices.Contains([]string{"VPreferences", "Bookmarks", "Annotations", "CustomTags", "Attachments", "Extensions"}, child.name.Local) {
@@ -72,6 +74,10 @@ func (e *Editor) SetDocumentActions(actions []Action) error {
}
}
}
+ patch.data, err = editorXMLGenerated(encoded, context.name.Space)
+ if err != nil {
+ return err
+ }
return e.commitAnnotationParts(base, map[string][]byte{name: editorPatchXML(data, []editorXMLPatch{patch})})
}
diff --git a/ofdgo_editor_annot.go b/ofdgo_editor_annot.go
index 007bfac..5f76c26 100644
--- a/ofdgo_editor_annot.go
+++ b/ofdgo_editor_annot.go
@@ -69,9 +69,9 @@ func (e *Editor) UpdateAnnotation(index int, id, remark, creator string) error {
if child := root.child("Remark"); child != nil {
data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, child, escaped.Bytes())})
} else if root.open == root.end {
- data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, root, editorXMLText("Remark", remark))})
+ data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, root, root.textXML("Remark", remark))})
} else {
- data = editorPatchXML(data, []editorXMLPatch{{root.open, root.open, editorXMLText("Remark", remark)}})
+ data = editorPatchXML(data, []editorXMLPatch{{root.open, root.open, root.textXML("Remark", remark)}})
}
}
root, err = parseEditorXML(data)
@@ -280,7 +280,10 @@ func (e *Editor) editAnnotations(index int, ids []string, edit func([]byte, *edi
if err != nil {
return err
}
- parts[docName] = editorXMLSetText(docData, doc, [][2]string{{"Annotations", ""}})
+ parts[docName], err = editorXMLSetText(docData, doc, [][2]string{{"Annotations", ""}})
+ if err != nil {
+ return err
+ }
}
}
if len(parts) == 0 {
diff --git a/ofdgo_editor_annot_content.go b/ofdgo_editor_annot_content.go
index 0eec077..996abbe 100644
--- a/ofdgo_editor_annot_content.go
+++ b/ofdgo_editor_annot_content.go
@@ -38,7 +38,7 @@ func (e *Editor) AddAnnotation(index int, annotation Annotation) (string, error)
if err != nil {
return err
}
- return next.appendAnnotations(index, data)
+ return next.appendAnnotations(index, data, true)
})
if err != nil {
return "", err
@@ -211,9 +211,9 @@ func (e *Editor) annotationXML(index int, id string) ([]byte, error) {
}
// appendAnnotations 在独立页面注解文件末尾追加原文,保留原索引及未知内容
-// 入参: index 页面索引, annotations 标准注解XML片段
+// 入参: index 页面索引, annotations 注解XML片段, generated 是否为自产内容
// 返回: error 错误信息
-func (e *Editor) appendAnnotations(index int, annotations []byte) error {
+func (e *Editor) appendAnnotations(index int, annotations []byte, generated bool) error {
base := e.source
var err error
if base == nil {
@@ -271,6 +271,12 @@ func (e *Editor) appendAnnotations(index int, annotations []byte) error {
return err
}
added := bytes.TrimPrefix(annotations, []byte(xml.Header))
+ if generated {
+ added, err = editorXMLGenerated(added, page.name.Space)
+ if err != nil {
+ return err
+ }
+ }
if page.open == page.end {
content = editorPatchXML(content, []editorXMLPatch{editorXMLContent(content, page, added)})
} else {
@@ -300,7 +306,7 @@ func (e *Editor) appendAnnotations(index int, annotations []byte) error {
data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, loc, value.Bytes())})
}
} else {
- entry, err := editorXMLContainer("Page", ofdAttrs{{Name: xml.Name{Local: "PageID"}, Value: e.pages[index].ID}}, editorXMLText("FileLoc", "/"+file))
+ entry, err := root.containerXML("Page", ofdAttrs{{Name: xml.Name{Local: "PageID"}, Value: e.pages[index].ID}}, root.textXML("FileLoc", "/"+file))
if err != nil {
return err
}
@@ -329,7 +335,7 @@ func (e *Editor) appendAnnotations(index int, annotations []byte) error {
break
}
}
- patch := editorXMLPatch{position, position, editorXMLText("Annotations", "/"+name)}
+ patch := editorXMLPatch{position, position, doc.textXML("Annotations", "/"+name)}
if node := doc.child("Annotations"); node != nil {
var value bytes.Buffer
_ = xml.EscapeText(&value, []byte("/"+name))
diff --git a/ofdgo_editor_annot_ops.go b/ofdgo_editor_annot_ops.go
index e22b570..6599b36 100644
--- a/ofdgo_editor_annot_ops.go
+++ b/ofdgo_editor_annot_ops.go
@@ -134,7 +134,7 @@ func (e *Editor) PasteAnnotations(page int, selection *AnnotationSelection, dx,
content = append(content, bytes.TrimPrefix(data, []byte(xml.Header))...)
result = append(result, id)
}
- if err := edit.appendAnnotations(page, content); err != nil {
+ if err := edit.appendAnnotations(page, content, false); err != nil {
return err
}
edit.source.annotationStates = states
@@ -160,7 +160,7 @@ func (e *Editor) PasteAnnotations(page int, selection *AnnotationSelection, dx,
var added []byte
for _, name := range selection.resources {
if !registered[name] {
- added = append(added, editorXMLText("DocumentRes", "/"+name)...)
+ added = append(added, common.textXML("DocumentRes", "/"+name)...)
registered[name] = true
}
}
diff --git a/ofdgo_editor_attachment.go b/ofdgo_editor_attachment.go
index 4b168cd..8314a39 100644
--- a/ofdgo_editor_attachment.go
+++ b/ofdgo_editor_attachment.go
@@ -86,12 +86,20 @@ func (e *Editor) AddAttachment(name string, data []byte) (string, error) {
if err != nil {
return "", err
}
+ entry, err = editorXMLGenerated(entry, root.name.Space)
+ if err != nil {
+ return "", err
+ }
if root.open == root.end {
parts[name] = editorPatchXML(existing, []editorXMLPatch{editorXMLContent(existing, root, entry)})
} else {
parts[name] = editorPatchXML(existing, []editorXMLPatch{{root.close, root.close, entry}})
}
} else if node != nil {
+ entry, err = editorXMLGenerated(entry, node.name.Space)
+ if err != nil {
+ return "", err
+ }
if node.open == node.end {
parts[docName] = editorPatchXML(docData, []editorXMLPatch{editorXMLContent(docData, node, entry)})
} else {
@@ -108,7 +116,7 @@ func (e *Editor) AddAttachment(name string, data []byte) (string, error) {
if next := doc.child("Extensions"); next != nil {
position = next.start
}
- parts[docName] = editorPatchXML(docData, []editorXMLPatch{{position, position, editorXMLText("Attachments", "/"+name)}})
+ parts[docName] = editorPatchXML(docData, []editorXMLPatch{{position, position, doc.textXML("Attachments", "/"+name)}})
}
if err := e.commitAnnotationParts(base, parts); err != nil {
return "", err
@@ -151,9 +159,9 @@ func (e *Editor) ReplaceAttachment(id string, data []byte) error {
if child := node.child("FileLoc"); child != nil {
fragment = editorPatchXML(fragment, []editorXMLPatch{editorXMLContent(fragment, child, location.Bytes())})
} else if node.open == node.end {
- fragment = editorPatchXML(fragment, []editorXMLPatch{editorXMLContent(fragment, node, editorXMLText("FileLoc", "/"+file))})
+ fragment = editorPatchXML(fragment, []editorXMLPatch{editorXMLContent(fragment, node, node.textXML("FileLoc", "/"+file))})
} else {
- fragment = editorPatchXML(fragment, []editorXMLPatch{{node.open, node.open, editorXMLText("FileLoc", "/"+file)}})
+ fragment = editorPatchXML(fragment, []editorXMLPatch{{node.open, node.open, node.textXML("FileLoc", "/"+file)}})
}
root, err := parseEditorXML(fragment)
if err != nil {
diff --git a/ofdgo_editor_composite.go b/ofdgo_editor_composite.go
index 2445e2b..1e856fc 100644
--- a/ofdgo_editor_composite.go
+++ b/ofdgo_editor_composite.go
@@ -543,7 +543,7 @@ func (e *Editor) compositeMembers(n *editorCompositeNode, reader *Reader, render
var collect func(*editorXML) error
collect = func(container *editorXML) error {
for _, child := range container.children {
- if child.name.Space != "" && child.name.Space != ofdNamespace && child.name.Space != "http://www.ofdspec.org" {
+ if classifyOFDNamespace(child.name.Space) == ofdXMLUnknown {
continue
}
switch child.name.Local {
@@ -1055,7 +1055,7 @@ func editorResourceReferences(data []byte) ([]string, error) {
refs := make(map[string]bool)
var collect func(*editorXML)
collect = func(node *editorXML) {
- if node.name.Space == "" || node.name.Space == ofdNamespace || node.name.Space == "http://www.ofdspec.org" {
+ if classifyOFDNamespace(node.name.Space) != ofdXMLUnknown {
if node.name.Local == "Thumbnail" || node.name.Local == "Substitution" {
if id := editorResourceID(editorImportText(data, node)); id != "" {
refs[id] = true
diff --git a/ofdgo_editor_composite_copy.go b/ofdgo_editor_composite_copy.go
index dc41162..e853abb 100644
--- a/ofdgo_editor_composite_copy.go
+++ b/ofdgo_editor_composite_copy.go
@@ -347,7 +347,7 @@ func (n *editorCompositeNode) preserveActionRegions(matrix Matrix) (bool, error)
}
var targets []*editorXML
for _, action := range actions.children {
- if action.name.Local != "Action" || action.name.Space != "" && action.name.Space != actions.name.Space && action.name.Space != ofdNamespace {
+ if action.name.Local != "Action" || action.name.Space != "" && action.name.Space != actions.name.Space && action.name.Space != ofdNamespace2016 {
continue
}
if action.attr("Event") == "CLICK" && action.child("Region") == nil {
@@ -425,7 +425,7 @@ func (n *editorCompositeNode) convertCoordinates(parent Matrix, inward bool) err
}
}
for _, child := range node.children {
- if child.name.Space != "" && child.name.Space != ofdNamespace && child.name.Space != "http://www.ofdspec.org" {
+ if classifyOFDNamespace(child.name.Space) == ofdXMLUnknown {
continue
}
switch child.name.Local {
@@ -487,7 +487,7 @@ func (n *editorCompositeNode) loadedScope(path []int) *editorCompositeNode {
func compositeContainers(node *editorXML) []*editorXML {
var result []*editorXML
for _, child := range node.children {
- if (child.name.Local == "Content" || child.name.Local == "PageBlock") && (child.name.Space == "" || child.name.Space == ofdNamespace || child.name.Space == "http://www.ofdspec.org") {
+ if (child.name.Local == "Content" || child.name.Local == "PageBlock") && classifyOFDNamespace(child.name.Space) != ofdXMLUnknown {
result = append(result, child)
}
}
diff --git a/ofdgo_editor_composite_image.go b/ofdgo_editor_composite_image.go
index 78bc914..991fb0a 100644
--- a/ofdgo_editor_composite_image.go
+++ b/ofdgo_editor_composite_image.go
@@ -204,11 +204,17 @@ func appendCompositeClip(node *editorCompositeNode, shape PathObject, matrix Mat
}
var patch editorXMLPatch
if original := node.node.child("Clips"); original != nil {
- clip := editorXMLEncodedFragment(data, root.children[0])
+ clip, err := editorXMLEncodedFragment(data, root.children[0], original.name.Space)
+ if err != nil {
+ return err
+ }
content := append(bytes.Clone(node.data[original.open:original.close]), clip...)
patch = editorXMLContent(node.data, original, content)
} else {
- data = editorXMLEncodedFragment(data, root)
+ data, err = editorXMLEncodedFragment(data, root, node.node.name.Space)
+ if err != nil {
+ return err
+ }
if actions := node.node.child("Actions"); actions != nil {
patch = editorXMLPatch{actions.end, actions.end, data}
} else {
diff --git a/ofdgo_editor_copy.go b/ofdgo_editor_copy.go
index 1b29758..83ededd 100644
--- a/ofdgo_editor_copy.go
+++ b/ofdgo_editor_copy.go
@@ -135,7 +135,7 @@ func editorXMLRemapIDs(data []byte, root *editorXML, ids map[string]string) ([]b
var patches []editorXMLPatch
var visit func(*editorXML) error
visit = func(node *editorXML) error {
- if node.name.Space == "" || node.name.Space == ofdNamespace || node.name.Space == "http://www.ofdspec.org" {
+ if classifyOFDNamespace(node.name.Space) != ofdXMLUnknown {
decoder := xml.NewDecoder(bytes.NewReader(data[node.start:node.open]))
token, err := decoder.RawToken()
if err != nil {
diff --git a/ofdgo_editor_draw.go b/ofdgo_editor_draw.go
index e1ffb20..5cab1d9 100644
--- a/ofdgo_editor_draw.go
+++ b/ofdgo_editor_draw.go
@@ -43,7 +43,7 @@ func (e *Editor) drawParamXML(id string) ([]byte, error) {
}
if params := root.child("DrawParams"); params != nil {
for _, node := range params.children {
- if node.name.Local == "DrawParam" && node.attr("ID") == id && (node.name.Space == "" || node.name.Space == ofdNamespace || node.name.Space == "http://www.ofdspec.org") {
+ if node.name.Local == "DrawParam" && node.attr("ID") == id && classifyOFDNamespace(node.name.Space) != ofdXMLUnknown {
return editorXMLStandalone(data[node.start:node.end], node)
}
}
diff --git a/ofdgo_editor_group.go b/ofdgo_editor_group.go
index 29df555..edc8904 100644
--- a/ofdgo_editor_group.go
+++ b/ofdgo_editor_group.go
@@ -202,7 +202,7 @@ func editorUngroupable(node *editorXML) bool {
return false
}
for _, member := range content.children {
- if member.name.Space != "" && member.name.Space != ofdNamespace && member.name.Space != "http://www.ofdspec.org" {
+ if classifyOFDNamespace(member.name.Space) == ofdXMLUnknown {
return false
}
if member.name.Local == "PageBlock" {
diff --git a/ofdgo_editor_import.go b/ofdgo_editor_import.go
index 19580bd..3017f6d 100644
--- a/ofdgo_editor_import.go
+++ b/ofdgo_editor_import.go
@@ -644,6 +644,10 @@ func (e *Editor) appendImportedOutlines(imported []byte) ([]byte, error) {
if err != nil {
return nil, err
}
+ value, err = editorXMLGenerated(value, root.name.Space)
+ if err != nil {
+ return nil, err
+ }
content = append(content, value...)
}
preview := *e
@@ -882,11 +886,11 @@ func editorImportText(data []byte, node *editorXML) string {
return value.Text
}
-// encode 编码标准OFD节点,保留文字数据和几何值,仅改写资源、标识及路径
+// encode 编码标准OFD节点,重映射引用并将旧式裁剪换算到标准坐标
// 入参: entry 来源上下文, node 当前节点
// 返回: []byte 独立XML片段, error 错误信息
func (m *editorPageImport) encode(entry editorImportEntry, node *editorXML) ([]byte, error) {
- if node.name.Space != "" && node.name.Space != ofdNamespace && node.name.Space != "http://www.ofdspec.org" {
+ if classifyOFDNamespace(node.name.Space) == ofdXMLUnknown {
return nil, fmt.Errorf("cannot remap extension namespace %q", node.name.Space)
}
name := node.name.Local
@@ -931,6 +935,9 @@ func (m *editorPageImport) encode(entry editorImportEntry, node *editorXML) ([]b
continue
}
key, value := attr.Name.Local, attr.Value
+ if name == "Clips" && key == "TransFlag" {
+ continue
+ }
var err error
switch key {
case "ID":
@@ -974,6 +981,22 @@ func (m *editorPageImport) encode(entry editorImportEntry, node *editorXML) ([]b
}
attrs = append(attrs, xml.Attr{Name: xml.Name{Local: key}, Value: value})
}
+ if name == "Area" && node.parent != nil && node.parent.name.Local == "Clip" && node.parent.parent != nil && node.parent.parent.name.Local == "Clips" {
+ clips := node.parent.parent
+ transformed := classifyOFDNamespace(clips.name.Space) == ofdXMLLegacy
+ if clips.hasAttr("TransFlag") {
+ var err error
+ transformed, err = strconv.ParseBool(clips.attr("TransFlag"))
+ if err != nil {
+ return nil, err
+ }
+ }
+ if transformed && clips.parent != nil {
+ matrix := NewMatrix(clips.parent.attr("CTM")).Multiply(NewMatrix(node.attr("CTM")))
+ attrs = slices.DeleteFunc(attrs, func(attr xml.Attr) bool { return attr.Name.Space == "" && attr.Name.Local == "CTM" })
+ attrs.add("CTM", matrix.String())
+ }
+ }
if m.defaultCS != "" && (name == "FillColor" || name == "StrokeColor" || name == "Color") && node.attr("ColorSpace") == "" {
attrs = append(attrs, xml.Attr{Name: xml.Name{Local: "ColorSpace"}, Value: m.defaultCS})
}
@@ -1251,14 +1274,18 @@ func (m *editorPageImport) merge(base *editorSource, refs []Page, annotations, s
return nil, err
}
if added {
- content = editorXMLText("DocumentRes", "/"+strings.TrimPrefix(name, "/"))
+ content = common.textXML("DocumentRes", "/"+strings.TrimPrefix(name, "/"))
}
}
for _, id := range slices.Sorted(maps.Keys(m.templateParts)) {
- content = append(content, m.templateParts[id]...)
+ entry, err := editorXMLGenerated(m.templateParts[id], common.name.Space)
+ if err != nil {
+ return nil, err
+ }
+ content = append(content, entry...)
}
patches := []editorXMLPatch{{position, position, content}}
- patch := editorXMLPatch{common.open, common.open, editorXMLText("MaxUnitID", strconv.Itoa(m.maximum))}
+ patch := editorXMLPatch{common.open, common.open, common.textXML("MaxUnitID", strconv.Itoa(m.maximum))}
if maximum := common.child("MaxUnitID"); maximum != nil {
patch = editorXMLContent(data, maximum, []byte(strconv.Itoa(m.maximum)))
}
@@ -1268,10 +1295,14 @@ func (m *editorPageImport) merge(base *editorSource, refs []Page, annotations, s
return nil, fmt.Errorf("document has no Pages")
}
if len(m.pageParts) != 0 {
+ entries, err := editorXMLGenerated(m.pageParts, pages.name.Space)
+ if err != nil {
+ return nil, err
+ }
if pages.open == pages.end {
- patches = append(patches, editorXMLContent(data, pages, m.pageParts))
+ patches = append(patches, editorXMLContent(data, pages, entries))
} else {
- patches = append(patches, editorXMLPatch{pages.close, pages.close, m.pageParts})
+ patches = append(patches, editorXMLPatch{pages.close, pages.close, entries})
}
}
data = editorPatchXML(data, patches)
@@ -1282,7 +1313,10 @@ func (m *editorPageImport) merge(base *editorSource, refs []Page, annotations, s
}
if base.document.Annotations == "" {
root, _ = parseEditorXML(data)
- data = editorXMLSetText(data, root, [][2]string{{"Annotations", loc}})
+ data, err = editorXMLSetText(data, root, [][2]string{{"Annotations", loc}})
+ if err != nil {
+ return nil, err
+ }
}
}
if len(m.attachments) != 0 {
@@ -1293,6 +1327,10 @@ func (m *editorPageImport) merge(base *editorSource, refs []Page, annotations, s
root, _ = parseEditorXML(data)
node := root.child("Attachments")
if node != nil && len(node.children) != 0 {
+ entries, err = editorXMLGenerated(entries, node.name.Space)
+ if err != nil {
+ return nil, err
+ }
content := append(bytes.Clone(data[node.open:node.close]), entries...)
data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, node, content)})
} else {
@@ -1301,7 +1339,10 @@ func (m *editorPageImport) merge(base *editorSource, refs []Page, annotations, s
return nil, err
}
if base.document.Attachments.Path == "" {
- data = editorXMLSetText(data, root, [][2]string{{"Attachments", loc}})
+ data, err = editorXMLSetText(data, root, [][2]string{{"Attachments", loc}})
+ if err != nil {
+ return nil, err
+ }
}
}
}
@@ -1324,7 +1365,10 @@ func (m *editorPageImport) merge(base *editorSource, refs []Page, annotations, s
if file, ok := base.reader.packageFile(name); ok {
name = cleanPackagePath(file.Name)
}
- files[name] = editorXMLSetText(ofd, root.childAt("DocBody", base.reader.documentIndex), [][2]string{{"Signatures", loc}})
+ files[name], err = editorXMLSetText(ofd, root.childAt("DocBody", base.reader.documentIndex), [][2]string{{"Signatures", loc}})
+ if err != nil {
+ return nil, err
+ }
}
}
reader := &Reader{Zip: base.reader.Zip, files: files, encryption: base.reader.encryption, documentIndex: base.reader.documentIndex}
@@ -1377,6 +1421,10 @@ func mergeImportIndex(reader *Reader, files map[string][]byte, loc, fallback, ro
if parseErr != nil {
return "", parseErr
}
+ entries, err = editorXMLGenerated(entries, root.name.Space)
+ if err != nil {
+ return "", err
+ }
if root.open == root.end {
data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, root, entries)})
} else {
@@ -1400,7 +1448,7 @@ func mergeImportIndex(reader *Reader, files map[string][]byte, loc, fallback, ro
id, _ := strconv.Atoi(value)
maximum = max(maximum, id)
}
- value := editorXMLText("MaxSignId", strconv.Itoa(maximum))
+ value := root.textXML("MaxSignId", strconv.Itoa(maximum))
patch := editorXMLPatch{root.open, root.open, value}
if node := root.child("MaxSignId"); node != nil {
patch = editorXMLPatch{node.start, node.end, value}
diff --git a/ofdgo_editor_link.go b/ofdgo_editor_link.go
index 62e2b4f..f588181 100644
--- a/ofdgo_editor_link.go
+++ b/ofdgo_editor_link.go
@@ -199,7 +199,10 @@ func (e *Editor) UpdateAnnotationLink(page int, id string, target AnnotationLink
}
if len(targets) == 1 {
n := targets[0]
- updated := link
+ updated, err := editorXMLGenerated(link, n.name.Space)
+ if err != nil {
+ return nil, err
+ }
if n.name.Local == "URI" && target.URI != nil {
var old URI
if err := xml.Unmarshal(data[n.start:n.end], &old); err != nil {
@@ -261,12 +264,20 @@ func (e *Editor) UpdateAnnotationLink(page int, id string, target AnnotationLink
}
action = bytes.TrimPrefix(action, []byte(xml.Header))
if actions := objects[0].child("Actions"); actions != nil {
+ action, err = editorXMLGenerated(action, actions.name.Space)
+ if err != nil {
+ return nil, err
+ }
data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, actions, append(bytes.Clone(data[actions.open:actions.close]), action...))})
} else {
actions, err := editorXMLContainer("Actions", nil, action)
if err != nil {
return nil, err
}
+ actions, err = editorXMLGenerated(actions, objects[0].name.Space)
+ if err != nil {
+ return nil, err
+ }
position := objects[0].open
data = editorPatchXML(data, []editorXMLPatch{{position, position, bytes.TrimPrefix(actions, []byte(xml.Header))}})
}
diff --git a/ofdgo_editor_metadata.go b/ofdgo_editor_metadata.go
index 3c84edd..bb418aa 100644
--- a/ofdgo_editor_metadata.go
+++ b/ofdgo_editor_metadata.go
@@ -18,7 +18,6 @@ import (
"bytes"
"encoding/xml"
"fmt"
- "io"
"reflect"
)
@@ -51,7 +50,10 @@ func editorUpdateInfoXML(data []byte, docID string, index int, before, after Doc
values = append(values, [2]string{field[0], field[2]})
}
}
- data = editorXMLSetText(data, info, values)
+ data, err = editorXMLSetText(data, info, values)
+ if err != nil {
+ return nil, err
+ }
if !reflect.DeepEqual(before.CustomDatas, after.CustomDatas) {
root, err := parseEditorXML(data)
if err != nil {
@@ -70,7 +72,7 @@ func editorCreatorXML(data []byte, docID string, index int) ([]byte, error) {
if err != nil {
return nil, err
}
- return editorXMLSetText(data, info, [][2]string{{"Creator", ofdCreator}, {"CreatorVersion", ""}}), nil
+ return editorXMLSetText(data, info, [][2]string{{"Creator", ofdCreator}, {"CreatorVersion", ""}})
}
// editorDocInfoXML 获取文档描述,修改缺少描述的源文件时补建标准容器及标识
@@ -88,11 +90,15 @@ func editorDocInfoXML(data []byte, docID string, index int) ([]byte, *editorXML,
if info := body.child("DocInfo"); info != nil {
return data, info, nil
}
- info, err := editorXMLContainer("DocInfo", nil, editorXMLText("DocID", docID))
+ info, err := body.containerXML("DocInfo", nil, body.textXML("DocID", docID))
if err != nil {
return nil, nil, err
}
- data = editorPatchXML(data, []editorXMLPatch{{body.open, body.open, info}})
+ if body.open == body.end {
+ data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, body, info)})
+ } else {
+ data = editorPatchXML(data, []editorXMLPatch{{body.open, body.open, info}})
+ }
root, err = parseEditorXML(data)
if err != nil {
return nil, nil, err
@@ -105,6 +111,10 @@ func editorDocInfoXML(data []byte, docID string, index int) ([]byte, *editorXML,
// 返回: []byte 修改后的XML, error 编码错误
func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byte, error) {
container := info.child("CustomDatas")
+ context := info
+ if container != nil {
+ context = container
+ }
var items []CustomData
if values != nil {
items = values.CustomData
@@ -113,7 +123,7 @@ func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byt
var nodes []*editorXML
if container != nil {
for _, node := range container.children {
- if node.name.Local == "CustomData" && (node.name.Space == ofdNamespace || node.name.Space == container.name.Space) {
+ if node.name.Local == "CustomData" && (node.name.Space == ofdNamespace2016 || node.name.Space == container.name.Space) {
nodes = append(nodes, node)
byName[node.attr("Name")] = append(byName[node.attr("Name")], node)
}
@@ -140,10 +150,9 @@ func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byt
}
}
if node == nil {
- entry, err := xml.Marshal(struct {
- XMLName xml.Name
- CustomData
- }{xml.Name{Space: ofdNamespace, Local: "CustomData"}, item})
+ var value bytes.Buffer
+ _ = xml.EscapeText(&value, []byte(item.Value))
+ entry, err := context.containerXML("CustomData", ofdAttrs{{Name: xml.Name{Local: "Name"}, Value: item.Name}}, value.Bytes())
if err != nil {
return nil, err
}
@@ -163,14 +172,14 @@ func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byt
if err != nil {
return nil, err
}
- updated, err := editorCustomDataValue(entry, renamed, original.Value, item.Value)
+ updated, err := editorXMLTextValue(entry, renamed, original.Value, item.Value)
if err != nil {
return nil, err
}
encoded = append(encoded, updated)
continue
}
- entry, err := editorCustomDataValue(data, node, original.Value, item.Value)
+ entry, err := editorXMLTextValue(data, node, original.Value, item.Value)
if err != nil {
return nil, err
}
@@ -180,9 +189,10 @@ func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byt
if len(encoded) == 0 {
return data, nil
}
- content := []byte("")
- content = append(content, bytes.Join(encoded, nil)...)
- content = append(content, []byte("")...)
+ content, err := info.containerXML("CustomDatas", nil, bytes.Join(encoded, nil))
+ if err != nil {
+ return nil, err
+ }
if info.open == info.end {
return editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, info, content)}), nil
}
@@ -206,44 +216,3 @@ func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byt
}
return editorPatchXML(data, patches), nil
}
-
-// editorCustomDataValue 修改字段文本,保留扩展子节点和注释
-// 入参: data 原XML, node 字段节点, before 原值, after 新值
-// 返回: []byte 字段XML, error 编码错误
-func editorCustomDataValue(data []byte, node *editorXML, before, after string) ([]byte, error) {
- if before == after {
- return data[node.start:node.end], nil
- }
- var content bytes.Buffer
- if err := xml.EscapeText(&content, []byte(after)); err != nil {
- return nil, err
- }
- if node.open < node.close {
- inner := data[node.open:node.close]
- decoder := xml.NewDecoder(bytes.NewReader(inner))
- depth := 0
- for {
- start := decoder.InputOffset()
- token, err := decoder.RawToken()
- if err == io.EOF {
- break
- }
- if err != nil {
- return nil, err
- }
- _, text := token.(xml.CharData)
- if !text || depth != 0 {
- content.Write(inner[start:decoder.InputOffset()])
- }
- switch token.(type) {
- case xml.StartElement:
- depth++
- case xml.EndElement:
- depth--
- }
- }
- }
- patch := editorXMLContent(data, node, content.Bytes())
- entry := append(bytes.Clone(data[node.start:patch.start]), patch.data...)
- return append(entry, data[patch.end:node.end]...), nil
-}
diff --git a/ofdgo_editor_namespace.go b/ofdgo_editor_namespace.go
new file mode 100644
index 0000000..46c098e
--- /dev/null
+++ b/ofdgo_editor_namespace.go
@@ -0,0 +1,169 @@
+// 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"
+ "encoding/xml"
+ "strconv"
+)
+
+// textXML 按所在节点的命名空间编码文本节点
+// 入参: name 节点名称, value 文本
+// 返回: []byte XML片段
+func (n *editorXML) textXML(name, value string) []byte {
+ var content bytes.Buffer
+ _ = xml.EscapeText(&content, []byte(value))
+ data, _ := n.containerXML(name, nil, content.Bytes())
+ return data
+}
+
+// containerXML 按所在节点的命名空间封装子节点,不改写子节点原文
+// 入参: name 节点名称, attrs 属性, content 子节点XML
+// 返回: []byte XML片段, error 编码错误
+func (n *editorXML) containerXML(name string, attrs ofdAttrs, content []byte) ([]byte, error) {
+ var output bytes.Buffer
+ if n.name.Space == "" {
+ attrs = append(attrs, xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: ""})
+ } else {
+ name = "ofd:" + name
+ attrs.add("xmlns:ofd", n.name.Space)
+ }
+ value := struct {
+ Content []byte `xml:",innerxml"`
+ }{content}
+ err := xml.NewEncoder(&output).EncodeElement(value, xml.StartElement{Name: xml.Name{Local: name}, Attr: attrs})
+ return output.Bytes(), err
+}
+
+// editorXMLGenerated 将自产标准片段用于指定命名空间,已标准化的旧版裁剪保持2016语义
+// 入参: data 自产XML片段,不含待保留的源文件原文, namespace 目标命名空间
+// 返回: []byte XML片段, error 解析错误
+func editorXMLGenerated(data []byte, namespace string) ([]byte, error) {
+ if namespace == ofdNamespace2016 || len(data) == 0 {
+ return data, nil
+ }
+ header := []byte(``)
+ wrapped := append(bytes.Clone(header), bytes.TrimPrefix(data, []byte(xml.Header))...)
+ end := len(wrapped)
+ wrapped = append(wrapped, []byte(``)...)
+ root, err := parseEditorXML(wrapped)
+ if err != nil {
+ return nil, err
+ }
+ prefix := editorXMLNamespacePrefix(root)
+ var patches []editorXMLPatch
+ var visit func(*editorXML, bool) error
+ visit = func(node *editorXML, declare bool) error {
+ if node.name.Space != ofdNamespace2016 || namespace == ofdNamespaceLegacy && node.name.Local == "Clips" {
+ value, err := editorXMLStandalone(wrapped[node.start:node.end], node)
+ if err != nil {
+ return err
+ }
+ patches = append(patches, editorXMLPatch{node.start, node.end, value})
+ return nil
+ }
+ decoder := xml.NewDecoder(bytes.NewReader(wrapped[node.start:node.open]))
+ token, err := decoder.RawToken()
+ if err != nil {
+ return err
+ }
+ start := token.(xml.StartElement)
+ name := node.name.Local
+ if namespace != "" {
+ name = prefix + ":" + name
+ }
+ var output bytes.Buffer
+ output.WriteString("<" + name)
+ ofdDeclared := false
+ for _, attr := range start.Attr {
+ if attr.Name.Space == "xmlns" && attr.Name.Local == "ofd" {
+ ofdDeclared = true
+ }
+ if namespace == "" && attr.Name.Space == "" && attr.Name.Local == "xmlns" {
+ continue
+ }
+ key := attr.Name.Local
+ if attr.Name.Space != "" {
+ key = attr.Name.Space + ":" + key
+ }
+ output.WriteString(" " + key + `="`)
+ _ = xml.EscapeText(&output, []byte(attr.Value))
+ output.WriteByte('"')
+ }
+ if declare && !ofdDeclared {
+ output.WriteString(` xmlns:ofd="` + ofdNamespace2016 + `"`)
+ }
+ if declare || namespace == "" {
+ key := "xmlns"
+ if namespace != "" {
+ key = "xmlns:" + prefix
+ }
+ output.WriteString(" " + key + `="`)
+ _ = xml.EscapeText(&output, []byte(namespace))
+ output.WriteByte('"')
+ }
+ if node.open == node.end {
+ output.WriteString("/>")
+ } else {
+ output.WriteByte('>')
+ patches = append(patches, editorXMLPatch{node.close, node.end, []byte("" + name + ">")})
+ }
+ patches = append(patches, editorXMLPatch{node.start, node.open, output.Bytes()})
+ for _, child := range node.children {
+ if err := visit(child, false); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ for _, node := range root.children {
+ if err := visit(node, true); err != nil {
+ return nil, err
+ }
+ }
+ patches = append(patches, editorXMLPatch{0, len(header), nil}, editorXMLPatch{end, len(wrapped), nil})
+ return editorPatchXML(wrapped, patches), nil
+}
+
+// editorXMLNamespacePrefix 选择未被当前作用域及子树占用的命名空间前缀
+// 入参: node 节点
+// 返回: string 可用前缀
+func editorXMLNamespacePrefix(node *editorXML) string {
+ used := make(map[string]bool)
+ collect := func(current *editorXML) {
+ for _, attr := range current.attrs {
+ if attr.Name.Space == "xmlns" {
+ used[attr.Name.Local] = true
+ }
+ }
+ }
+ for parent := node.parent; parent != nil; parent = parent.parent {
+ collect(parent)
+ }
+ var visit func(*editorXML)
+ visit = func(current *editorXML) {
+ collect(current)
+ for _, child := range current.children {
+ visit(child)
+ }
+ }
+ visit(node)
+ prefix := "ofd"
+ for index := 1; used[prefix]; index++ {
+ prefix = "ofd" + strconv.Itoa(index)
+ }
+ return prefix
+}
diff --git a/ofdgo_editor_outline.go b/ofdgo_editor_outline.go
index c55ebe9..7b10495 100644
--- a/ofdgo_editor_outline.go
+++ b/ofdgo_editor_outline.go
@@ -68,6 +68,10 @@ func (e *Editor) AddOutline(parent []int, title string, page int) ([]int, error)
if err != nil {
return nil, err
}
+ item, err = editorXMLGenerated(item, node.name.Space)
+ if err != nil {
+ return nil, err
+ }
var content []byte
if node.open != node.end {
content = bytes.Clone(data[node.open:node.close])
@@ -115,7 +119,7 @@ func (e *Editor) UpdateOutline(path []int, title string, page int) error {
if actions != nil {
var removals []editorXMLPatch
for _, action := range actions.children {
- if action.name.Space != actions.name.Space && action.name.Space != ofdNamespace && action.name.Space != "" {
+ if action.name.Space != actions.name.Space && action.name.Space != ofdNamespace2016 && action.name.Space != "" {
continue
}
if action.name.Local == "Action" && action.child("Goto") != nil {
@@ -126,7 +130,10 @@ func (e *Editor) UpdateOutline(path []int, title string, page int) error {
if actions.open != actions.end {
content = editorPatchXML(data[actions.open:actions.close], removals)
}
- added := e.outlineAction(page)
+ added, err := editorXMLGenerated(e.outlineAction(page), actions.name.Space)
+ if err != nil {
+ return err
+ }
if len(added) > 0 {
addRoot, err := parseEditorXML(added)
if err != nil {
@@ -141,8 +148,10 @@ func (e *Editor) UpdateOutline(path []int, title string, page int) error {
}
patches = append(patches, editorXMLContent(data, actions, content))
} else if page >= 0 {
- var content []byte
- content = append(content, e.outlineAction(page)...)
+ content, err := editorXMLGenerated(e.outlineAction(page), node.name.Space)
+ if err != nil {
+ return err
+ }
if node.open != node.end {
content = append(content, data[node.open:node.close]...)
}
@@ -275,6 +284,7 @@ func (e *Editor) outlineXML() ([]byte, error) {
if node := root.child("Outlines"); node != nil {
return editorXMLStandalone(data[node.start:node.end], node)
}
+ return root.containerXML("Outlines", nil, nil)
}
return editorXMLContainer("Outlines", nil, nil)
}
@@ -305,7 +315,7 @@ func (e *Editor) outlineAction(page int) []byte {
func editorOutlineChildren(node *editorXML) []*editorXML {
var children []*editorXML
for _, child := range node.children {
- if child.name.Local == "OutlineElem" && (child.name.Space == node.name.Space || child.name.Space == ofdNamespace || child.name.Space == "") {
+ if child.name.Local == "OutlineElem" && (child.name.Space == node.name.Space || child.name.Space == ofdNamespace2016 || child.name.Space == "") {
children = append(children, child)
}
}
@@ -415,7 +425,7 @@ func (e *Editor) withOutlines(data []byte) ([]byte, error) {
walk = func(node *editorXML) {
if actions := node.child("Actions"); actions != nil {
for _, action := range actions.children {
- if action.name.Local != "Action" || action.name.Space != actions.name.Space && action.name.Space != ofdNamespace && action.name.Space != "" {
+ if action.name.Local != "Action" || action.name.Space != actions.name.Space && action.name.Space != ofdNamespace2016 && action.name.Space != "" {
continue
}
if target := action.child("Goto"); target != nil {
diff --git a/ofdgo_editor_package.go b/ofdgo_editor_package.go
index 18bac92..7ed4e06 100644
--- a/ofdgo_editor_package.go
+++ b/ofdgo_editor_package.go
@@ -149,8 +149,9 @@ func (e *Editor) sourcePartsPrepared(progress editorProgress, generated map[stri
}
var patches []editorXMLPatch
if e.maxID != source.document.CommonData.MaxUnitID {
- value := editorXMLText("MaxUnitID", strconv.Itoa(e.maxID))
+ value := common.textXML("MaxUnitID", strconv.Itoa(e.maxID))
if maxID := common.child("MaxUnitID"); maxID != nil {
+ value = maxID.textXML("MaxUnitID", strconv.Itoa(e.maxID))
patches = append(patches, editorXMLPatch{maxID.start, maxID.end, value})
} else {
patches = append(patches, editorXMLPatch{common.open, common.open, value})
@@ -166,7 +167,7 @@ func (e *Editor) sourcePartsPrepared(progress editorProgress, generated map[stri
}
var references []byte
for _, name := range resourceFiles {
- references = append(references, editorXMLText("DocumentRes", "/"+name)...)
+ references = append(references, common.textXML("DocumentRes", "/"+name)...)
}
patches = append(patches, editorXMLPatch{position, position, references})
}
@@ -190,10 +191,7 @@ func (e *Editor) sourcePartsPrepared(progress editorProgress, generated map[stri
if original != nil {
entries = append(entries, data[original.start:original.end])
} else {
- item, err := e.encodeXML(func(x *ofdXML) {
- x.root("Page", ofdAttrs{{Name: xml.Name{Local: "ID"}, Value: page.ID}, {Name: xml.Name{Local: "BaseLoc"}, Value: page.BaseLoc}})
- x.end("Page")
- })
+ item, err := pages.containerXML("Page", ofdAttrs{{Name: xml.Name{Local: "ID"}, Value: page.ID}, {Name: xml.Name{Local: "BaseLoc"}, Value: page.BaseLoc}}, nil)
if err != nil {
return nil, err
}
@@ -308,17 +306,24 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err
if page.Area.PhysicalBox != source.original.Area.PhysicalBox {
area := source.root.child("Area")
if area != nil {
- areaData := data[area.start:area.end]
+ areaData, err := editorXMLStandalone(data[area.start:area.end], area)
+ if err != nil {
+ return nil, err
+ }
areaRoot, err := parseEditorXML(areaData)
if err != nil {
return nil, err
}
- updated := editorXMLSetText(areaData, areaRoot, [][2]string{{"PhysicalBox", page.Area.PhysicalBox}})
+ updated, err := editorXMLSetText(areaData, areaRoot, [][2]string{{"PhysicalBox", page.Area.PhysicalBox}})
+ if err != nil {
+ return nil, err
+ }
patches = append(patches, editorXMLPatch{area.start, area.end, updated})
} else {
- value := []byte("")
- value = append(value, editorXMLText("PhysicalBox", page.Area.PhysicalBox)...)
- value = append(value, []byte("")...)
+ value, err := source.root.containerXML("Area", nil, source.root.textXML("PhysicalBox", page.Area.PhysicalBox))
+ if err != nil {
+ return nil, err
+ }
patches = append(patches, editorXMLPatch{source.root.open, source.root.open, value})
}
}
@@ -400,11 +405,15 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err
}
}
var added []byte
+ context := source.root.child("Content")
+ if context == nil {
+ context = source.root
+ }
for _, layer := range page.Content.Layer[len(source.original.Content.Layer):] {
if len(layer.Objects) == 0 {
continue
}
- encoded, err := e.sourceLayerXML(layer)
+ encoded, err := e.sourceLayerXML(layer, context.name.Space)
if err != nil {
return nil, err
}
@@ -413,9 +422,10 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err
if len(added) != 0 {
content := source.root.child("Content")
if content == nil {
- value := []byte("")
- value = append(value, added...)
- value = append(value, []byte("")...)
+ value, err := context.containerXML("Content", nil, added)
+ if err != nil {
+ return nil, err
+ }
position := source.root.close
if actions := source.root.child("Actions"); actions != nil {
position = actions.start
@@ -433,8 +443,14 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err
return nil, err
}
patch := editorXMLPatch{source.root.close, source.root.close, encoded}
+ context := source.root
if node := source.root.child("Actions"); node != nil {
patch.start, patch.end = node.start, node.end
+ context = node
+ }
+ patch.data, err = editorXMLGenerated(encoded, context.name.Space)
+ if err != nil {
+ return nil, err
}
patches = append(patches, patch)
}
@@ -442,9 +458,9 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err
}
// sourceLayerXML 写出新增图层,对复制对象保留原始定位和显式默认值
-// 入参: layer 图层
+// 入参: layer 图层, namespace 所在容器的命名空间
// 返回: []byte 图层XML, error 错误信息
-func (e *Editor) sourceLayerXML(layer Layer) ([]byte, error) {
+func (e *Editor) sourceLayerXML(layer Layer, namespace string) ([]byte, error) {
if !slices.ContainsFunc(layer.Objects, func(object GraphicObject) bool { return e.objectOrigin(editorObjectID(object)) != nil }) {
data, err := e.encodeXML(func(x *ofdXML) {
attrs := ofdAttrs{{Name: xml.Name{Local: "ID"}, Value: layer.ID}, {Name: xml.Name{Local: "Type"}, Value: layer.Type}}
@@ -458,7 +474,10 @@ func (e *Editor) sourceLayerXML(layer Layer) ([]byte, error) {
}
x.end("Layer")
})
- return bytes.TrimPrefix(data, []byte(xml.Header)), err
+ if err != nil {
+ return nil, err
+ }
+ return editorXMLGenerated(bytes.TrimPrefix(data, []byte(xml.Header)), namespace)
}
var content []byte
for _, object := range layer.Objects {
@@ -471,6 +490,9 @@ func (e *Editor) sourceLayerXML(layer Layer) ([]byte, error) {
}
} else {
data, err = editorObjectXML(object)
+ if err == nil {
+ data, err = editorXMLGenerated(data, namespace)
+ }
}
if err != nil {
return nil, err
@@ -479,7 +501,7 @@ func (e *Editor) sourceLayerXML(layer Layer) ([]byte, error) {
}
attrs := ofdAttrs{{Name: xml.Name{Local: "ID"}, Value: layer.ID}, {Name: xml.Name{Local: "Type"}, Value: layer.Type}}
attrs.add("DrawParam", layer.DrawParam)
- return editorXMLContainer("Layer", attrs, content)
+ return (&editorXML{name: xml.Name{Space: namespace}}).containerXML("Layer", attrs, content)
}
// sourceNewPageXML 编码新增页面,同时保留复制对象的原文语义
@@ -510,7 +532,7 @@ func (e *Editor) sourcePageData(page PageContent, references *editorReferenceSca
}
var layers []byte
for _, layer := range page.Content.Layer {
- data, err := e.sourceLayerXML(layer)
+ data, err := e.sourceLayerXML(layer, ofdNamespace2016)
if err != nil {
return nil, err
}
diff --git a/ofdgo_editor_pages.go b/ofdgo_editor_pages.go
index a64d914..fd36abc 100644
--- a/ofdgo_editor_pages.go
+++ b/ofdgo_editor_pages.go
@@ -97,7 +97,7 @@ func (e *Editor) prunePageReferences(parts map[string][]byte, generated map[stri
return err
}
}
- if node.name.Space != "" && node.name.Space != ofdNamespace && node.name.Space != "http://www.ofdspec.org" {
+ if classifyOFDNamespace(node.name.Space) == ofdXMLUnknown {
return nil
}
remove := false
@@ -192,7 +192,7 @@ func pruneCreatedPageReferences(data []byte, removed map[string]bool) ([]byte, e
var patches []editorXMLPatch
var walk func(*editorXML)
walk = func(node *editorXML) {
- if node.name.Space != ofdNamespace {
+ if node.name.Space != ofdNamespace2016 {
return
}
if node.name.Local == "Action" {
diff --git a/ofdgo_editor_reference.go b/ofdgo_editor_reference.go
index 02d55c6..24f9562 100644
--- a/ofdgo_editor_reference.go
+++ b/ofdgo_editor_reference.go
@@ -126,7 +126,7 @@ func (s *editorReferenceScan) accept(token xml.Token) bool {
}
switch token := token.(type) {
case xml.StartElement:
- if token.Name.Space != "" && token.Name.Space != ofdNamespace {
+ if token.Name.Space != "" && token.Name.Space != ofdNamespace2016 {
s.safe = false
return false
}
@@ -233,7 +233,7 @@ func (x *ofdXML) referenceToken(token xml.Token) {
s := x.references
switch value := token.(type) {
case xml.StartElement:
- value.Name = xml.Name{Space: ofdNamespace, Local: strings.TrimPrefix(value.Name.Local, "ofd:")}
+ value.Name = xml.Name{Space: ofdNamespace2016, Local: strings.TrimPrefix(value.Name.Local, "ofd:")}
for _, attr := range value.Attr {
if editorResourceReferenceKind(attr.Name.Local, s.resource) != editorReferenceNone && !editorReferenceTextSafe(attr.Value) {
s.safe = false
@@ -241,7 +241,7 @@ func (x *ofdXML) referenceToken(token xml.Token) {
}
token = value
case xml.EndElement:
- value.Name = xml.Name{Space: ofdNamespace, Local: strings.TrimPrefix(value.Name.Local, "ofd:")}
+ value.Name = xml.Name{Space: ofdNamespace2016, Local: strings.TrimPrefix(value.Name.Local, "ofd:")}
token = value
case xml.CharData:
if s.capture && !editorReferenceTextSafe(string(value)) {
diff --git a/ofdgo_editor_xml.go b/ofdgo_editor_xml.go
index cac71c9..12e8cab 100644
--- a/ofdgo_editor_xml.go
+++ b/ofdgo_editor_xml.go
@@ -92,7 +92,7 @@ func (n *editorXML) child(name string) *editorXML {
// 返回: *editorXML 节点,不存在时为nil
func (n *editorXML) childAt(name string, index int) *editorXML {
for _, child := range n.children {
- if child.name.Local == name && (child.name.Space == n.name.Space || child.name.Space == ofdNamespace || child.name.Space == "") {
+ if child.name.Local == name && (child.name.Space == n.name.Space || child.name.Space == ofdNamespace2016 || child.name.Space == "") {
if index == 0 {
return child
}
@@ -162,15 +162,13 @@ func editorXMLContent(data []byte, node *editorXML, content []byte) editorXMLPat
// 入参: name 节点名称, value 文本
// 返回: []byte XML片段
func editorXMLText(name, value string) []byte {
- var escaped bytes.Buffer
- _ = xml.EscapeText(&escaped, []byte(value))
- return []byte("" + escaped.String() + "")
+ return (&editorXML{name: xml.Name{Space: ofdNamespace2016}}).textXML(name, value)
}
// editorXMLSetText 更新或追加直接子节点,保留其他节点
// 入参: data 原文, node 父节点, values 需要修改的字段
-// 返回: []byte 修改后的XML
-func editorXMLSetText(data []byte, node *editorXML, values [][2]string) []byte {
+// 返回: []byte 修改后的XML, error 文本编码错误
+func editorXMLSetText(data []byte, node *editorXML, values [][2]string) ([]byte, error) {
var patches []editorXMLPatch
var added []byte
order := []string{"PhysicalBox", "ApplicationBox", "ContentBox", "BleedBox"}
@@ -181,10 +179,18 @@ func editorXMLSetText(data []byte, node *editorXML, values [][2]string) []byte {
child := node.child(value[0])
var encoded []byte
if value[1] != "" {
- encoded = editorXMLText(value[0], value[1])
+ encoded = node.textXML(value[0], value[1])
}
if child != nil {
- patches = append(patches, editorXMLPatch{child.start, child.end, encoded})
+ if value[1] == "" {
+ patches = append(patches, editorXMLPatch{child.start, child.end, nil})
+ } else {
+ encoded, err := editorXMLTextValue(data, child, editorImportText(data, child), value[1])
+ if err != nil {
+ return nil, err
+ }
+ patches = append(patches, editorXMLPatch{child.start, child.end, encoded})
+ }
} else if node.open == node.end {
added = append(added, encoded...)
} else if len(encoded) != 0 {
@@ -201,7 +207,48 @@ func editorXMLSetText(data []byte, node *editorXML, values [][2]string) []byte {
if len(added) != 0 {
patches = append(patches, editorXMLContent(data, node, added))
}
- return editorPatchXML(data, patches)
+ return editorPatchXML(data, patches), nil
+}
+
+// editorXMLTextValue 修改字段文本,保留命名空间、属性、扩展子节点和注释
+// 入参: data 原XML, node 字段节点, before 原值, after 新值
+// 返回: []byte 字段XML, error 编码错误
+func editorXMLTextValue(data []byte, node *editorXML, before, after string) ([]byte, error) {
+ if before == after {
+ return data[node.start:node.end], nil
+ }
+ var content bytes.Buffer
+ if err := xml.EscapeText(&content, []byte(after)); err != nil {
+ return nil, err
+ }
+ if node.open < node.close {
+ inner := data[node.open:node.close]
+ decoder := xml.NewDecoder(bytes.NewReader(inner))
+ depth := 0
+ for {
+ start := decoder.InputOffset()
+ token, err := decoder.RawToken()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+ _, text := token.(xml.CharData)
+ if !text || depth != 0 {
+ content.Write(inner[start:decoder.InputOffset()])
+ }
+ switch token.(type) {
+ case xml.StartElement:
+ depth++
+ case xml.EndElement:
+ depth--
+ }
+ }
+ }
+ patch := editorXMLContent(data, node, content.Bytes())
+ entry := append(bytes.Clone(data[node.start:patch.start]), patch.data...)
+ return append(entry, data[patch.end:node.end]...), nil
}
// editorXMLObjects 递归登记图层和页块内的对象位置,保留原容器层级
@@ -228,7 +275,7 @@ func editorXMLObjects(container *editorXML, nodes map[string]*editorXML) error {
// 入参: node 节点, allowed 允许的属性名称
// 返回: bool 是否支持
func editorXMLAttributes(node *editorXML, allowed string) bool {
- if node.name.Space != "" && node.name.Space != ofdNamespace && node.name.Space != "http://www.ofdspec.org" {
+ if classifyOFDNamespace(node.name.Space) == ofdXMLUnknown {
return false
}
for _, attr := range node.attrs {
@@ -326,7 +373,7 @@ func editorXMLContainersSupported(node *editorXML) bool {
// 入参: node 原始对象节点
// 返回: bool 是否支持保真复制
func editorXMLCopyable(node *editorXML) bool {
- if node.attr("ID") != "" && (editorResourceID(node.attr("ID")) == "" || node.name.Space != "" && node.name.Space != ofdNamespace && node.name.Space != "http://www.ofdspec.org") {
+ if node.attr("ID") != "" && (editorResourceID(node.attr("ID")) == "" || classifyOFDNamespace(node.name.Space) == ofdXMLUnknown) {
return false
}
for _, child := range node.children {
@@ -343,7 +390,11 @@ func editorXMLCopyable(node *editorXML) bool {
// 返回: []byte 对象XML, error 错误信息
func editorXMLObject(data []byte, node *editorXML, before, after GraphicObject) ([]byte, error) {
if before.Type != after.Type {
- return editorObjectXML(after)
+ encoded, err := editorObjectXML(after)
+ if err != nil {
+ return nil, err
+ }
+ return editorXMLGenerated(encoded, node.name.Space)
}
if node.attr("DrawParam") != "" && (before.Type == "TextObject" && before.TextObject.DrawParam == "" || before.Type == "PathObject" && before.PathObject.DrawParam == "") {
before = GraphicObject{Type: before.Type}
@@ -373,8 +424,16 @@ func editorXMLObject(data []byte, node *editorXML, before, after GraphicObject)
// 入参: data 原文, node 原节点, oldXML 修改前编码, newXML 修改后编码
// 返回: []byte 更新节点, error 错误信息
func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte, error) {
- legacyClip := node.name.Local == "Clips" && node.hasAttr("TransFlag")
+ return editorXMLMergeNamespace(data, node, oldXML, newXML, node.name.Space)
+}
+
+// editorXMLMergeNamespace 合并节点差异,并向已换算的裁剪子树传递目标命名空间
+// 入参: data 原文, node 原节点, oldXML 修改前编码, newXML 修改后编码, namespace 目标命名空间
+// 返回: []byte 更新节点, error 解析错误
+func editorXMLMergeNamespace(data []byte, node *editorXML, oldXML, newXML []byte, namespace string) ([]byte, error) {
+ legacyClip := node.name.Local == "Clips" && (node.hasAttr("TransFlag") || classifyOFDNamespace(node.name.Space) == ofdXMLLegacy)
if legacyClip {
+ namespace = ofdNamespace2016
var clips Clips
if err := xml.Unmarshal(data[node.start:node.end], &clips); err != nil {
return nil, err
@@ -404,7 +463,7 @@ func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte
}
}
contentChanged := !bytes.Equal(oldXML[oldNode.open:oldNode.close], newXML[newNode.open:newNode.close])
- if len(changes) == 0 && !contentChanged {
+ if len(changes) == 0 && !contentChanged && namespace == node.name.Space {
return bytes.Clone(data[node.start:node.end]), nil
}
d := xml.NewDecoder(bytes.NewReader(data[node.start:node.open]))
@@ -413,6 +472,10 @@ func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte
return nil, err
}
start := token.(xml.StartElement)
+ if legacyClip || namespace != node.name.Space {
+ start.Name.Space = editorXMLNamespacePrefix(node)
+ start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "xmlns", Local: start.Name.Space}, Value: namespace})
+ }
var result bytes.Buffer
name := start.Name.Local
if start.Name.Space != "" {
@@ -445,11 +508,11 @@ func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte
}
}
result.WriteByte('>')
- if contentChanged {
+ if contentChanged || namespace != node.name.Space {
matching := len(node.children) > 0 && len(node.children) == len(oldNode.children) && len(node.children) == len(newNode.children)
if matching {
for i, child := range node.children {
- if child.name.Space != "" && child.name.Space != ofdNamespace && child.name.Space != "http://www.ofdspec.org" || child.name.Local != oldNode.children[i].name.Local || child.name.Local != newNode.children[i].name.Local {
+ if !packageOFDNode(child, oldNode.children[i].name.Local) || child.name.Local != newNode.children[i].name.Local {
matching = false
break
}
@@ -459,7 +522,11 @@ func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte
position := node.open
for i, child := range node.children {
before, after := oldNode.children[i], newNode.children[i]
- updated, err := editorXMLMerge(data, child, oldXML[before.start:before.end], newXML[after.start:after.end])
+ childNamespace := child.name.Space
+ if childNamespace == node.name.Space {
+ childNamespace = namespace
+ }
+ updated, err := editorXMLMergeNamespace(data, child, oldXML[before.start:before.end], newXML[after.start:after.end], childNamespace)
if err != nil {
return nil, err
}
@@ -469,7 +536,7 @@ func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte
}
result.Write(data[position:node.close])
} else {
- content, err := editorXMLMergeContent(data, node, oldXML, oldNode, newXML, newNode)
+ content, err := editorXMLMergeContent(data, node, oldXML, oldNode, newXML, newNode, namespace)
if err != nil {
return nil, err
}
@@ -483,16 +550,24 @@ func editorXMLMerge(data []byte, node *editorXML, oldXML, newXML []byte) ([]byte
}
// editorXMLMergeContent 按字段合并子节点,保留未修改字段、扩展节点和注释
-// 入参: data 原文数据, node 原文节点, oldXML 修改前数据, oldNode 修改前节点, newXML 修改后数据, newNode 修改后节点
+// 入参: data 原文数据, node 原文节点, oldXML 修改前数据, oldNode 修改前节点, newXML 修改后数据, newNode 修改后节点, namespace 目标命名空间
// 返回: []byte 子节点内容, error 错误信息
-func editorXMLMergeContent(data []byte, node *editorXML, oldXML []byte, oldNode *editorXML, newXML []byte, newNode *editorXML) ([]byte, error) {
+func editorXMLMergeContent(data []byte, node *editorXML, oldXML []byte, oldNode *editorXML, newXML []byte, newNode *editorXML, namespace string) ([]byte, error) {
if len(oldNode.children) == 0 && len(newNode.children) == 0 {
- return newXML[newNode.open:newNode.close], nil
+ updated, err := editorXMLTextValue(data, node, editorImportText(oldXML, oldNode), editorImportText(newXML, newNode))
+ if err != nil {
+ return nil, err
+ }
+ root, err := parseEditorXML(updated)
+ if err != nil {
+ return nil, err
+ }
+ return updated[root.open:root.close], nil
}
groups := func(root *editorXML, encoded bool) map[string][]*editorXML {
result := make(map[string][]*editorXML)
for _, child := range root.children {
- if encoded || child.name.Space == "" || child.name.Space == ofdNamespace || child.name.Space == "http://www.ofdspec.org" {
+ if encoded || packageOFDNode(child, child.name.Local) {
result[child.name.Local] = append(result[child.name.Local], child)
}
}
@@ -515,7 +590,7 @@ func editorXMLMergeContent(data []byte, node *editorXML, oldXML []byte, oldNode
for _, child := range next {
newData = append(newData, newXML[child.start:child.end]...)
}
- if bytes.Equal(oldData, newData) {
+ if bytes.Equal(oldData, newData) && namespace == node.name.Space {
continue
}
if len(source) > 0 && len(source) == len(old) {
@@ -523,7 +598,11 @@ func editorXMLMergeContent(data []byte, node *editorXML, oldXML []byte, oldNode
var encoded []byte
if i < len(next) {
var err error
- encoded, err = editorXMLMerge(data, child, oldXML[old[i].start:old[i].end], newXML[next[i].start:next[i].end])
+ childNamespace := child.name.Space
+ if childNamespace == node.name.Space {
+ childNamespace = namespace
+ }
+ encoded, err = editorXMLMergeNamespace(data, child, oldXML[old[i].start:old[i].end], newXML[next[i].start:next[i].end], childNamespace)
if err != nil {
return nil, err
}
@@ -533,7 +612,11 @@ func editorXMLMergeContent(data []byte, node *editorXML, oldXML []byte, oldNode
if len(next) > len(source) {
var added []byte
for _, child := range next[len(source):] {
- added = append(added, editorXMLEncodedFragment(newXML, child)...)
+ fragment, err := editorXMLEncodedFragment(newXML, child, namespace)
+ if err != nil {
+ return nil, err
+ }
+ added = append(added, fragment...)
}
position := source[len(source)-1].end - node.open
patches = append(patches, editorXMLPatch{position, position, added})
@@ -542,7 +625,11 @@ func editorXMLMergeContent(data []byte, node *editorXML, oldXML []byte, oldNode
}
newData = nil
for _, child := range next {
- newData = append(newData, editorXMLEncodedFragment(newXML, child)...)
+ fragment, err := editorXMLEncodedFragment(newXML, child, namespace)
+ if err != nil {
+ return nil, err
+ }
+ newData = append(newData, fragment...)
}
if len(source) > 0 {
for i, child := range source {
@@ -566,13 +653,26 @@ func editorXMLMergeContent(data []byte, node *editorXML, oldXML []byte, oldNode
return editorPatchXML(data[node.open:node.close], patches), nil
}
-// editorXMLEncodedFragment 为新增的标准编码节点局部声明前缀,不覆盖原文命名空间
-// 入参: data 标准编码内容, node 待插入的子节点
-// 返回: []byte 独立片段
-func editorXMLEncodedFragment(data []byte, node *editorXML) []byte {
- position := node.open - node.start - 1
- fragment := data[node.start:node.end]
- return editorPatchXML(fragment, []editorXMLPatch{{position, position, []byte(" xmlns:ofd=\"" + ofdNamespace + "\"")}})
+// editorXMLEncodedFragment 补齐自产片段的继承声明,再按目标命名空间编码
+// 入参: data 标准编码内容, node 待插入节点, namespace 目标命名空间
+// 返回: []byte 独立片段, error 解析错误
+func editorXMLEncodedFragment(data []byte, node *editorXML, namespace string) ([]byte, error) {
+ fragment, err := editorXMLStandalone(data[node.start:node.end], node)
+ if err != nil {
+ return nil, err
+ }
+ if node.name.Space == "ofd" {
+ root, err := parseEditorXML(fragment)
+ if err != nil {
+ return nil, err
+ }
+ position := root.open - 1
+ if fragment[position-1] == '/' {
+ position--
+ }
+ fragment = editorPatchXML(fragment, []editorXMLPatch{{position, position, []byte(" xmlns:ofd=\"" + ofdNamespace2016 + "\"")}})
+ }
+ return editorXMLGenerated(fragment, namespace)
}
// editorXMLAttribute 修改单个标准属性,保留节点其余原文
@@ -594,13 +694,7 @@ func editorXMLAttribute(data []byte, node *editorXML, name, value string) ([]byt
// 入参: name 节点名称, attrs 属性, content 子节点XML
// 返回: []byte XML片段, error 错误信息
func editorXMLContainer(name string, attrs ofdAttrs, content []byte) ([]byte, error) {
- var output bytes.Buffer
- attrs.add("xmlns:ofd", ofdNamespace)
- value := struct {
- Content []byte `xml:",innerxml"`
- }{content}
- err := xml.NewEncoder(&output).EncodeElement(value, xml.StartElement{Name: xml.Name{Local: "ofd:" + name}, Attr: attrs})
- return output.Bytes(), err
+ return (&editorXML{name: xml.Name{Space: ofdNamespace2016}}).containerXML(name, attrs, content)
}
// editorXMLStandalone 补齐原对象继承的命名空间,使跨页复制不依赖原父节点
@@ -628,6 +722,9 @@ func editorXMLStandalone(data []byte, source *editorXML) ([]byte, error) {
}
}
}
+ if _, exists := namespaces["xmlns"]; !exists {
+ namespaces["xmlns"] = ""
+ }
for _, attr := range root.attrs {
if attr.Name.Space == "xmlns" {
delete(namespaces, "xmlns:"+attr.Name.Local)
diff --git a/ofdgo_object.go b/ofdgo_object.go
index 94c95fc..13e57cb 100644
--- a/ofdgo_object.go
+++ b/ofdgo_object.go
@@ -53,6 +53,23 @@ func (clips *Clips) usesObjectMatrix() bool {
return clips != nil && clips.TransFlag != nil && *clips.TransFlag
}
+// UnmarshalXML 兼容旧命名空间的对象变换裁剪,显式TransFlag优先
+// 入参: d XML解码器, start 起始节点
+// 返回: error 解码错误
+func (clips *Clips) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ type plain Clips
+ var value plain
+ if classifyOFDNamespace(start.Name.Space) == ofdXMLLegacy {
+ transform := true
+ value.TransFlag = &transform
+ }
+ if err := d.DecodeElement(&value, &start); err != nil {
+ return err
+ }
+ *clips = Clips(value)
+ return nil
+}
+
// UnmarshalXML 解析路径并区分省略样式、显式实线与零线宽
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
diff --git a/ofdgo_package.go b/ofdgo_package.go
index 829d644..0f6c0fd 100644
--- a/ofdgo_package.go
+++ b/ofdgo_package.go
@@ -38,11 +38,11 @@ func packagePagePath(directory, id string) string {
return path.Join(directory, "Pages", "Page_"+id, "Content.xml")
}
-// packageOFDNode 判断包索引中的标准节点,兼容旧版及省略命名空间的文档
+// packageOFDNode 识别包索引中的OFD节点,兼容与父节点同命名空间的已知字段
// 入参: node XML节点, name 节点名称
-// 返回: bool 是否为标准节点
+// 返回: bool 是否为OFD节点
func packageOFDNode(node *editorXML, name string) bool {
- return node.name.Local == name && (node.name.Space == "" || node.name.Space == ofdNamespace || node.name.Space == "http://www.ofdspec.org")
+ return node.name.Local == name && (classifyOFDNamespace(node.name.Space) != ofdXMLUnknown || node.parent != nil && node.name.Space == node.parent.name.Space)
}
// packageName 为新增条目分配文档内路径,不覆盖原文件或本次资源
@@ -118,6 +118,10 @@ func mergePackageResources(reader *Reader, parts map[string][]byte, doc *Documen
return "", false, err
}
target := root.child(group.name.Local)
+ context := root
+ if target != nil {
+ context = target
+ }
var content []byte
for _, node := range group.children {
if target != nil && slices.ContainsFunc(target.children, func(old *editorXML) bool {
@@ -129,6 +133,10 @@ func mergePackageResources(reader *Reader, parts map[string][]byte, doc *Documen
if err != nil {
return "", false, err
}
+ entry, err = editorXMLGenerated(entry, context.name.Space)
+ if err != nil {
+ return "", false, err
+ }
content = append(content, entry...)
}
if len(content) == 0 {
@@ -139,7 +147,7 @@ func mergePackageResources(reader *Reader, parts map[string][]byte, doc *Documen
data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, target, content)})
continue
}
- content, err = editorXMLContainer(group.name.Local, nil, content)
+ content, err = root.containerXML(group.name.Local, nil, content)
if err != nil {
return "", false, err
}
diff --git a/ofdgo_pdf.go b/ofdgo_pdf.go
index b8a76ea..dde31a2 100644
--- a/ofdgo_pdf.go
+++ b/ofdgo_pdf.go
@@ -257,7 +257,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
return fmt.Errorf("import PDF page %d: %w", index+1, err)
}
if len(importer.annotationData) != 0 {
- if err := editor.appendAnnotations(index, importer.annotationData); err != nil {
+ if err := editor.appendAnnotations(index, importer.annotationData, true); err != nil {
return fmt.Errorf("import PDF page %d: %w", index+1, err)
}
importer.annotationData = nil
diff --git a/ofdgo_reader_dependencies.go b/ofdgo_reader_dependencies.go
index b46607a..76ea1ab 100644
--- a/ofdgo_reader_dependencies.go
+++ b/ofdgo_reader_dependencies.go
@@ -109,11 +109,11 @@ func (r *Reader) documentFiles(ctx context.Context, index int) (map[string]bool,
}
switch node := token.(type) {
case xml.StartElement:
- if depth == 0 && node.Name.Space != "" && node.Name.Space != ofdNamespace && node.Name.Space != "http://www.ofdspec.org" {
+ if depth == 0 && classifyOFDNamespace(node.Name.Space) == ofdXMLUnknown {
input.Close()
goto nextFile
}
- if node.Name.Space != "" && node.Name.Space != ofdNamespace && node.Name.Space != "http://www.ofdspec.org" {
+ if classifyOFDNamespace(node.Name.Space) == ofdXMLUnknown {
input.Close()
return nil, fmt.Errorf("cannot determine document dependencies in %s", actual)
}
diff --git a/ofdgo_sign_write.go b/ofdgo_sign_write.go
index 1f2011d..c2c6265 100644
--- a/ofdgo_sign_write.go
+++ b/ofdgo_sign_write.go
@@ -312,6 +312,10 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
}
}
oldCount := len(list.List)
+ previousList, err := signatureWriteXML("Signatures", list)
+ if err != nil {
+ return nil, err
+ }
verifyOptions := append([]SignatureVerifyOption(nil), options.ExistingVerifyOptions...)
verifyOptions = append(verifyOptions, WithSignatureCert(identity.cert.Raw))
if options.Mode == SignatureAppend && oldCount > 0 {
@@ -342,7 +346,10 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
if body == nil {
return nil, fmt.Errorf("missing document body")
}
- parts["OFD.xml"] = editorXMLSetText(parts["OFD.xml"], body, [][2]string{{"Signatures", "/" + listPath}})
+ parts["OFD.xml"], err = editorXMLSetText(parts["OFD.xml"], body, [][2]string{{"Signatures", "/" + listPath}})
+ if err != nil {
+ return nil, err
+ }
}
if options.Mode == SignatureReplace {
list.List = nil
@@ -373,10 +380,22 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
parts[sealPath] = bytes.Clone(options.Seal)
}
list.List = append(list.List, Signature{ID: id, Type: kind, BaseLoc: "/" + sigPath})
- parts[listPath], err = signatureWriteXML("Signatures", list)
+ updatedList, err := signatureWriteXML("Signatures", list)
if err != nil {
return nil, err
}
+ if original := parts[listPath]; len(original) != 0 {
+ root, err := parseEditorXML(original)
+ if err != nil {
+ return nil, err
+ }
+ updated, err := editorXMLMerge(original, root, previousList, updatedList)
+ if err != nil {
+ return nil, err
+ }
+ updatedList = editorPatchXML(original, []editorXMLPatch{{root.start, root.end, updated}})
+ }
+ parts[listPath] = updatedList
refs := SignatureReferences{CheckMethod: identity.digest}
names := make([]string, 0, len(parts))
for name := range parts {
@@ -518,7 +537,7 @@ func signatureWriteIDs(list *Signatures, listPath string, parts map[string][]byt
func signatureWriteXML(name string, value any) ([]byte, error) {
var out bytes.Buffer
out.WriteString(xml.Header)
- err := xml.NewEncoder(&out).EncodeElement(value, xml.StartElement{Name: xml.Name{Space: ofdNamespace, Local: name}})
+ err := xml.NewEncoder(&out).EncodeElement(value, xml.StartElement{Name: xml.Name{Space: ofdNamespace2016, Local: name}})
return out.Bytes(), err
}
diff --git a/ofdgo_text_xml.go b/ofdgo_text_xml.go
index 2060b37..5efbdde 100644
--- a/ofdgo_text_xml.go
+++ b/ofdgo_text_xml.go
@@ -24,6 +24,19 @@ type textObjectTokens struct {
transforms []int
}
+// UnmarshalXML 解析字形变换,省略的字符数和字形数均取1
+// 入参: decoder XML解码器, start 变换起始节点
+// 返回: error 解码错误
+func (transform *CGTransform) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
+ type plain CGTransform
+ value := plain{CodeCount: 1, GlyphCount: 1}
+ if err := decoder.DecodeElement(&value, &start); err != nil {
+ return err
+ }
+ *transform = CGTransform(value)
+ return nil
+}
+
// Token 转发XML令牌并记录直接子节点顺序
// 返回: xml.Token XML令牌, error 解码错误
func (s *textObjectTokens) Token() (xml.Token, error) {
diff --git a/ofdgo_writer.go b/ofdgo_writer.go
index 4234d58..3a014bb 100644
--- a/ofdgo_writer.go
+++ b/ofdgo_writer.go
@@ -31,9 +31,6 @@ import (
"time"
)
-// ofdNamespace 为OFD文档的XML命名空间
-const ofdNamespace = "http://www.ofdspec.org/2016"
-
// editorProgress 保存准备阶段的进度检查点,内存预览使用nil
type editorProgress func(stage string, completed, total int) error
@@ -675,7 +672,7 @@ func (x *ofdXML) checkContext() bool {
// root 写入声明命名空间的根节点
// 入参: name 节点名, attrs 属性
func (x *ofdXML) root(name string, attrs ofdAttrs) {
- attrs.add("xmlns:ofd", ofdNamespace)
+ attrs.add("xmlns:ofd", ofdNamespace2016)
x.start(name, attrs)
}
@@ -785,7 +782,7 @@ func (x *ofdXML) object(object GraphicObject, root bool) {
var fill, stroke *FillColor
var actions []Action
if root {
- attrs.add("xmlns:ofd", ofdNamespace)
+ attrs.add("xmlns:ofd", ofdNamespace2016)
}
switch object.Type {
case "TextObject", "Text":
@@ -1112,7 +1109,7 @@ func (x *ofdXML) shading(name string, attrs ofdAttrs, segments []ShdSegment) {
// 入参: name 元素名称, value 元素内容
func (x *ofdXML) element(name string, value any) {
if x.checkContext() {
- x.err = x.encoder.EncodeElement(value, xml.StartElement{Name: xml.Name{Space: ofdNamespace, Local: name}})
+ x.err = x.encoder.EncodeElement(value, xml.StartElement{Name: xml.Name{Space: ofdNamespace2016, Local: name}})
x.referenceElement(name, value)
}
}
diff --git a/ofdgo_xml.go b/ofdgo_xml.go
new file mode 100644
index 0000000..ba48613
--- /dev/null
+++ b/ofdgo_xml.go
@@ -0,0 +1,48 @@
+// 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
+
+// 旧版与2016版OFD命名空间
+const (
+ ofdNamespaceLegacy = "http://www.ofdspec.org"
+ ofdNamespace2016 = "http://www.ofdspec.org/2016"
+)
+
+// 未声明、未知、旧版及2016版OFD命名空间,不限制已知内容的读取
+const (
+ ofdXMLUnqualified ofdXMLNamespace = iota
+ ofdXMLUnknown
+ ofdXMLLegacy
+ ofdXML2016
+)
+
+// ofdXMLNamespace 标识XML节点采用的OFD命名空间
+type ofdXMLNamespace uint8
+
+// classifyOFDNamespace 识别OFD命名空间,未声明与未知版本分别处理
+// 入参: namespace XML命名空间
+// 返回: ofdXMLNamespace 命名空间类别
+func classifyOFDNamespace(namespace string) ofdXMLNamespace {
+ switch namespace {
+ case "":
+ return ofdXMLUnqualified
+ case ofdNamespaceLegacy:
+ return ofdXMLLegacy
+ case ofdNamespace2016:
+ return ofdXML2016
+ default:
+ return ofdXMLUnknown
+ }
+}