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("")}) + } + 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 + } +}