概述
这是一个基于GDScript内置XMLParser编写的简易SVG文件解析函数库。
目的就是可以将SVG文件解析为GDSCript可以处理的字典或DOM形式,方便SVG渲染和编辑。
目前还只是一个简易实现版本。还需要一些改进。
函数库源码
# =============================================
# 名称:SVGParser
# 类型:静态函数库
# 描述:解析SVG文件,并转换为字典
# 作者:巽星石
# 创建时间:2024年7月20日18:22:43
# 最后修改时间:2024年7月21日01: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] = nullfor 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 tagstatic 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:Dictionaryvar 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 EditorScriptfunc _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 EditorScriptfunc _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 EditorScriptfunc _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:[]]]
]