From 05579265b80b4fabe2952e7bac0d6484a43ecf01 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 12 Oct 2026 14:56:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B5=84=E6=BA=90=E8=A7=A3=E6=9E=90=E4=B8=8E?= =?UTF-8?q?=E5=AD=97=E4=BD=93=E5=8E=8B=E7=BC=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_compression_fonts.go | 9 +-- ofdgo_editor_font_binding.go | 8 +- ofdgo_editor_resources.go | 27 +++++-- ofdgo_res.go | 139 ++++++++++++++++++++++++++++++++++- ofdgo_xml.go | 12 +++ 5 files changed, 178 insertions(+), 17 deletions(-) diff --git a/ofdgo_compression_fonts.go b/ofdgo_compression_fonts.go index b6037d2..963bc5d 100644 --- a/ofdgo_compression_fonts.go +++ b/ofdgo_compression_fonts.go @@ -53,11 +53,10 @@ func (r *Reader) compressionFontUsage(ctx context.Context, documents map[string] if err != nil { return nil, err } - fonts := root.child("Fonts") - if fonts == nil { - continue - } - for _, font := range fonts.children { + for font := range editorResourceNodes(root, "Fonts", "Font") { + if err := ctx.Err(); err != nil { + return nil, err + } file := font.child("FontFile") if file == nil { continue diff --git a/ofdgo_editor_font_binding.go b/ofdgo_editor_font_binding.go index 033c536..24554d5 100644 --- a/ofdgo_editor_font_binding.go +++ b/ofdgo_editor_font_binding.go @@ -360,11 +360,9 @@ func fontBindingNode(data []byte, id string) (*editorXML, error) { if err != nil { return nil, err } - if fonts := root.child("Fonts"); fonts != nil { - for _, node := range fonts.children { - if node.matchesOFD("Font") && editorResourceID(node.attr("ID")) == editorResourceID(id) { - return node, nil - } + for node := range editorResourceNodes(root, "Fonts", "Font") { + if editorResourceID(node.attr("ID")) == editorResourceID(id) { + return node, nil } } return nil, fmt.Errorf("font declaration is missing") diff --git a/ofdgo_editor_resources.go b/ofdgo_editor_resources.go index 9f42fe0..e1d4f48 100644 --- a/ofdgo_editor_resources.go +++ b/ofdgo_editor_resources.go @@ -21,6 +21,7 @@ import ( "encoding/xml" "fmt" "io" + "iter" "maps" "path" "slices" @@ -411,17 +412,31 @@ func normalizeResourceReferences(references map[string]bool) { // 返回: *editorXML 资源节点,未找到时为nil func editorResourceNode(root *editorXML, group, kind, id string) *editorXML { nodes := make(map[string]*editorXML) - if collection := root.child(group); collection != nil { - for _, node := range collection.children { - if node.matchesOFD(kind) { - nodes[node.attr("ID")] = node - } - } + for node := range editorResourceNodes(root, group, kind) { + nodes[node.attr("ID")] = node } node, _ := resourceValue(nodes, id) return node } +// editorResourceNodes 按原顺序遍历同类资源,包含交错出现的重复集合 +// 入参: root 资源文件根节点, group 集合名称, kind 资源类型 +// 返回: iter.Seq[*editorXML] 资源节点序列 +func editorResourceNodes(root *editorXML, group, kind string) iter.Seq[*editorXML] { + return func(yield func(*editorXML) bool) { + for _, collection := range root.children { + if !collection.matchesOFD(group) { + continue + } + for _, node := range collection.children { + if node.matchesOFD(kind) && !yield(node) { + return + } + } + } + } +} + // editorResourceLocation 按ST_Loc与Res.BaseLoc解析路径,保留路径大小写 // 入参: name 所在XML, base 资源目录, value 路径值 // 返回: string 包内路径 diff --git a/ofdgo_res.go b/ofdgo_res.go index a8e5ae6..4acd77f 100644 --- a/ofdgo_res.go +++ b/ofdgo_res.go @@ -14,7 +14,12 @@ package ofdgo -import "encoding/xml" +import ( + "encoding/xml" + "fmt" + "strconv" + "strings" +) // Res 资源文件结构 type Res struct { @@ -134,6 +139,138 @@ type CompositeGraphicUnit struct { extentSet bool } +// UnmarshalXML 解析资源文件,隔离外部命名空间的同名集合。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (res *Res) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + if start.Name.Local != "Res" { + return fmt.Errorf("expected Res element, got %s", start.Name.Local) + } + *res = Res{XMLName: start.Name, BaseLoc: attrValue(start, "BaseLoc")} + return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error { + var target any + switch node.Name.Local { + case "ColorSpaces": + target = &res.ColorSpaces + case "Fonts": + target = &res.Fonts + case "MultiMedias": + target = &res.MultiMedias + case "DrawParams": + target = &res.DrawParams + case "CompositeGraphicUnits": + target = &res.CompositeGraphicUnits + default: + return d.Skip() + } + return d.DecodeElement(target, &node) + }) +} + +// UnmarshalXML 解析颜色空间集合。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (spaces *ColorSpaces) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + return decodeOFDCollection(d, start, "ColorSpace", &spaces.ColorSpace) +} + +// UnmarshalXML 解析颜色空间属性及调色板。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (space *ColorSpace) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + *space = ColorSpace{ID: attrValue(start, "ID"), Type: attrValue(start, "Type"), Profile: attrValue(start, "Profile")} + if value := strings.TrimSpace(attrValue(start, "BitsPerComponent")); value != "" { + bits, err := strconv.Atoi(value) + if err != nil { + return err + } + space.BitsPerComponent = bits + } + return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error { + if node.Name.Local == "Palette" { + return decodeOFDCollection(d, node, "CV", &space.Palette) + } + return d.Skip() + }) +} + +// UnmarshalXML 解析字体集合。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (fonts *Fonts) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + return decodeOFDCollection(d, start, "Font", &fonts.Font) +} + +// UnmarshalXML 解析字体声明及内嵌字体路径。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (font *Font) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + *font = Font{ID: attrValue(start, "ID"), FontName: attrValue(start, "FontName"), FamilyName: attrValue(start, "FamilyName"), Charset: attrValue(start, "Charset")} + for _, attr := range start.Attr { + if attr.Name.Space != "" { + continue + } + var target *bool + switch attr.Name.Local { + case "Italic": + target = &font.Italic + case "Bold": + target = &font.Bold + case "Serif": + target = &font.Serif + case "FixedWidth": + target = &font.FixedWidth + } + if value := strings.TrimSpace(attr.Value); target != nil && value != "" { + flag, err := strconv.ParseBool(value) + if err != nil { + return err + } + *target = flag + } + } + return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error { + if node.Name.Local == "FontFile" { + return d.DecodeElement(&font.FontFile, &node) + } + return d.Skip() + }) +} + +// UnmarshalXML 解析多媒体集合。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (media *MultiMedias) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + return decodeOFDCollection(d, start, "MultiMedia", &media.MultiMedia) +} + +// UnmarshalXML 解析多媒体声明及包内文件路径。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (media *MultiMedia) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + *media = MultiMedia{ID: attrValue(start, "ID"), Type: attrValue(start, "Type"), Format: attrValue(start, "Format")} + return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error { + if node.Name.Local == "MediaFile" { + return d.DecodeElement(&media.MediaFile, &node) + } + return d.Skip() + }) +} + +// UnmarshalXML 解析绘制参数集合。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (params *DrawParams) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + return decodeOFDCollection(d, start, "DrawParam", ¶ms.DrawParam) +} + +// UnmarshalXML 解析复合图元资源集合。 +// 入参: d XML解码器, start 起始节点 +// 返回: error 解码错误 +func (units *CompositeGraphicUnits) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + return decodeOFDCollection(d, start, "CompositeGraphicUnit", &units.CompositeGraphicUnit) +} + // sameResourceID 判断资源标识是否相同,数字标识按数值比较 // 入参: first 第一个标识, second 第二个标识 // 返回: bool 是否相同 diff --git a/ofdgo_xml.go b/ofdgo_xml.go index 42f8cb5..4a10f4c 100644 --- a/ofdgo_xml.go +++ b/ofdgo_xml.go @@ -92,6 +92,18 @@ func decodeOFDChildren(d *xml.Decoder, start xml.StartElement, decode func(*xml. } } +// decodeOFDCollection 按命名空间追加同类子节点,保留重复资源集合的记录 +// 入参: d XML解码器, start 集合起始节点, name 子节点名称, values 接收结果的切片 +// 返回: error 解析错误 +func decodeOFDCollection[T any](d *xml.Decoder, start xml.StartElement, name string, values *[]T) error { + return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error { + if node.Name.Local == name { + return d.DecodeElement(values, &node) + } + return d.Skip() + }) +} + // classifyOFDNamespace 识别OFD命名空间,未声明与未知版本分别处理 // 入参: namespace XML命名空间 // 返回: ofdXMLNamespace 命名空间类别