【Godot4.2】MLTag类:HTML、XML通用标签类
概述
HTML和XML采用类似的标签形式。
之前在Godot中以函数库形式实现了网页标签和内容生成。能用,但是缺点也很明显。函数之间没有从属关系,但是多有依赖,而且没有划分出各种对象和类型。
如果以完全的面向对象形式来设计标签类或者元素类,将可以更贴近HTML或XML的本来面目。也更容易生成。
整体思路是设计如下的类继承结构:

实现之后,将可以充分的定义和生成HTML、XML和SVG标签,并用于内容生成或文档解析。
通用标签类
因为HTML和XML标签的语法格式和要素是类似的,因此可以通过创建一个通用的标签类,来生成HTML或XML标签。
MLtag就是这个通用的标签类,它定义了几个核心的属性:
tag_name:标签名称attrs:包含标签所有属性的字典is_single:是否为单标签,默认为falsecontent:标签的子内容,可以直接赋值,也可以使用append()方法追加。单标签(is_single为true)时被忽略get_end_tag():获取结束标签,单标签时返回空字符串append():追加当前标签的子内容,可以是字符串形式,也可以是SVG标签实例(会自动调用to_string()方法转化为字符串)
# =============================================
# 名称:MLtag
# 类型:类
# 描述:HTML、XML通用标签类,用于定义和生成HTML、XML标签字符串
# 作者:巽星石
# 创建时间:2024年7月16日17:48:01
# 最后修改时间:2024年7月16日22:27:53
# =============================================
class_name MLTag# ====================== 属性 ======================
var tag_name = "" # 标签名称
var attrs:Dictionary = {} # 属性字典
var is_single = false # 是否单标签
var content = "" # 子内容 # ====================== 方法 ======================
# 获取结束标签
func get_end_tag() -> String:return "" if is_single else "</%s>" % tag_name# 追加子内容
func append(new_content) -> void:if new_content is String:content += "\n" + new_contentelse:content += "\n" + new_content.to_string()# ====================== 虚函数 ======================
# 转化为字符串
func _to_string() -> String:var tag_str:String# 获取属性字典的字符串var attr = ""for key in attrs.keys():if attrs[key] != "":attr += "%s=\"%s\" " % [key,attrs[key]]tag_str = "<%s%s/>" % [tag_name," " + attr] if is_single else "<%s%s>%s</%s>" % [tag_name," " + attr,content,tag_name]return tag_str
测试:
@tool
extends EditorScriptfunc _run() -> void:var tag = MLTag.new() # 创建一个标签tag.name = "a" # 名称tag.attrs["id"] = "a1" # 设定属性tag.attrs["href"] = "https://www.runoob.com/"tag.content = "菜鸟教程"print(tag)
默认是双标签,也就是带有起始标签和结束标签的标签对。
<a id="a1" href="https://www.runoob.com/" >菜鸟教程</a>
is_single设为true后,就被认为是单标签:
@tool
extends EditorScriptfunc _run() -> void:var tag = MLTag.new() # 创建一个标签tag.name = "a" # 名称tag.attrs["id"] = "a1" # 设定属性tag.attrs["href"] = "1.com"tag.innerHTML = "hahah"tag.is_single = true # 设为标签print(tag)
打印输出的结果也就变了:
<a id="a1" href="https://www.runoob.com/" />
可以看到没有了结束标签,子内容部分也自动省略。
HTML标签基类
# =============================================
# 名称:HTMLtag
# 类型:类
# 描述:HTML标签基类
# 作者:巽星石
# 创建时间:2024年7月16日19:05:46
# 最后修改时间:2024年7月16日19:29:34
# =============================================
class_name HTMLTag extends MLTag# ====================== 属性 ======================
# ID
var id:String:set(val):attrs["id"] = valget:return attrs["id"]
# name
var name:String:set(val):attrs["name"] = valget:return attrs["name"]
# css类名
var className:String:set(val):attrs["class"] = valget:return attrs["class"]
# 行内CSS样式
var style:String:set(val):attrs["style"] = valget:return attrs["style"]
# ====================== 初始化 ======================
func _init() -> void:# HTML标签通用属性attrs = {"id" = "", # ID"name" = "", # name属性"class" = "", # css类名"style" = "", # 行内样式}
测试代码:
@tool
extends EditorScriptfunc _run() -> void:var tag = HTMLTag.new() # 创建一个标签tag.tag_name = "a" # 名称# 设定HTML标签通用属性tag.id = "a2"tag.name = "a2"tag.className = "url"tag.style = "font-size:16px;"tag.innerHTML = "一个超链接"print(tag)
输出:
<a id="a2" name="a2" class="url" style="font-size:16px;" >一个超链接</a>
HTML<a>标签
# =============================================
# 名称:HTMLtagA
# 类型:类
# 描述:HTML的<a>标签类
# 作者:巽星石
# 创建时间:2024年7月16日19:32:47
# 最后修改时间:2024年7月16日22:30:57
# =============================================
class_name HTMLTagA extends HTMLTag# ====================== 属性 ======================
# 链接地址
var href:String:set(val):attrs["href"] = valget:return attrs["href"]
# 指定链接如何在浏览器中打开
var target:String:set(val):attrs["target"] = valget:return attrs["target"]
# 鼠标提示文本
var title:String:set(val):attrs["title"] = valget:return attrs["title"]
# 指定与链接目标的关系,如 nofollow、noopener 等
var rel:String:set(val):attrs["rel"] = valget:return attrs["rel"]# ====================== 初始化 ======================
func _init() -> void:super() # 初始化父类,否则无法继承相关的默认属性# 确定标签名称以及是否单标签tag_name = "a"is_single = false# <a>标签可用属性attrs["href"] = "" # 链接地址attrs["target"] = "" # 指定链接如何在浏览器中打开attrs["title"] = "" # 鼠标提示文本attrs["rel"] = "" # 指定与链接目标的关系,如 nofollow、noopener 等
测试代码:
@tool
extends EditorScriptfunc _run() -> void:var link = HTMLTagA.new() # 创建一个标签# 设定<a>标签属性link.href = "https://www.runoob.com/"link.innerHTML = "菜鸟教程"print(link)
输出:
<a href="https://www.runoob.com/" >菜鸟教程</a>
SVG标签类

我发现专门做一个XML标签基类是没有必要的,XML本身就没有固定的标签,所以由MLTag定义完全可以解决XML标签定义和生成的功能。
而<svg>本身既可以算HTML标签,也可以看做是XML标签。这里为了简化,我创建SVG类,代表<svg>标签,并直接继承自MLTag类型。
# =============================================
# 名称:SVG
# 类型:类
# 描述:SVG的<svg>标签类
# 作者:巽星石
# 创建时间:2024年7月16日21:37:24
# 最后修改时间:2024年7月16日21:44:04
# =============================================
class_name SVG extends MLTag# ====================== 属性 ======================
# SVG画布宽度
var width:String:set(val):attrs["width"] = valget:return attrs["width"]
# SVG画布高度
var height:String:set(val):attrs["height"] = valget:return attrs["height"]# ====================== 初始化 ======================
func _init() -> void:tag_name = "svg"# HTML标签通用属性attrs = {"version"="1.1","baseProfile"="full","width"="200","height"="200","xmlns"="http://www.w3.org/2000/svg",}
SVGShape
SVG的形状和路径有许多共同的属性,所以抽象出一个SVGShape类型,用来承载共同属性。
# =============================================
# 名称:SVGShape
# 类型:类
# 描述:SVG所有形状和路径标签类的基类,承载共同属性
# 作者:巽星石
# 创建时间:2024年7月16日21:51:00
# 最后修改时间:2024年7月16日23:07:46
# =============================================
class_name SVGShape extends MLTag# ====================== 属性 ======================
# 填充颜色
var fill:String:set(val):attrs["fill"] = valget:return attrs["fill"]# 描边颜色
var stroke:String:set(val):attrs["stroke"] = valget:return attrs["stroke"]# 描边宽度
var stroke_width:int:set(val):attrs["stroke-width"] = str(val)get:return int(attrs["stroke-width"])# ====================== 初始化 ======================
func _init() -> void:tag_name = "svg"# svg形状和路径标签通用属性attrs["fill"] = "#fff"attrs["stroke"] = "#000"attrs["stroke-width"] = "1"
矩形
基于SVGShape类型,我们可以创建SVG具体的形状和路径类型。SVGRect就是SVG矩形
# =============================================
# 名称:SVGRect
# 类型:类
# 描述:SVG的<rect>标签类
# 作者:巽星石
# 创建时间:2024年7月16日21:47:43
# 最后修改时间:2024年7月16日22:16:54
# =============================================
class_name SVGRect extends SVGShape# ====================== 属性 ======================# ---------------------- 位置 ----------------------
# 矩形左上角x坐标
var x:String:set(val):attrs["x"] = valget:return attrs["x"]# 矩形左上角y坐标
var y:String:set(val):attrs["y"] = valget:return attrs["y"]
# ---------------------- 尺寸 ----------------------
# 矩形宽度
var width:String:set(val):attrs["width"] = valget:return attrs["width"]# 矩形高度
var height:String:set(val):attrs["height"] = valget:return attrs["height"]
# ---------------------- 圆角 ----------------------
# 矩形圆角半径(水平方向)
var rx:String:set(val):attrs["rx"] = valget:return attrs["rx"]# 矩形圆角半径(垂直方向)
var ry:String:set(val):attrs["ry"] = valget:return attrs["ry"]# ====================== 初始化 ======================
func _init() -> void:super() # 初始化父类,否则无法继承相关的默认属性tag_name = "rect"# HTML标签通用属性attrs["x"] = "0"attrs["y"] = "0"attrs["width"] = "100"attrs["height"] = "100"attrs["rx"] = "0"attrs["ry"] = "0"
测试代码:
@tool
extends EditorScriptfunc _run() -> void:var svg = SVG.new() # 创建SVG标签var rect = SVGRect.new() # 创建矩形svg.append(rect)print(svg)
获得的SVG代码:
<svg version="1.1" baseProfile="full" width="200" height="200" xmlns="http://www.w3.org/2000/svg" >
<rect fill="#fff" stroke="#000" stroke-width="1" x="0" y="0" width="100" height="100" rx="0" ry="0" ></rect></svg>
SVG预览如下:

@tool
extends EditorScriptfunc _run() -> void:var svg = SVG.new(100,200) # 创建SVG标签var rect = SVGRect.new(0,0,100,200,12,12) # 创建矩形# 设定矩形属性rect.stroke = "red"rect.stroke_width = 2# 将矩形追加到svg内容的末尾svg.append(rect)print(svg) # 打印SVG代码
获得的SVG代码:
<svg version="1.1" baseProfile="full" width="100" height="200" xmlns="http://www.w3.org/2000/svg" >
<rect fill="#fff" stroke="red" stroke-width="2" x="0" y="0" width="100" height="200" rx="12" ry="12" ></rect></svg>
预览如下:

总结
- 纯面向对象的设计,尤其是基于继承的多个类组成的体系在某些方面还是很有用的。比如HTML、XML、SVG这样本身就很明显的继承设计。
- 基于继承或许会写出一堆类,但是辅以类图和文档,还是能让别人比较轻松的理解和掌握的。
- 静态函数库则完全是一个很差的设计形式,很有可能会被一堆函数搞得特别臃肿。
- 适当的用类和类的继承形式取代静态函数库形式是我正在做的尝试
相关文章:
【Godot4.2】MLTag类:HTML、XML通用标签类
概述 HTML和XML采用类似的标签形式。 之前在Godot中以函数库形式实现了网页标签和内容生成。能用,但是缺点也很明显。函数之间没有从属关系,但是多有依赖,而且没有划分出各种对象和类型。 如果以完全的面向对象形式来设计标签类或者元素类…...
美式键盘 QWERTY 布局的起源
注:机翻,未校对。 The QWERTY Keyboard Is Tech’s Biggest Unsolved Mystery QWERTY 键盘是科技界最大的未解之谜 It’s on your computer keyboard and your smartphone screen: QWERTY, the first six letters of the top row of the standard keybo…...
【JavaEE】HTTP(2)
🤡🤡🤡个人主页🤡🤡🤡 🤡🤡🤡JavaEE专栏🤡🤡🤡 🤡🤡🤡下一篇文章:【JavaEE】HTTP协议(…...
LinuxShell编程2——shell搭建Discuzz论坛网站
目录 一、环境准备 ①准备一台虚拟机 ②初始化虚拟机 1、关闭防火墙 2、关闭selinux 3、配置yum源 4、修改主机名 二、搭建LAMP环境 ①安装httpd(阿帕奇apache)服务器 查看是否安装过httpd 启动httpd 设置开机启动 查看状态 安装网络工具 测试 ②安装…...
.NET MAUI开源架构_1.学习资源分享
最近需要开发Android的App,想预研下使用.NET开源架构.NET MAUI来开发App程序。因此网上搜索了下相关资料,现在把我查询的结果记录下,方便后面学习。 1.官方文档 1.1MAUI官方学习网站 .NET Multi-Platform App UI 文档 - .NET MAUI | Micro…...
Unsloth 微调 Llama 3
本文参考: https://colab.research.google.com/drive/135ced7oHytdxu3N2DNe1Z0kqjyYIkDXp 改编自:https://blog.csdn.net/qq_38628046/article/details/138906504 文章目录 一、项目说明安装相关依赖下载模型和数据 二、训练1、加载 model、tokenizer2、…...
热修复的原理
热修复的原理 水一篇哈,完事儿后删掉热修复的原理 水一篇哈,完事儿后删掉 热修复的原理 Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,而他们加载类的时候都需要ClassLoader,…...
【对顶堆 优先队列】2102. 序列顺序查询
本文涉及知识点 对顶堆 优先队列 LeetCode 2102. 序列顺序查询 一个观光景点由它的名字 name 和景点评分 score 组成,其中 name 是所有观光景点中 唯一 的字符串,score 是一个整数。景点按照最好到最坏排序。景点评分 越高 ,这个景点越好。…...
Go 语言中的互斥锁 Mutex
Mutex 是一种互斥锁,名称来自 mutual exclusion,是一种用于控制多线程对共享资源的竞争访问的同步机制。在有的编程语言中,也将其称为锁(lock)。当一个线程获取互斥锁时,它将阻止其他线程对该资源的访问,直到该线程释放锁。这可以防止多个线程对共享资源进行冲突访问,从而…...
CSS 中的 ::before 和 ::after 伪元素
目录 一、CSS 伪元素 二、::before ::after 介绍 1、::before 2、::after 3、content 常用属性值 三、::before ::after 应用场景 1、设置统一字符 2、通过背景添加图片 3、添加装饰线 4、右侧展开箭头 5、对话框小三角 6、插入icon图标 一、CSS 伪元素 CSS伪元…...
JuiceFS缓存特性
缓存 对于一个由对象存储和数据库组合驱动的文件系统,缓存是本地客户端与远端服务之间高效交互的重要纽带。读写的数据可以提前或者异步载入缓存,再由客户端在后台与远端服务交互执行异步上传或预取数据。相比直接与远端服务交互,采用缓存技…...
R语言实现SVM算法——分类与回归
### 11.6 基于支持向量机进行类别预测 ### # 构建数据子集 X <- iris[iris$Species! virginica,2:3] # 自变量:Sepal.Width, Petal.Length y <- iris[iris$Species ! virginica,Species] # 因变量 plot(X,col y,pch as.numeric(y)15,cex 1.5) # 绘制散点图…...
React@16.x(57)Redux@4.x(6)- 实现 bindActionCreators
目录 1,分析1,直接传入函数2,传入对象 2,实现 1,分析 一般情况下,action 并不是一个写死的对象,而是通过函数来获取。 而 bindActionCreators 的作用:为了更方便的使用创建 action…...
【深度学习入门篇 ⑦】PyTorch池化层
【🍊易编橙:一个帮助编程小伙伴少走弯路的终身成长社群🍊】 大家好,我是小森( ﹡ˆoˆ﹡ ) ! 易编橙终身成长社群创始团队嘉宾,橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官…...
【Pytorch】数据集的加载和处理(一)
Pytorch torchvision 包提供了很多常用数据集 数据按照用途一般分为三组:训练(train)、验证(validation)和测试(test)。使用训练数据集来训练模型,使用验证数据集跟踪模型在训练期间…...
论文翻译:Explainability for Large Language Models: A Survey
https://arxiv.org/pdf/2309.01029 目录 可解释性在大型语言模型中:一项调查摘要1 引言2 LLMs的训练范式2.1 传统微调范式2.2 提示范式 3 传统微调范式的解释3.1 局部解释3.1.1 基于特征归因的解释3.1.2 基于注意力的解释3.1.3 基于示例的解释 3.2 全局解释3.2.1 基…...
38 IRF+链路聚合+ACL+NAT组网架构
38 IRF+链路聚合+ACL+NAT组网架构 参考文献 34 IRF的实例-CSDN博客 35 解决单条链路故障问题-华三链路聚合-CSDN博客 36 最经典的ACL控制-CSDN博客 37 公私网转换技术-NAT基础-CSDN博客 32 华三vlan案例+STP-CSDN博客 一 网络架构...
【昇思学习打卡营打卡-第二十八天】MindNLP ChatGLM-6B StreamChat
MindNLP ChatGLM-6B StreamChat 本案例基于MindNLP和ChatGLM-6B实现一个聊天应用。 安装mindnlp pip install mindnlp安装mdtex2html pip install mdtex2html配置网络线路 export HF_ENDPOINThttps://hf-mirror.com代码开发 下载权重大约需要10分钟 from mindnlp.transf…...
前端打包部署后源码安全问题总结
随着现代Web应用越来越依赖于客户端技术,前端安全问题也随之突显。源码泄露是一个严重的安全问题,它不仅暴露了应用的内部逻辑和业务关键信息,还可能导致更广泛的安全风险。本文将详细介绍源码泄露的潜在风险,并提供一系列策略和工…...
扩展你的App:Xcode中App Extensions的深度指南
扩展你的App:Xcode中App Extensions的深度指南 在iOS开发的世界中,App Extensions提供了一种强大的方式,允许你的应用程序与系统和其他应用更紧密地集成。从今天起,我们将探索Xcode中App Extensions的神秘领域,学习如…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
