From f3cc35e78c8b64684bd387f9c2bdba903ac67a90 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 19 Oct 2026 10:45:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=BD=92=E5=B9=B6=E6=96=87=E4=BB=B6):=20?= =?UTF-8?q?=E5=BD=92=E5=B9=B6=E6=A0=87=E5=BC=95=E4=B8=8E=E6=A0=85=E6=A0=BC?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_raster.go | 123 ++++++++++++++++++++++ ofdgo_raster_scene.go | 143 ------------------------- ofdgo_structure.go | 206 ++++++++++++++++++++++++++++++++++++ ofdgo_structure_xml.go | 229 ----------------------------------------- 4 files changed, 329 insertions(+), 372 deletions(-) delete mode 100644 ofdgo_raster_scene.go delete mode 100644 ofdgo_structure_xml.go diff --git a/ofdgo_raster.go b/ofdgo_raster.go index a3b5186..0c32057 100644 --- a/ofdgo_raster.go +++ b/ofdgo_raster.go @@ -22,6 +22,7 @@ import ( "image" "image/color" "math" + "slices" basegeom "github.com/xiaoqidun/ofdgo/internal/base/geom" baseimage "github.com/xiaoqidun/ofdgo/internal/base/image" @@ -113,6 +114,48 @@ type RasterSpread struct { Period float64 } +// RasterScene 固定尺寸、DPI、字体和资源的可重复绘制场景 +// 每次Render返回独立图像;源图像只读借用,修改页面或配置后应重新准备 +type RasterScene interface { + Render() (image.Image, error) +} + +// RasterSceneBackend 可选的光栅预编译能力,不改变后端选择 +type RasterSceneBackend interface { + RasterBackend + Prepare(page *RasterPage) (RasterScene, error) +} + +// RasterSceneUpdater 可选的增量场景能力,保持原后端与显式几何配置 +// Update按新页面的完整内容及DPI使派生数据失效,失败时原场景仍可绘制 +// 图片仍为只读借用,编辑图片应提供新资源;修改后端配置应重新Prepare +type RasterSceneUpdater interface { + RasterScene + Update(page *RasterPage) error +} + +// RasterSceneReuseBackend 可选的跨快照复用能力,不改变显式后端选择 +// 实现必须验证前次场景及配置,返回独立快照,不能修改仍可使用的旧场景 +type RasterSceneReuseBackend interface { + RasterSceneBackend + PrepareReuse(page *RasterPage, previous RasterScene) (RasterScene, error) +} + +// rasterPreparationKey 隔离渲染器私有的前次场景 +type rasterPreparationKey struct{} + +// rasterPreparationState 记录前次场景及实际后端配置 +type rasterPreparationState struct { + backend RasterBackend + scene RasterScene +} + +// rasterScene 复用后端无关指令,图片保持只读引用 +type rasterScene struct { + backend RasterBackend + page *RasterPage +} + // PixelSize 检查页面尺寸并计算目标像素大小,合法正尺寸至少保留一个像素 // 返回: int 宽度, int 高度, error 无效尺寸 func (p *RasterPage) PixelSize() (int, int, error) { @@ -514,3 +557,83 @@ func drawRasterImageCommand(backend RasterBackend, page *RasterPage, dst draw.Im draw.DrawMask(dst, bounds, layer, bounds.Min, mask, bounds.Min, draw.Over) return nil } + +// PreparePage 编译并准备当前配置的独立页面快照,按后端能力复用未修改指令 +// 每次重新解释源页面,按内容使模板、签章、字体及样式派生数据失效 +// 入参: page 源页面 +// 返回: RasterScene 可重复绘制场景, error 编译或准备错误 +func (r *Renderer) PreparePage(page *PageContent) (RasterScene, error) { + if r.backends.Raster == nil { + return nil, fmt.Errorf("raster scene: %w", ErrBackendUnavailable) + } + compiled, err := r.CompilePage(page) + if err != nil { + return nil, err + } + if backend, ok := r.backends.Raster.(RasterSceneReuseBackend); ok { + state, _ := r.backendStates[rasterPreparationKey{}].(rasterPreparationState) + var previous RasterScene + if sameBackend(state.backend, r.backends.Raster) { + previous = state.scene + } + scene, err := backend.PrepareReuse(compiled, previous) + if err != nil { + return nil, err + } + r.backendStates[rasterPreparationKey{}] = rasterPreparationState{backend: r.backends.Raster, scene: scene} + return scene, nil + } + return PrepareRasterScene(r.backends.Raster, compiled) +} + +// PrepareRasterScene 准备绘制快照,未实现预编译的后端复用公共指令 +// 入参: backend 光栅后端, page 绘制页面 +// 返回: RasterScene 可重复绘制场景, error 准备错误 +func PrepareRasterScene(backend RasterBackend, page *RasterPage) (RasterScene, error) { + if backend == nil { + return nil, fmt.Errorf("raster scene: %w", ErrBackendUnavailable) + } + if _, _, err := page.PixelSize(); err != nil { + return nil, err + } + if provider, ok := backend.(RasterSceneBackend); ok { + return provider.Prepare(page) + } + return &rasterScene{backend: backend, page: cloneRasterPage(page)}, nil +} + +// Render 绘制固定快照 +// 返回: image.Image 独立图像, error 绘制错误 +func (s *rasterScene) Render() (image.Image, error) { return s.backend.Render(s.page) } + +// cloneRasterPage 隔离可变指令,源图片按接口约定只读借用 +// 入参: page 绘制页面 +// 返回: *RasterPage 指令快照 +func cloneRasterPage(page *RasterPage) *RasterPage { + result := *page + result.Commands = slices.Clone(page.Commands) + for i := range result.Commands { + cmd := &result.Commands[i] + cmd.Path, cmd.Clip = slices.Clone(cmd.Path), slices.Clone(cmd.Clip) + if cmd.Stroke != nil { + stroke := *cmd.Stroke + stroke.Dashes = slices.Clone(stroke.Dashes) + cmd.Stroke = &stroke + } + if cmd.Paint.Gradient != nil { + gradient := *cmd.Paint.Gradient + gradient.Stops = slices.Clone(gradient.Stops) + if gradient.Mesh != nil { + mesh := *gradient.Mesh + mesh.Triangles = slices.Clone(mesh.Triangles) + gradient.Mesh = &mesh + } + if gradient.Spread != nil { + spread := *gradient.Spread + gradient.Spread = &spread + } + cmd.Paint.Gradient = &gradient + } + } + return &result +} diff --git a/ofdgo_raster_scene.go b/ofdgo_raster_scene.go deleted file mode 100644 index 9dc4d17..0000000 --- a/ofdgo_raster_scene.go +++ /dev/null @@ -1,143 +0,0 @@ -// 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 ( - "fmt" - "image" - "slices" -) - -// RasterScene 固定尺寸、DPI、字体和资源的可重复绘制场景 -// 每次Render返回独立图像;源图像只读借用,修改页面或配置后应重新准备 -type RasterScene interface { - Render() (image.Image, error) -} - -// RasterSceneBackend 可选的光栅预编译能力,不改变后端选择 -type RasterSceneBackend interface { - RasterBackend - Prepare(page *RasterPage) (RasterScene, error) -} - -// RasterSceneUpdater 可选的增量场景能力,保持原后端与显式几何配置 -// Update按新页面的完整内容及DPI使派生数据失效,失败时原场景仍可绘制 -// 图片仍为只读借用,编辑图片应提供新资源;修改后端配置应重新Prepare -type RasterSceneUpdater interface { - RasterScene - Update(page *RasterPage) error -} - -// RasterSceneReuseBackend 可选的跨快照复用能力,不改变显式后端选择 -// 实现必须验证前次场景及配置,返回独立快照,不能修改仍可使用的旧场景 -type RasterSceneReuseBackend interface { - RasterSceneBackend - PrepareReuse(page *RasterPage, previous RasterScene) (RasterScene, error) -} - -// rasterPreparationKey 隔离渲染器私有的前次场景 -type rasterPreparationKey struct{} - -// rasterPreparationState 记录前次场景及实际后端配置 -type rasterPreparationState struct { - backend RasterBackend - scene RasterScene -} - -// rasterScene 复用后端无关指令,图片保持只读引用 -type rasterScene struct { - backend RasterBackend - page *RasterPage -} - -// PreparePage 编译并准备当前配置的独立页面快照,按后端能力复用未修改指令 -// 每次重新解释源页面,按内容使模板、签章、字体及样式派生数据失效 -// 入参: page 源页面 -// 返回: RasterScene 可重复绘制场景, error 编译或准备错误 -func (r *Renderer) PreparePage(page *PageContent) (RasterScene, error) { - if r.backends.Raster == nil { - return nil, fmt.Errorf("raster scene: %w", ErrBackendUnavailable) - } - compiled, err := r.CompilePage(page) - if err != nil { - return nil, err - } - if backend, ok := r.backends.Raster.(RasterSceneReuseBackend); ok { - state, _ := r.backendStates[rasterPreparationKey{}].(rasterPreparationState) - var previous RasterScene - if sameBackend(state.backend, r.backends.Raster) { - previous = state.scene - } - scene, err := backend.PrepareReuse(compiled, previous) - if err != nil { - return nil, err - } - r.backendStates[rasterPreparationKey{}] = rasterPreparationState{backend: r.backends.Raster, scene: scene} - return scene, nil - } - return PrepareRasterScene(r.backends.Raster, compiled) -} - -// PrepareRasterScene 准备绘制快照,未实现预编译的后端复用公共指令 -// 入参: backend 光栅后端, page 绘制页面 -// 返回: RasterScene 可重复绘制场景, error 准备错误 -func PrepareRasterScene(backend RasterBackend, page *RasterPage) (RasterScene, error) { - if backend == nil { - return nil, fmt.Errorf("raster scene: %w", ErrBackendUnavailable) - } - if _, _, err := page.PixelSize(); err != nil { - return nil, err - } - if provider, ok := backend.(RasterSceneBackend); ok { - return provider.Prepare(page) - } - return &rasterScene{backend: backend, page: cloneRasterPage(page)}, nil -} - -// Render 绘制固定快照 -// 返回: image.Image 独立图像, error 绘制错误 -func (s *rasterScene) Render() (image.Image, error) { return s.backend.Render(s.page) } - -// cloneRasterPage 隔离可变指令,源图片按接口约定只读借用 -// 入参: page 绘制页面 -// 返回: *RasterPage 指令快照 -func cloneRasterPage(page *RasterPage) *RasterPage { - result := *page - result.Commands = slices.Clone(page.Commands) - for i := range result.Commands { - cmd := &result.Commands[i] - cmd.Path, cmd.Clip = slices.Clone(cmd.Path), slices.Clone(cmd.Clip) - if cmd.Stroke != nil { - stroke := *cmd.Stroke - stroke.Dashes = slices.Clone(stroke.Dashes) - cmd.Stroke = &stroke - } - if cmd.Paint.Gradient != nil { - gradient := *cmd.Paint.Gradient - gradient.Stops = slices.Clone(gradient.Stops) - if gradient.Mesh != nil { - mesh := *gradient.Mesh - mesh.Triangles = slices.Clone(mesh.Triangles) - gradient.Mesh = &mesh - } - if gradient.Spread != nil { - spread := *gradient.Spread - gradient.Spread = &spread - } - cmd.Paint.Gradient = &gradient - } - } - return &result -} diff --git a/ofdgo_structure.go b/ofdgo_structure.go index 56bead7..b7462e7 100644 --- a/ofdgo_structure.go +++ b/ofdgo_structure.go @@ -20,7 +20,10 @@ import ( "encoding/xml" "fmt" "io" + "slices" + "strconv" "strings" + "unicode/utf8" ) // 公文标引类型标识 @@ -447,3 +450,206 @@ func (r *Renderer) StructureTargetsContext(ctx context.Context, index int, path } return result, nil } + +// encodeStructureNode 编码有界标引子树,引用由ObjectRef节点表达,不重复编码派生References字段 +// 入参: node 根节点 +// 返回: []byte XML片段, error 名称、属性或编码错误 +func encodeStructureNode(node StructureNode) ([]byte, error) { + return encodeStructureNodeContext(context.Background(), node) +} + +// encodeStructureNodeContext 编码标引子树并检查取消状态 +// 入参: ctx 取消上下文, node 根节点 +// 返回: []byte XML片段, error 名称、深度、编码或取消错误 +func encodeStructureNodeContext(ctx context.Context, node StructureNode) ([]byte, error) { + var output bytes.Buffer + encoder := xml.NewEncoder(&output) + var write func(StructureNode, int) error + write = func(node StructureNode, depth int) error { + if err := ctx.Err(); err != nil { + return err + } + if depth > 128 { + return fmt.Errorf("custom tag structure limit exceeded") + } + if err := validateStructureNode(node); err != nil { + return err + } + start := xml.StartElement{Name: xml.Name{Space: node.Namespace, Local: node.Name}} + switch node.Namespace { + case "": + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: ""}) + case XMLNamespace: + start.Name = xml.Name{Local: "xml:" + node.Name} + } + for _, attr := range node.Attributes { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: attr.Namespace, Local: attr.Name}, Value: attr.Value}) + } + if err := encoder.EncodeToken(start); err != nil { + return err + } + if err := encoder.EncodeToken(xml.CharData(node.Text)); err != nil { + return err + } + for _, child := range node.Children { + if err := write(child, depth+1); err != nil { + return err + } + } + return encoder.EncodeToken(start.End()) + } + if err := write(node, 1); err != nil { + return nil, err + } + if err := encoder.Close(); err != nil { + return nil, err + } + return output.Bytes(), nil +} + +// validateStructureNode 校验标引节点名称、属性和文本 +// 入参: node 节点字段 +// 返回: error 字段校验错误 +func validateStructureNode(node StructureNode) error { + if !ofdXMLIDValid(node.Name) || node.Name == "xmlns" { + return fmt.Errorf("invalid custom tag node name: %s", node.Name) + } + if !structureXMLText(node.Text) || !structureXMLText(node.Namespace) || node.Namespace == xmlDeclarationNamespace { + return fmt.Errorf("invalid custom tag text or namespace") + } + seen := make(map[xml.Name]bool) + for _, attr := range node.Attributes { + name := xml.Name{Space: attr.Namespace, Local: attr.Name} + if !ofdXMLIDValid(attr.Name) || attr.Name == "xmlns" || attr.Namespace == xmlDeclarationNamespace || !structureXMLText(attr.Namespace) || !structureXMLText(attr.Value) || seen[name] { + return fmt.Errorf("invalid or duplicate custom tag attribute: %s", attr.Name) + } + seen[name] = true + } + return nil +} + +// structureXMLText 检查XML 1.0允许的字符,不以替换字符掩盖无效输入 +// 入参: value 文本 +// 返回: bool 是否可无损编码 +func structureXMLText(value string) bool { + if !utf8.ValidString(value) { + return false + } + for _, r := range value { + if r != '\t' && r != '\n' && r != '\r' && (r < 0x20 || r == 0xfffe || r == 0xffff) { + return false + } + } + return true +} + +// updateStructureNodeXML 替换节点字段,保留子树继承的命名空间和非文本XML内容 +// 入参: data 原XML, source 原节点, node 新字段 +// 返回: []byte 独立节点片段, error 校验或编码错误 +func updateStructureNodeXML(data []byte, source *editorXML, node StructureNode) ([]byte, error) { + if err := validateStructureNode(node); err != nil { + return nil, err + } + fragment, err := editorXMLStandalone(data[source.start:source.end], source) + if err != nil { + return nil, err + } + before, err := parseStructure(context.Background(), fragment, CustomTag{}) + if err != nil { + return nil, err + } + if node.Name == before.Name && node.Namespace == before.Namespace && node.Text == before.Text && slices.Equal(node.Attributes, before.Attributes) { + return data[source.start:source.end], nil + } + root, err := parseEditorXML(fragment) + if err != nil { + return nil, err + } + if before.Text != node.Text { + var text bytes.Buffer + if err := xml.EscapeText(&text, []byte(node.Text)); err != nil { + return nil, err + } + fragment, err = editorXMLDirectText(fragment, root, text.Bytes()) + if err != nil { + return nil, err + } + } + root, err = parseEditorXML(fragment) + if err != nil { + return nil, err + } + var attrs []xml.Attr + used := make(map[string]bool) + for _, attr := range root.attrs { + if attr.Name.Space == "xmlns" { + attrs = append(attrs, xml.Attr{Name: xml.Name{Local: "xmlns:" + attr.Name.Local}, Value: attr.Value}) + used[attr.Name.Local] = true + } else if attr.Name.Space == "" && attr.Name.Local == "xmlns" { + attrs = append(attrs, attr) + } + } + qualify := func(name, namespace string) string { + if namespace == "" { + return name + } + if namespace == XMLNamespace { + return "xml:" + name + } + for _, attr := range attrs { + if strings.HasPrefix(attr.Name.Local, "xmlns:") && attr.Value == namespace { + return strings.TrimPrefix(attr.Name.Local, "xmlns:") + ":" + name + } + } + prefix := "tag" + for i := 1; used[prefix]; i++ { + prefix = "tag" + strconv.Itoa(i) + } + used[prefix] = true + attrs = append(attrs, xml.Attr{Name: xml.Name{Local: "xmlns:" + prefix}, Value: namespace}) + return prefix + ":" + name + } + name := qualify(node.Name, node.Namespace) + if node.Namespace == "" { + for i := range attrs { + if attrs[i].Name.Local == "xmlns" { + attrs[i].Value = "" + } + } + } + for _, attr := range node.Attributes { + key := qualify(attr.Name, attr.Namespace) + attrs = append(attrs, xml.Attr{Name: xml.Name{Local: key}, Value: attr.Value}) + } + var output bytes.Buffer + encoder := xml.NewEncoder(&output) + start := xml.StartElement{Name: xml.Name{Local: name}, Attr: attrs} + if err := encoder.EncodeToken(start); err != nil { + return nil, err + } + if err := encoder.Flush(); err != nil { + return nil, err + } + if root.open != root.end { + if node.Namespace == "" && before.Namespace != "" { + var patches []editorXMLPatch + for _, child := range root.children { + standalone, err := editorXMLStandalone(fragment[child.start:child.end], child) + if err != nil { + return nil, err + } + patches = append(patches, editorXMLPatch{child.start - root.open, child.end - root.open, standalone}) + } + output.Write(editorPatchXML(fragment[root.open:root.close], patches)) + } else { + output.Write(fragment[root.open:root.close]) + } + } + if err := encoder.EncodeToken(start.End()); err != nil { + return nil, err + } + if err := encoder.Close(); err != nil { + return nil, err + } + return output.Bytes(), nil +} diff --git a/ofdgo_structure_xml.go b/ofdgo_structure_xml.go deleted file mode 100644 index 73cc074..0000000 --- a/ofdgo_structure_xml.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2025-2026 肖其顿 (XIAO QI DUN) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ofdgo - -import ( - "bytes" - "context" - "encoding/xml" - "fmt" - "slices" - "strconv" - "strings" - "unicode/utf8" -) - -// encodeStructureNode 编码有界标引子树,引用由ObjectRef节点表达,不重复编码派生References字段 -// 入参: node 根节点 -// 返回: []byte XML片段, error 名称、属性或编码错误 -func encodeStructureNode(node StructureNode) ([]byte, error) { - return encodeStructureNodeContext(context.Background(), node) -} - -// encodeStructureNodeContext 编码标引子树并检查取消状态 -// 入参: ctx 取消上下文, node 根节点 -// 返回: []byte XML片段, error 名称、深度、编码或取消错误 -func encodeStructureNodeContext(ctx context.Context, node StructureNode) ([]byte, error) { - var output bytes.Buffer - encoder := xml.NewEncoder(&output) - var write func(StructureNode, int) error - write = func(node StructureNode, depth int) error { - if err := ctx.Err(); err != nil { - return err - } - if depth > 128 { - return fmt.Errorf("custom tag structure limit exceeded") - } - if err := validateStructureNode(node); err != nil { - return err - } - start := xml.StartElement{Name: xml.Name{Space: node.Namespace, Local: node.Name}} - switch node.Namespace { - case "": - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: ""}) - case XMLNamespace: - start.Name = xml.Name{Local: "xml:" + node.Name} - } - for _, attr := range node.Attributes { - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: attr.Namespace, Local: attr.Name}, Value: attr.Value}) - } - if err := encoder.EncodeToken(start); err != nil { - return err - } - if err := encoder.EncodeToken(xml.CharData(node.Text)); err != nil { - return err - } - for _, child := range node.Children { - if err := write(child, depth+1); err != nil { - return err - } - } - return encoder.EncodeToken(start.End()) - } - if err := write(node, 1); err != nil { - return nil, err - } - if err := encoder.Close(); err != nil { - return nil, err - } - return output.Bytes(), nil -} - -// validateStructureNode 校验标引节点名称、属性和文本 -// 入参: node 节点字段 -// 返回: error 字段校验错误 -func validateStructureNode(node StructureNode) error { - if !ofdXMLIDValid(node.Name) || node.Name == "xmlns" { - return fmt.Errorf("invalid custom tag node name: %s", node.Name) - } - if !structureXMLText(node.Text) || !structureXMLText(node.Namespace) || node.Namespace == xmlDeclarationNamespace { - return fmt.Errorf("invalid custom tag text or namespace") - } - seen := make(map[xml.Name]bool) - for _, attr := range node.Attributes { - name := xml.Name{Space: attr.Namespace, Local: attr.Name} - if !ofdXMLIDValid(attr.Name) || attr.Name == "xmlns" || attr.Namespace == xmlDeclarationNamespace || !structureXMLText(attr.Namespace) || !structureXMLText(attr.Value) || seen[name] { - return fmt.Errorf("invalid or duplicate custom tag attribute: %s", attr.Name) - } - seen[name] = true - } - return nil -} - -// structureXMLText 检查XML 1.0允许的字符,不以替换字符掩盖无效输入 -// 入参: value 文本 -// 返回: bool 是否可无损编码 -func structureXMLText(value string) bool { - if !utf8.ValidString(value) { - return false - } - for _, r := range value { - if r != '\t' && r != '\n' && r != '\r' && (r < 0x20 || r == 0xfffe || r == 0xffff) { - return false - } - } - return true -} - -// updateStructureNodeXML 替换节点字段,保留子树继承的命名空间和非文本XML内容 -// 入参: data 原XML, source 原节点, node 新字段 -// 返回: []byte 独立节点片段, error 校验或编码错误 -func updateStructureNodeXML(data []byte, source *editorXML, node StructureNode) ([]byte, error) { - if err := validateStructureNode(node); err != nil { - return nil, err - } - fragment, err := editorXMLStandalone(data[source.start:source.end], source) - if err != nil { - return nil, err - } - before, err := parseStructure(context.Background(), fragment, CustomTag{}) - if err != nil { - return nil, err - } - if node.Name == before.Name && node.Namespace == before.Namespace && node.Text == before.Text && slices.Equal(node.Attributes, before.Attributes) { - return data[source.start:source.end], nil - } - root, err := parseEditorXML(fragment) - if err != nil { - return nil, err - } - if before.Text != node.Text { - var text bytes.Buffer - if err := xml.EscapeText(&text, []byte(node.Text)); err != nil { - return nil, err - } - fragment, err = editorXMLDirectText(fragment, root, text.Bytes()) - if err != nil { - return nil, err - } - } - root, err = parseEditorXML(fragment) - if err != nil { - return nil, err - } - var attrs []xml.Attr - used := make(map[string]bool) - for _, attr := range root.attrs { - if attr.Name.Space == "xmlns" { - attrs = append(attrs, xml.Attr{Name: xml.Name{Local: "xmlns:" + attr.Name.Local}, Value: attr.Value}) - used[attr.Name.Local] = true - } else if attr.Name.Space == "" && attr.Name.Local == "xmlns" { - attrs = append(attrs, attr) - } - } - qualify := func(name, namespace string) string { - if namespace == "" { - return name - } - if namespace == XMLNamespace { - return "xml:" + name - } - for _, attr := range attrs { - if strings.HasPrefix(attr.Name.Local, "xmlns:") && attr.Value == namespace { - return strings.TrimPrefix(attr.Name.Local, "xmlns:") + ":" + name - } - } - prefix := "tag" - for i := 1; used[prefix]; i++ { - prefix = "tag" + strconv.Itoa(i) - } - used[prefix] = true - attrs = append(attrs, xml.Attr{Name: xml.Name{Local: "xmlns:" + prefix}, Value: namespace}) - return prefix + ":" + name - } - name := qualify(node.Name, node.Namespace) - if node.Namespace == "" { - for i := range attrs { - if attrs[i].Name.Local == "xmlns" { - attrs[i].Value = "" - } - } - } - for _, attr := range node.Attributes { - key := qualify(attr.Name, attr.Namespace) - attrs = append(attrs, xml.Attr{Name: xml.Name{Local: key}, Value: attr.Value}) - } - var output bytes.Buffer - encoder := xml.NewEncoder(&output) - start := xml.StartElement{Name: xml.Name{Local: name}, Attr: attrs} - if err := encoder.EncodeToken(start); err != nil { - return nil, err - } - if err := encoder.Flush(); err != nil { - return nil, err - } - if root.open != root.end { - if node.Namespace == "" && before.Namespace != "" { - var patches []editorXMLPatch - for _, child := range root.children { - standalone, err := editorXMLStandalone(fragment[child.start:child.end], child) - if err != nil { - return nil, err - } - patches = append(patches, editorXMLPatch{child.start - root.open, child.end - root.open, standalone}) - } - output.Write(editorPatchXML(fragment[root.open:root.close], patches)) - } else { - output.Write(fragment[root.open:root.close]) - } - } - if err := encoder.EncodeToken(start.End()); err != nil { - return nil, err - } - if err := encoder.Close(); err != nil { - return nil, err - } - return output.Bytes(), nil -}