feat(归并文件): 归并标引与栅格文件
build.yaml / build (push) Successful in 44s

This commit is contained in:
xiaoqidun committed 2026-10-19 10:45:46 +08:00
1 parent bfecdeb0d6
commit f3cc35e78c
4 files changed
+329 -372

No files matched your search

+123
View File
@@ -22,6 +22,7 @@ import (
"image" "image"
"image/color" "image/color"
"math" "math"
"slices"
basegeom "github.com/xiaoqidun/ofdgo/internal/base/geom" basegeom "github.com/xiaoqidun/ofdgo/internal/base/geom"
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image" baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
@@ -113,6 +114,48 @@ type RasterSpread struct {
Period float64 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 检查页面尺寸并计算目标像素大小,合法正尺寸至少保留一个像素 // PixelSize 检查页面尺寸并计算目标像素大小,合法正尺寸至少保留一个像素
// 返回: int 宽度, int 高度, error 无效尺寸 // 返回: int 宽度, int 高度, error 无效尺寸
func (p *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) draw.DrawMask(dst, bounds, layer, bounds.Min, mask, bounds.Min, draw.Over)
return nil 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
}
-143
View File
@@ -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
}
+206
View File
@@ -20,7 +20,10 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
"slices"
"strconv"
"strings" "strings"
"unicode/utf8"
) )
// 公文标引类型标识 // 公文标引类型标识
@@ -447,3 +450,206 @@ func (r *Renderer) StructureTargetsContext(ctx context.Context, index int, path
} }
return result, nil 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
}
-229
View File
@@ -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
}