【Godot4.2】SVGParser - SVG解析器函数库

概述

这是一个基于GDScript内置XMLParser编写的简易SVG文件解析函数库。

目的就是可以将SVG文件解析为GDSCript可以处理的字典或DOM形式,方便SVG渲染和编辑。

目前还只是一个简易实现版本。还需要一些改进。

函数库源码

# =============================================
# 名称:SVGParser
# 类型:静态函数库
# 描述:解析SVG文件,并转换为字典
# 作者:巽星石
# 创建时间:202472018:22:43
# 最后修改时间:202472101:17:52
# =============================================
class_name SVGParser

# 标签顺序列表
# 返回SVG解析后的所有单标签和双标签的起始和结束标签
static func get_tags_list(path:String) -> Array:
	var tags:Array = []
	var xml = XMLParser.new()
	var err = xml.open(path)
	if err == OK:
		while xml.read() == OK:
			match xml.get_node_type():
				XMLParser.NODE_ELEMENT:  # 起始标签
					if xml.is_empty(): # 单标签(自闭合)
						tags.append("<%s />" % [xml.get_node_name()])
					else:
						tags.append("<%s>" %  [xml.get_node_name()])
				XMLParser.NODE_ELEMENT_END:  # 结束标签
					tags.append("</%s>" %  [xml.get_node_name()])	
	return tags

# 将SVG文档转化为字典
static func to_dict(path:String) -> Dictionary:
	var xml = XMLParser.new()
	var err = xml.open(path)
	if err == OK:
		# 1.获取顺序标签列表(包含起始单标签、双标签的起始和结束标签)
		var tags:Array = []
		
		while xml.read() == OK:
			if xml.get_node_type() in [XMLParser.NODE_ELEMENT,XMLParser.NODE_ELEMENT_END]:
				# 构造字典
				var tag = {}
				if xml.get_node_type() == XMLParser.NODE_ELEMENT:
					tag["name"] = xml.get_node_name()
				else:
					tag["name"] = "/%s" % xml.get_node_name()
				tag["is_single"] = xml.is_empty()  # 是否单标签
				tag["index"] = tags.size()
				tag["children"] = []
				# 构造属性字典
				tag["attrs"] = {}
				for i in range(xml.get_attribute_count()):
					tag["attrs"][xml.get_attribute_name(i)] = xml.get_attribute_value(i)
				tags.append(tag)
		#print(JSON.stringify(tags,"\t"))
		
		# 2.使用栈获取双标签的其实和结束范围序列
		var stack:Array = []
		var arr:Array = []  # 双标签的起止索引
		for i in range(tags.size()):
			if tags[i]["is_single"] != true: # 双标签
				if !tags[i]["name"].begins_with("/"): #起始标签
					stack.push_front(tags[i])   # 推入
				else: # 结束标签
					var last_tag = stack.pop_front()
					if tags[i]["name"] == "/%s" % last_tag["name"]: # 起止标签匹配
						arr.append([last_tag["index"],tags[i]["index"]])
		
		#print(JSON.stringify(stack,"\t"))
		
		
		# 3.设定父子级别关系
		# 获取所有双标签的起始标签索引
		var dbl_indexs = []
		for i in range(arr.size()):
			dbl_indexs.append(arr[i][0])
		# 删除标签列表中所有双标签结束标签
		for i in range(tags.size()):
			if tags[i]["name"].begins_with("/"):
				tags[i] = null
		for tag in tags:
			if tag == null:
				tags.erase(tag)
		for tag in tags:
			if tag == null:
				tags.erase(tag)
		# 遍历双标签索引对,设定父子关系
		for i in range(arr.size()):
			var start = arr[i][0]
			var end = arr[i][1]
			# 遍历子标签
			for x in range(start+1,end):
				var tag = get_tag(tags,x)
				#print(tag)
				if tag != null and !tag["name"].begins_with("/"):
					get_tag(tags,start)["children"].append(tag)
					remove_tag(tags,x)
		return tags[0]
	else:
		return {}

static func get_tag(tags,index):
	for tag in tags:
		if tag != null:
			if tag["index"] == index:
				return tag

static func remove_tag(tags,index):
	for tag in tags:
		if tag != null:
			if tag["index"] == index:
				tags.erase(tag)
				
# ================================== 简易DOM创建 ==================================
# SVG元素
class item:
	var tag_name:String = ""
	var attrs:Dictionary
	var children:Array[item] = [] # 子节点
	
	func _to_string() -> String:
		return "\n%s:%s\n" % [tag_name,str(children)]

# 递归形式创建DOM
static func dom(item_dic:Dictionary) -> item:
	var itm = item.new()
	itm.tag_name = item_dic["name"]
	for dic in item_dic["children"]:
		itm.children.append(dom(dic))
	return itm

# 返回SVG文件的DOM形式
static func to_DOM(path:String) -> item:
	var dict = to_dict(path)
	return dom(dict)

获取顺序标签列表

在这里插入图片描述

我们以Godot图标icon.svg为例,其SVG源码如下:

<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg">
    <rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4" />
    <g transform="scale(.101) translate(122 122)">
        <g fill="#fff">
            <path d="M105 673v33q407 354 814 0v-33z" />
            <path fill="#478cbf"
                d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z" />
            <path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z" />
            <circle cx="725" cy="526" r="90" />
            <circle cx="299" cy="526" r="90" />
        </g>
        <g fill="#414042">
            <circle cx="307" cy="532" r="60" />
            <circle cx="717" cy="532" r="60" />
        </g>
    </g>
</svg>

测试代码:

@tool
extends EditorScript

func _run() -> void:
	var tags = SVGParser.get_tags_list("icon.svg")
	print(JSON.stringify(tags,"\t"))

输出:

[
	"<svg>",
	"<rect />",
	"<g>",
	"<g>",
	"<path />",
	"<path />",
	"<path />",
	"<circle />",
	"<circle />",
	"</g>",
	"<g>",
	"<circle />",
	"<circle />",
	"</g>",
	"</g>",
	"</svg>"
]

可以看到其返回SVG解析后的所有单标签和双标签(包括起始和结束标签)的顺序列表。

通过它可以测试函数库是否正确解析了SVG文件的标签结构。

SVG转字典

  • to_dict()方法,可以将SVG文件内容解析和转化为GDScript的字典。

测试代码:

@tool
extends EditorScript

func _run() -> void:
	var dict = SVGParser.to_dict("res://icon.svg")
	print(JSON.stringify(dict,"\t"))

转化结果:

{
	"attrs": {
		"height": "128",
		"width": "128",
		"xmlns": "http://www.w3.org/2000/svg"
	},
	"children": [
		{
			"attrs": {
				"fill": "#363d52",
				"height": "124",
				"rx": "14",
				"stroke": "#212532",
				"stroke-width": "4",
				"width": "124",
				"x": "2",
				"y": "2"
			},
			"children": [],
			"index": 1,
			"is_single": true,
			"name": "rect"
		},
		{
			"attrs": {
				"transform": "scale(.101) translate(122 122)"
			},
			"children": [
				{
					"attrs": {
						"fill": "#fff"
					},
					"children": [
						{
							"attrs": {
								"d": "M105 673v33q407 354 814 0v-33z"
							},
							"children": [],
							"index": 4,
							"is_single": true,
							"name": "path"
						},
						{
							"attrs": {
								"d": "m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z",
								"fill": "#478cbf"
							},
							"children": [],
							"index": 5,
							"is_single": true,
							"name": "path"
						},
						{
							"attrs": {
								"d": "M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"
							},
							"children": [],
							"index": 6,
							"is_single": true,
							"name": "path"
						},
						{
							"attrs": {
								"cx": "725",
								"cy": "526",
								"r": "90"
							},
							"children": [],
							"index": 7,
							"is_single": true,
							"name": "circle"
						},
						{
							"attrs": {
								"cx": "299",
								"cy": "526",
								"r": "90"
							},
							"children": [],
							"index": 8,
							"is_single": true,
							"name": "circle"
						}
					],
					"index": 3,
					"is_single": false,
					"name": "g"
				},
				{
					"attrs": {
						"fill": "#414042"
					},
					"children": [
						{
							"attrs": {
								"cx": "307",
								"cy": "532",
								"r": "60"
							},
							"children": [],
							"index": 11,
							"is_single": true,
							"name": "circle"
						},
						{
							"attrs": {
								"cx": "717",
								"cy": "532",
								"r": "60"
							},
							"children": [],
							"index": 12,
							"is_single": true,
							"name": "circle"
						}
					],
					"index": 10,
					"is_single": false,
					"name": "g"
				}
			],
			"index": 2,
			"is_single": false,
			"name": "g"
		}
	],
	"index": 0,
	"is_single": false,
	"name": "svg"
}

目前除了text标签还需要一点特殊处理外,其他标签已经不存在明显解析问题。

在字典基础上,已经可以实现在Godot中的分层渲染和转为内置绘图函数绘制。也可以进一步转化为DOM形式,方便编辑和二次输出。

生成DOM

@tool
extends EditorScript

func _run() -> void:
	var dom = SVGParser.to_DOM("icon.svg")
	print(dom)

输出:

svg:[
rect:[]
, 
g:[
g:[
path:[]
, 
path:[]
, 
path:[]
, 
circle:[]
, 
circle:[]
]
, 
g:[
circle:[]
, 
circle:[]
]
]
]

整理后:

svg:[
    rect:[], 
    g:[
        g:[
            path:[], 
            path:[], 
            path:[], 
            circle:[], 
            circle:[]
        ], 
        g:[
            circle:[], 
            circle:[]
        ]
    ]
]

相关推荐

  1. Matlab: 输入解析验证函数

    2024-07-21 08:18:02       46 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-21 08:18:02       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-21 08:18:02       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-21 08:18:02       45 阅读
  4. Python语言-面向对象

    2024-07-21 08:18:02       55 阅读

热门阅读

  1. linux学习笔记整理: 关于linux:Redis数据库 2024/7/20;

    2024-07-21 08:18:02       19 阅读
  2. 安卓逆向入门(3)------Frida基础

    2024-07-21 08:18:02       18 阅读
  3. SDL常用结构体和函数接口

    2024-07-21 08:18:02       20 阅读
  4. 谷粒商城实战笔记-35-前端基础-ES6-模块化

    2024-07-21 08:18:02       18 阅读
  5. 分享一款开源免费的ftp服务工具——Pyftpsync

    2024-07-21 08:18:02       20 阅读
  6. 线程局部变量共享

    2024-07-21 08:18:02       18 阅读
  7. SQL Server报告服务的艺术:在SSRS中打造专业报告

    2024-07-21 08:18:02       18 阅读
  8. 探索Sklearn的分层抽样:数据科学中的精确艺术

    2024-07-21 08:18:02       18 阅读