【Godot4.2】GodotXML插件 - 解析和生成XML

概述

近期在研究Godot的XML和SVG解析,Godot提供了XMLParser类型,但本身只提供了低级的XML解析接口和功能,想要完成完整的XML文档解析和存储可能还需要在其基础上编写类或功能,就像我在昨天(2024年7月20日)基于XMLParser编写了一个简易的SVG文件解析器SVGParser。

但是我早就知道存在GodotXML这样的解析器。加上群友推荐,于是上手试用了一下。和以往一样,看源码里一堆英文注释就手痒,就机翻之,感觉代码布局不合理就稍加改动,并自己加一些注释。

本文主要简介GodotXML项目及其核心文件和核心类的方法,并贴出了汉化后的源代码内容。希望大家一起来学习和使用。

GodotXML简介

  • GodotXML是一个为Godot4.x引擎提供高级XML支持的插件。支持Godot 4.0-4.2,以及可能的未来版本。
  • 项目代码在Github上开源,项目地址:https://github.com/elenakrittik/GodotXML。

功能

  • 基于GDSCript内置的XMLParser编写
  • 可以解析文件或字符串形式XML数据,并方便转换为GDSCript中的字典形式
  • 美化XML(带换行和Tab缩进,类似于VSCode中的“文档格式化”功能)
  • 可以基于GDSCript对象形式生成XML文档结构并方便存储

下载安装

  • 在Godot4资产库中搜索 “GodotXML” 插件安装
  • 或者在Github项目主页下载ZIP压缩包到本地,将 addons/ 文件夹复制到项目的根目录。

下载下来后核心只有3个.gd文件,说是插件其实本质就是1个静态函数库+2个类。
image.png

源码翻译+重排

XML

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qnBr0RJ5-1721699899307)(https://i-blog.csdnimg.cn/direct/e9d1943b34444ef49256d5d2ddaca551.png)]

  • xml.gd内部是一个class_nameXML的静态函数库
  • 使用XML.parse_file()可以解析XML文件为XMLDocument实例
  • 使用XML.parse_string()可以解析XML字符串为XMLDocument实例
  • 使用XML.parse_buffer()可以解析PackedByteArray类型的XML内容为XMLDocument实例

源代码:

# ==================================================================
# 名称:XML
# 描述:GodotXML - Godot4的高级XML支持。此类允许从文件解析XML数据并将其转储到各种源。
# 原作者:elenakrittik(https://github.com/elenakrittik)
# 翻译+重排:巽星石 最后修改:202472115:56:39
# ==================================================================
class_name XML extends RefCounted
# =============================== 表层解析函数(直接调用) ===============================
# 将文件内容作为XML解析为 XMLDocument 实例
# 指定路径处的文件必须可读,文件内容必须是语法有效的XML文档。
static func parse_file(path: String) -> XMLDocument:var file = FileAccess.open(path, FileAccess.READ)var xml: PackedByteArray = file.get_as_text().to_utf8_buffer()file = nullreturn XML._parse(xml)# 将字符串作为XML解析为 XMLDocument 实例
# 字符串内容必须是语法有效的XML文档
static func parse_str(xml: String) -> XMLDocument:return XML._parse(xml.to_utf8_buffer())# 将字 PackedByteArray 作为XML解析为 XMLDocument 实例
# PackedByteArray 内容必须是语法有效的XML文档。
static func parse_buffer(xml: PackedByteArray) -> XMLDocument:return XML._parse(xml)# =============================== 核心解析函数(底层、被调用) ===============================
# 被parse_file()parse_str()parse_buffer()调用
static func _parse(xml: PackedByteArray) -> XMLDocument:xml = _cleanup_double_blankets(xml)  # 见函数体中的注释var doc := XMLDocument.new()var queue: Array[XMLNode] = []  # 未关闭的标签队列var parser := XMLParser.new()parser.open_buffer(xml)while parser.read() != ERR_FILE_EOF:var node: XMLNode = _make_node(queue, parser)# 如果节点类型为NODE_TEXT,则没有节点,因此我们跳过if node == null:continue# 如果我们刚刚开始,我们将第一个节点设置为根节点并初始化队列if len(queue) == 0:doc.root = nodequeue.append(node)else:var node_type := parser.get_node_type()# 下面,`queue.back().childs.append(…)`表示:# - 获取最后一个节点# - 因为我们在那个未闭合的节点内,所以我们得到的所有非闭合节点都是它的子节点# - 因此,我们访问.childs并将我们的非闭合节点附加到它们上# 希望这能说明一切if node.standalone:queue.back().children.append(node)# 这里一样elif node_type == XMLParser.NODE_ELEMENT_END:var last := queue.pop_back()  # 获取删除最后一个未关闭的节点# 如果我们有一个关闭节点,但它的名称与打开节点的名称不同,这是一个错误if node.name != last.name:push_error("Invalid closing tag: started with %s but ended with %s. Ignoring (output may be incorrect)." % [last.name, node.name])# 我们没有在这里打断,而是继续,因为无效的名字往往只是一个拼写错误continue# we just closed a node, so if the queue is empty we stop parsing (effectively ignoring# anything past the first root). this is done to prevent latter roots overwriting former# ones in case when there's more than one root (invalid per standard, but still used in# some documents). we do not natively support multiple roots (and will not, please do not# open PRs for that), but if the user really needs to, it is trivial to wrap the input with# another "housing" node.# 我们刚刚关闭了一个节点,因此如果队列为空,我们将停止解析(实际上忽略了# 任何超过第一根的东西)。这样做是为了防止后根覆盖前根# 如果有多个根(根据标准无效,但仍在使用# 一些文件)。我们本身不支持多个根(也不会,请不要# 为此打开PR),但如果用户真的需要,用以下内容包装输入是很简单的# 另一个“住房”节点。if queue.is_empty():break# opening nodeelse:queue.back().children.append(node)queue.append(node)  # 移动到节点体内# 如果解析已结束,但仍有未关闭的节点,我们会报告它if not queue.is_empty():queue.reverse()var names: Array[String] = []for node in queue:names.append(node.name)push_error("The following nodes were not closed: %s" % ", ".join(names))return doc# =============================== 转换函数 ===============================
# 将 XMLDocument 实例转为纯文本存储到指定文件
static func dump_file(path: String,document: XMLDocument,pretty: bool = false,indent_level: int = 0,indent_length: int = 2
) -> void:return document.root.dump_file(path, pretty, indent_level, indent_length)# 将 XMLDocument 实例转为 PackedByteArray形式
static func dump_buffer(document: XMLDocument,pretty: bool = false,indent_level: int = 0,indent_length: int = 2,
) -> PackedByteArray:return document.root.dump_buffer(pretty, indent_level, indent_length)## 将 XMLDocument 实例转为 String 形式
static func dump_str(document: XMLDocument,pretty: bool = false,indent_level: int = 0,indent_length: int = 2,
) -> String:return document.root.dump_str(pretty, indent_level, indent_length)# 创建节点
static func _make_node(queue: Array[XMLNode], parser: XMLParser) -> Variant:var node_type := parser.get_node_type()match node_type:XMLParser.NODE_ELEMENT:return XML._make_node_element(parser)XMLParser.NODE_ELEMENT_END:return XML._make_node_element_end(parser)XMLParser.NODE_TEXT:# 忽略根节点前的空白文本;这样更容易,相信我if queue.is_empty():returnXML._attach_node_data(queue.back(), parser)returnXMLParser.NODE_CDATA:if queue.is_empty():return_attach_node_cdata(queue.back(), parser)returnreturn# 创建节点元素
static func _make_node_element(parser: XMLParser) -> XMLNode:var node := XMLNode.new()node.name = parser.get_node_name()node.attributes = XML._get_attributes(parser)node.content = ""node.standalone = parser.is_empty()  # see .is_empty() docsnode.children = []return node# 创建结束标签
static func _make_node_element_end(parser: XMLParser) -> XMLNode:var node := XMLNode.new()node.name = parser.get_node_name()node.attributes = {}node.content = ""node.standalone = false  # 独立节点始终为NODE_ELEMENTnode.children = []return node# 
static func _attach_node_data(node: XMLNode, parser: XMLParser) -> void:# XMLParser将节点之间的空白内容视为NODE_TEXT,这是不需要的# 因此,我们去除了“空格”,导致只有实际的内容滑入到内容中node.content += parser.get_node_data().strip_edges()static func _attach_node_cdata(node: XMLNode, parser: XMLParser) -> void:node.cdata.append(parser.get_node_name().strip_edges())# 获取属性
static func _get_attributes(parser: XMLParser) -> Dictionary:var attrs: Dictionary = {}var attr_count: int = parser.get_attribute_count()for attr_idx in range(attr_count):attrs[parser.get_attribute_name(attr_idx)] = parser.get_attribute_value(attr_idx)return attrsstatic func _cleanup_double_blankets(xml: PackedByteArray) -> PackedByteArray:# #XMLParser再次“不正确”,并且由于双空格转义而重复节点# https://github.com/godotengine/godot/issues/81896#issuecomment-1731320027var rm_count := 0 # How much elements (blankets) to remove from the sourcevar idx := xml.size() - 1# 以相反的顺序迭代。这对perf很重要,否则我们# 需要执行double.reverse()并从一开始就删除元素# 这两种阵列都相当昂贵while idx >= 0:if xml[idx] in [9, 10, 13]: # [\t, \n, \r]rm_count += 1idx -= 1else:break# 移除空格while rm_count > 0:xml.remove_at(xml.size() - 1)rm_count -= 1return xml

XMLDocument

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6J3w9fdL-1721699899309)(https://i-blog.csdnimg.cn/direct/fb4373b9eeda4c02b522d65970bcd396.png)]

xml_document.gd中申明了一个XMLDocument类,用于表示XML文档,只提供一个root属性表示XML文档的根节点。

# ==================================================================
# 名称:XMLDocument
# 描述:表示XML文档
# 原作者:elenakrittik(https://github.com/elenakrittik)
# 翻译+重排:巽星石 最后修改:202472115:38:23
# ==================================================================
class_name XMLDocument extends RefCounted
var root: XMLNode
func _to_string():return "<XMLDocument root=%s>" % self.root

XMLNode

可以看到比较核心和重要的内容放到了XMLNode类上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I6v0DgEY-1721699899309)(https://i-blog.csdnimg.cn/direct/37345cf497eb44afa87f6d4d880153c8.png)]

# ==================================================================
# 名称:XMLNode
# 描述:表示XML元素(又名XML节点)。
# 提示:如果只有一个同名的子节点,您可以通过 this_node.my_child_name 访问它!
# 原作者:elenakrittik(https://github.com/elenakrittik)
# 翻译+重排:巽星石 最后修改:202472115:38:23
# ==================================================================
class_name XMLNode extends RefCounted
# ========================================== 属性 ==========================================
var name: String = ""             # 节点名称
var attributes: Dictionary = {}   # 节点属性字典
var content: String = ""          # 节点内容
var cdata: Array[String] = []     # CDATA
var standalone: bool = false      # 是否为空节点(也就是自闭合标签)
var children: Array[XMLNode] = [] # 子节点var _node_props: Array
var _node_props_initialized: bool = falseconst KNOWN_PROPERTIES: Array[String] = ["name", "attributes", "content", "cdata", "standalone", "children"]
# ========================================== 虚函数 ==========================================
# print()或调用to_string()时返回的文本
func _to_string():return "<XMLNode name=%s attributes=%s content=%s cdata=%s standalone=%s children=%s>" % [self.name,"{...}" if len(self.attributes) > 0 else "{}",'"..."' if len(self.content) > 0 else '""',"[...]" if len(self.cdata) > 0 else "[]",self.standalone,"[...]" if len(self.children) > 0 else "[]"]# 通过GDScript进行点访问
func _get(property: StringName):if not self._node_props_initialized:self._initialize_node_properties()if (property not in KNOWN_PROPERTIESand property in self._node_props):for child in self.children:if child.name == property:return child# 通过编辑器进行点式访问
func _get_property_list() -> Array[Dictionary]:var props: Array[Dictionary] = []if not self._node_props_initialized:self._initialize_node_properties()for child_name in self._node_props:props.append({"name": child_name,"type": TYPE_OBJECT,"class_name": "XMLNode","usage": PROPERTY_USAGE_DEFAULT,"hint": PROPERTY_HINT_NONE,"hint_string": "",})return props# 初始化节点属性
func _initialize_node_properties() -> void:var names_to_nodes := {}for child: XMLNode in self.children:if not child.name in names_to_nodes.keys():names_to_nodes[child.name] = childelse:names_to_nodes.erase(child.name)self._node_props = names_to_nodes.keys()self._node_props_initialized = true
# ========================================== 方法 ==========================================
# 将当前节点(及其所有子节点)转换为字典形式
func to_dict() -> Dictionary:var output := {}output["__name__"] = self.name         # 节点名称output["__content__"] = self.content   # 节点内容output["__cdata__"] = self.cdata       # CDATAoutput["attrs"] = self.attributes      # 节点属性# 遍历+递归获取子节点的字典形式var children_dict := {}for child in self.children:children_dict[child.name] = child.to_dict()output["children"] = children_dictreturn output# -------------------- 类型转化 -------------------- 
# 将当前节点转储到指定文件
func dump_file(path: String,pretty: bool = false,indent_level: int = 0,indent_length: int = 2) -> void:var file = FileAccess.open(path, FileAccess.WRITE)var xml: String = self.dump_str(pretty, indent_level, indent_length)file.store_string(xml)file = null# 将当前节点转化为 PackedByteArray 形式
func dump_buffer(pretty: bool = false,indent_level: int = 0,indent_length: int = 2) -> PackedByteArray:return self.dump_str(pretty, indent_level, indent_length).to_utf8_buffer()# 将当前节点转化为 String 形式。
func dump_str(pretty: bool = false,indent_level: int = 0,indent_length: int = 2) -> String:if indent_level < 0:push_warning("indent_level必须>= 0")indent_level = 0if indent_length < 0:push_warning("indent_length必须>= 0")indent_length = 0return self._dump() if not pretty else self._dump_pretty(indent_level, indent_length)# -------------------- 类型转化核心函数(底层、被调用) -------------------- 
# 转化核心函数
func _dump() -> String:var attribute_string := ""var children_string := ""var cdata_string = ""if not self.attributes.is_empty():attribute_string += " "for attribute_key in self.attributes:var attribute_value := self.attributes.get(attribute_key)if attribute_value is String:attribute_value = attribute_value.xml_escape(true)attribute_string += '{key}="{value}"'.format({"key": attribute_key, "value": attribute_value})for child: XMLNode in self.children:children_string += child._dump()for cdata_content in self.cdata:cdata_string += "<![CDATA[%s]]>" % cdata_content.replace("]]>", "]]]]><![CDATA[>")if self.standalone:return "<" + self.name + attribute_string + "/>"else:return ("<" + self.name + attribute_string + ">" +self.content.xml_escape() + cdata_string + children_string +"</" + self.name + ">")
# -------------------- 格式化 -------------------- 
# 格式化XML文档
# 让杂乱的内容变的清晰美观
func _dump_pretty(indent_level: int, indent_length: int) -> String:var indent_string := " ".repeat(indent_level * indent_length)var indent_next_string := indent_string + " ".repeat(indent_length)var attribute_string := ""var content_string := "\n" + indent_next_string + self.content.xml_escape() if not self.content.is_empty() else ""var children_string := ""var cdata_string := ""if not self.attributes.is_empty():for attribute_key in self.attributes:var attribute_value := self.attributes.get(attribute_key)if attribute_value is String:attribute_value = attribute_value.xml_escape(true)attribute_string += ' {key}="{value}"'.format({"key": attribute_key, "value": attribute_value})for child: XMLNode in self.children:children_string += "\n" + child.dump_str(true, indent_level + 1, indent_length)for cdata_content in self.cdata:cdata_string += "\n" + indent_next_string + ("<![CDATA[%s]]>" % cdata_content.replace("]]>", "]]]]>\n%s<![CDATA[>" % indent_next_string))if self.standalone:return indent_string + "<" + self.name + attribute_string + "/>"else:return (indent_string + "<" + self.name + attribute_string + ">" +content_string + cdata_string + children_string +"\n" + indent_string + "</" + self.name + ">")

使用

解析XML文件

@tool
extends EditorScriptfunc _run() -> void:var xml_doc:XMLDocument = XML.parse_file("icon.svg")  # 解析SVG文件为XMLDocument实例print(xml_doc)pass

上面代码中:

  • 我们使用XML静态函数库的parse_file()静态方法,将Godot项目根目录的icon.svg解析为XMLDocument实例
  • print(xml_doc)会自动调用XMLDocument内部的_to_string()并打印其返回值

输出:

<XMLDocument root=<XMLNode name=svg attributes={...} content="" cdata=[] standalone=false children=[...]>>

解析XML并转为字典形式

@tool
extends EditorScriptfunc _run() -> void:var xml_doc:XMLDocument = XML.parse_file("icon.svg")  # 解析SVG文件为XMLDocument实例var root:XMLNode = xml_doc.root          # 获取根节点var dict:Dictionary = root.to_dict()     # 转化为字典形式print(JSON.stringify(dict,"\t") )        # 以格式化的JSON形式打印

上面代码中:

  • 使用XMLNodeto_dict()方法会将当前节点转化为字典,其内部会采用递归形式将其所有子孙节点转化为字典放入对应父节点的children属性中。
  • XMLDocumentroot属性记录了解析后的XML文档根节点,我们只需要对这个根节点调用to_dict()方法,就可以获得整个XML文档的字典形式

上面代码输出:

{"__cdata__": [],"__content__": "","__name__": "svg","attrs": {"height": "128","width": "128","xmlns": "http://www.w3.org/2000/svg"},"children": {"g": {"__cdata__": [],"__content__": "","__name__": "g","attrs": {"transform": "scale(.101) translate(122 122)"},"children": {"g": {"__cdata__": [],"__content__": "","__name__": "g","attrs": {"fill": "#414042"},"children": {"circle": {"__cdata__": [],"__content__": "","__name__": "circle","attrs": {"cx": "717","cy": "532","r": "60"},"children": {}}}}}},"rect": {"__cdata__": [],"__content__": "","__name__": "rect","attrs": {"fill": "#363d52","height": "124","rx": "14","stroke": "#212532","stroke-width": "4","width": "124","x": "2","y": "2"},"children": {}}}
}

可以看到,每个节点主要结构:

  • __name__:存储XML节点名称
  • __content__:存储XML节点内部的文本
  • __cdata__:存储CDATD
  • attrs:以字典和键值对形式存储XML节点的属性和值
  • children以字典形式存储XML节点的子节点

构造XML文件

搞定XML解析和转化之后,我们也可以用代码形式生成XML文件。

@tool
extends EditorScriptfunc _run() -> void:var xml_doc:= XMLDocument.new()# 根节点svgvar root = XMLNode.new()root.name = "svg"root.attributes = {"width":200,"height":200}# 子节点rectvar rect = XMLNode.new()rect.name = "rect"rect.attributes = {"width":200,"height":200,"fill":"red"}root.children.append(rect)    # 添加为根节点的子节点xml_doc.root = root           # 设为文档根节点print(XML.dump_str(xml_doc))  # 转为文本形式

上面的代码中:

  • 我们利用XMLDocumentnew()方法创建了一个XMLDocument实例xml_doc
  • 接着我们用XMLNodenew()方法创建根节点root和其子节点rect,并设定相应的属性。
  • 最后将rect作为子节点添加到rootchildren中去,再指定xml_doc的根节点rootroot
  • 最后通过XML函数库的dump_str方法,将xml_doc转为字符串形式并打印。

打印输出:

<svg width="200"height="200"><rect width="200"height="200"fill="red"></rect></svg>

格式化

dump_str()方法的pretty参数设为true:

print(XML.dump_str(xml_doc,true))  # 转为文本形式

则会进行文档格式化,输出效果:

<svg width="200" height="200"><rect width="200" height="200" fill="red"></rect>
</svg>

可以看到这里默认把<rect>设定为双标签了。我们只需要设定rectstandalone属性等于true就可以了。

输出:

<svg width="200" height="200"><rect width="200" height="200" fill="red"/>
</svg>

存储文件

要将生成的XML或SVG内容存储为文件,只需要调用XML静态函数库的dump_file()方法就可以了。

XML.dump_file("test.svg",xml_doc,true)  # 转为文本形式

打开后:
image.png

我们只需要给根节点<svg>标签加上xmlns属性和其固定值http://www.w3.org/2000/svg就可以了。

# 根节点svgvar root = XMLNode.new()root.name = "svg"root.attributes = {"width":200,"height":200,"xmlns":"http://www.w3.org/2000/svg"}

此时生成的SVG文件就可以正常查看了。
image.png
也可以直接在Godot中使用了:

image.png

个人心声

之所以研究SVG,是因为:

  • 可以在Godot中作为美术素材使用,本质上是纯文本,而且是参数化和矢量化的
  • SVG与Godot内置绘图函数的参数化形式最为接近,理论上SVG可以与绘图函数互转
  • 可以用函数或代码形式动态生成SVG图形,并赋值给节点
  • 对于TextureButtonTextureProgress节点,可以参数化生成其所需的图片元素
  • 可以将碰撞图形、Polygon2DLine2DPath2D等的形状和路径信息转为SVG形式存储和修改

总结

  • Godot中的XMLParser类只提供了基础的API来解析XML文件,如果需要像ConfigFileJSON一样容易使用,你就需要自己动手编写一个解析器
  • 而GodotXML,低级别的可以直接“拿来主义”,用就完事儿,也可以研究研究源码,再其基础上进行改进或二次设计。
  • 在GodotXML基础上可以进一步编写类或函数库,实现更多功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/381902.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

六边形动态特效404单页HTML源码

源码介绍 动态悬浮的六边形,旁边404文字以及跳转按钮,整体看着像科技二次元画风,页面简约美观,可以做网站错误页或者丢失页面,将下面的代码放到空白的HTML里面,然后上传到服务器里面,设置好重定向即可 效果预览 完整源码 <!DOCTYPE html> <html><head…

CH04_依赖项属性

第4章&#xff1a;依赖项属性 本章目标 理解依赖项属性理解属性验证 依赖项属性 ​ 属性与事件是.NET抽象模型的核心部分。WPF使用了更高级的依赖项属性&#xff08;Dependency Property&#xff09;功能来替换原来.NET的属性&#xff0c;实现了更高效率的保存机制&#xf…

python实现责任链模式

把多个处理方法串成一个list。下一个list的节点是上一个list的属性。 每个节点都有判断是否能处理当前数据的方法。能处理&#xff0c;则直接处理&#xff0c;不能处理则调用下一个节点&#xff08;也就是当前节点的属性&#xff09;来进行处理。 Python 实现责任链模式&#…

【QAC】Dashboard服务端如何配置

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决Dashboard服务端如何配置的问题。 2、 问题场景 客户想使用Dashboard&#xff0c;Dashboard服务端如何配置。 3、软硬件环境 1、软件版本&#xff1a;HelixQAC23.04 2、机器环境&#xff1a;Windows 64bit 3…

HarmonyOS鸿蒙应用开发-ZRouter让系统路由表变得更简单

介绍 ZRouter是基于Navigation系统路由表和Hvigor插件实现的动态路由方案。 系统路由表是API 12起开始支持的&#xff0c;可以帮助我们实现动态路由的功能&#xff0c;其目的是为了解决多个业务模块&#xff08;HAR/HSP&#xff09;之间解耦问题&#xff0c;从而实现业务的复…

数据结构~~顺序表

目录 一、顺序表的概念 二、顺序表的接口实现 1.顺序表初始化 2.顺序表销毁 3.检查空间并扩容 4.顺序表尾插、顺序表头插 5.顺序表尾删、顺序表头删 6.顺序表查找 7.顺序表在pos位置插入x、删除pos位置的值 三、完整代码 四、总结 一、顺序表的概念 顺序表是用一…

逆向案例二十八——某高考志愿网异步请求头参数加密,以及webpack

网址&#xff1a;aHR0cDovL3d3dy54aW5nYW9rYW90Yi5jb20vY29sbGVnZXMvc2VhcmNo 抓包分析&#xff0c;发现请求头有参数u-sign是加密的&#xff0c;载荷没有进行加密&#xff0c;直接跟栈分析。 进入第二个栈&#xff0c;打上断点&#xff0c;分析有没有加密位置。 可以看到参数…

Python爬虫实战 | 爬取携程网景区评论|美食推荐|景点列表数据

本文采用Selenium库爬取携程网的景区评论。 携程接口接入 Selenium介绍 Selenium是一个Web的自动化测试工具&#xff0c;可以按指定的命令自动操作&#xff0c;如让浏览器加载页面、获取数据、页面截屏等。Selenium本身不自带浏览器&#xff0c;需要与第三方浏览器结合才能使…

面试官问:Django、Flask、FastAPI,你选哪个?为什么?

如果你是python Web方向的开发工程师&#xff0c;那么在面试中&#xff0c;会经常遇到面试官问这个问题&#xff1a; “在Python的三个流行Web框架&#xff1a;Django、Flask和FastAPI&#xff0c;说说它们的异同&#xff0c;以及你是怎么选择合适的框架&#xff1f;” 异同对…

基于SSM的高考志愿选择辅助系统

基于SSM的高考志愿选择辅助系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 前台 前台首页 院校展示 后台 后台首页 学校管理 摘要 随着高考制度的不断完…

python-爬虫实例(4):获取b站的章若楠的视频

目录 前言 道路千万条&#xff0c;安全第一条 爬虫不谨慎&#xff0c;亲人两行泪 获取b站的章若楠的视频 一、话不多说&#xff0c;先上代码 二、爬虫四步走 1.UA伪装 2.获取url 3.发送请求 4.获取响应数据进行解析并保存 总结 前言 道路千万条&#xff0c;安全第一条 爬…

成为CMake砖家(4): VSCode中的CMake语法高亮

大家好&#xff0c;我是白鱼。 在成为CMake砖家的路上&#xff0c;我的主力 IDE/编辑器是 VSCode。 VSCode 免费、插件丰富、文档完善&#xff0c; 相比于 CLion 的年费几百上千元的license真的很香。 不过&#xff0c; 工欲善其事必先利其器&#xff0c; VSCode 需要安装合适…

FastDFS分布式存储

一&#xff1a;FastDFS原理 FastDFS是一个开源的轻量级分布式文件系统&#xff0c;功能包括&#xff1a;文件存储&#xff0c;文件同步&#xff0c;文件访问&#xff08;文件上传、文件下载&#xff09;等&#xff0c;解决了大容量存储和负载均衡的问题。 1&#xff1a;FastD…

物联网在电力行业的应用

作者主页: 知孤云出岫 这里写目录标题 作者主页:物联网在电力行业的应用简介主要应用领域代码案例分析1. 智能电表数据采集和分析2. 设备监控和预测性维护3. 能耗管理和优化4. 电力负载预测5. 分布式能源管理6. 电动汽车充电管理7. 电网安全与故障检测 物联网在电力行业的应用…

CH03_布局

第3章&#xff1a;布局 本章目标 理解布局的原则理解布局的过程理解布局的容器掌握各类布局容器的运用 理解 WPF 中的布局 WPF 布局原则 ​ WPF 窗口只能包含单个元素。为在WPF 窗口中放置多个元素并创建更贴近实用的用户男面&#xff0c;需要在窗口上放置一个容器&#x…

海康威视综合安防管理平台 detection 前台RCE漏洞复现

0x01 产品简介 海康威视综合安防管理平台是一套“集成化”、“智能化”的平台,通过接入视频监控、一卡通、停车场、报警检测等系统的设备。海康威视集成化综合管理软件平台,可以对接入的视频监控点集中管理,实现统一部署、统一配置、统一管理和统一调度。 0x02 漏洞概述 海康…

【Gin】精准应用:Gin框架中工厂模式的现代软件开发策略与实施技巧(上)

【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(上) 大家好 我是寸铁&#x1f44a; 【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(上)✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 本次文章分为上下两部分&…

算法题目整合4

文章目录 122. 大数减法123. 滑动窗口最大值117. 软件构建124. 小红的数组构造125. 精华帖子126. 连续子数组最大和 122. 大数减法 题目描述 以字符串的形式读入两个数字&#xff0c;编写一个函数计算它们的差&#xff0c;以字符串形式返回。输入描述 输入两个数字&#xff…

UE TSharedPtr

文章目录 概述TSharedPtrTSharedPtr包含2部分 构造&#xff0c;析构&#xff0c;拷贝构造&#xff0c;移动构造构造拷贝构造移动构造 小结 概述 之前写过一篇c的智能指针的&#xff0c;这篇写下ue的。本质上来说是差不多的&#xff0c;可以简单看看。 TSharedPtr 如下图&…

分析性能提升40%,阿里云Hologres流量场景最佳实践

在互联网和移动分析时代&#xff0c;流量数据成为了企业洞察用户行为、优化产品决策和提升运营效率的关键资源。流量数据主要来源于用户在使用APP、小程序或访问网站等媒介平台时产生的各种操作行为&#xff0c;如点击、浏览、注册、下单等。这些行为数据通过数据埋点技术被采集…