使用kotlin编写html dsl框架

DSL、Groovy DSL、Kotlin DSL 详解 特性Groovy DSLKotlin DSL本质利用 Groovy动态语言特性构建的 DSL利用 Kotlin静态语言特性构建的类型安全 DSL类型系统动态类型静态类型IDE 支持较弱(补全、导航、重构、错误检查)极佳(得益于静态类型)性能运行时解释,通常较慢编译为字节码,通常更快灵活性极高,可以实现非常动态和简洁的语法高,但在动态魔法方面不如 Groovy学习曲线需要学习 Groovy 的独特语法和 DSL 技巧如果你会 Kotlin,就很容易理解其 DSL 机制经典案例。 阅读详情

前排提醒,这个框架就是我写着玩的,如果您已经会使用vue或其他前端框架,这篇文章可能对您没有什么意义。即使您不会如上提到的框架,也不要对该框架报有过高的期待,该框架更多的是,我自己的自娱自乐。

这里还要提醒一下,该框架没有实现对css和js的支持,就是一个生成html代码的工具。

前言

为什么我要写这个玩意出来?因为我有时想用网页写一写游戏评测文章,而用html编写就可以比较方便地通过浏览器在不同网页之间跳转。如果编写markdown文章或其他方式,就比较麻烦了。
而我作为一名安卓开发者,对前端开发并不熟悉,而且对网页的要求也不高。只需要可显示不同样式的文字、图片和链接等功能,所以就不想花精力去学习前端框架。当然了,以后如果工作需要,那去学习也不可厚非。
编写该框架确实有如上的原因,但还有一个原因:我想试试看我能不能写出来。所以就经历了框架设计、改进框架不合理的代码等过程,最后有了现在代码。

使用和演示

上面说了那么多,接下来贴一下使用代码。

html {
    header {
        title("test")
    }
    body {
        div {
            a {
                href = "https://www.baidu.com"
                text = "baidu"
            }
            h1("这是h1")
            text("text")
            
        }
        text("text")
    }
}.toHtmlCode().also(::println)

// println
<html><header><title>test</title></header><body><div><a href="https://www.baidu.com">baidu</a><h1>这是h1</h1>text<img/></div>text</body></html>

可以看到,只需很简单的编写代码,就能够输出完整的html代码,而不需要手写很多标签代码。
再看看我使用该框架编写html的实际代码和最终效果。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

代码

上面代码看似简单,但实际上,调用的代码一点都不少,想看实现的代码,可以直接看这个github链接,接下来是代码解析

HtmlTag
首先,需要一个类来描述html代码,所以我就创建了HtmlTag,任何一个标签都是HtmlTag的实现类。
interface HtmlTag {
    fun getTagString(): String

    // 获取标签的属性,如id、width、height等
    // Pair的first就是属性的字符串,second就是属性的值,类型设计为Any是考虑到Int、Double这些类型。如果限制为String,那每次都需要手动调用一次toString,这样做其实挺麻烦的
    fun getAttributeList(): List<Pair<String, Any>>

    // 转换成html代码,每个标签做好自己的转换任务,由上级调用下级的该方法进行转换,最终形成一条调用链,这样就能非常方便地生成html代码
    fun toHtmlCode(): String
}

从上面的演示代码可以看到,还有html这个方法,这个方法获取到的就是一个HtmlRoot对象,看看该对象里面有什么代码

class HtmlRoot: HtmlTag {
    var header: HtmlHeaderRoot? = null
    var body: HtmlBodyRoot? = null

    override fun getTagString(): String = TAG

    override fun toHtmlCode(): String {
        return generateHtmlCode(listOfNotNull(header, body))
    }

    override fun getAttributeList(): List<Pair<String, Any>> = emptyList()

    companion object {
        const val TAG = "html"
    }
}

这里的generateHtmlCode是一个扩展方法,下面会提到,先把注意力放到其他地方。

HtmlBody

可以看到,该类里面,有header和body对象,header和body的代码类似,我就把body拿出来讲。
不过在看看body的代码之前,先了解一下body的基类。
HtmlBody

interface HtmlBody: HtmlTag

就一个非常简单的接口,所有和body有关的代码,都必须为该接口的实现类,包括HtmlBodyRoot本身。该类有两个直接实现类:HtmlBodySingle和HtmlBodyGroup。
HtmlBodySingle

abstract class HtmlBodySingle<T: HtmlBody>: HtmlBody {
    open var body: T? = null
}

HtmlBodyGroup

abstract class HtmlBodyGroup<T: HtmlBody>: HtmlBody {
    protected open var internalBodyList: MutableList<T> = ArrayList()

    open fun addHtmlBody(body: T) {
        internalBodyList.add(body)
    }

    open fun addAllHtmlBody(list: List<T>) {
        internalBodyList.addAll(list)
    }

    open fun removeHtmlBody(body: T) {
        internalBodyList.remove(body)
    }

    open fun clearBodyList() {
        internalBodyList.clear()
    }

    open fun getBodyList(): List<T> {
        return internalBodyList
    }
}

可以看到,single只能有一个body,而group可以有多个body。为什么要这样做?因为我考虑到有些标签只能有一个子标签,如a、h这些标签,而有一些则可以多个,如body、div等。
一开始,我考虑的是,将single作为一个特殊的group标签,做一些实现,保证开发者永远只能在list里面添加一个标签,就像android的ScrollView一样。但随后想了想,还是不要这样做,因为这样会增加开发者出错的几率。
上面还有泛型这个东西,我考虑的是,有一些标签的子标签类型不需要那么宽泛,只需某些特定的标签,如table、ul、ol等,所以我就加了一个泛型上去。
现在再看看body标签的代码

class HtmlBodyRoot: HtmlBodyGroup<HtmlBody>() {
    var style: HtmlStyleRoot? = null

    override fun getTagString(): String = TAG

    // 这里就是将style和bodyList转换成String,生成一个字符串List,传递给generateHtmlCodeByStringList方法去生成html代码
    override fun toHtmlCode(): String {
        val style = style?.toStyleCode()?.takeIf { it.isNotEmpty() } ?: ""
        return generateHtmlCodeByStringList(listOf(style, getBodyList().bodyTagListToString()))
    }

    // 我的印象里,body好像没有属性,所以我直接返回一个空List
    override fun getAttributeList(): List<Pair<String, Any>> = emptyList()

    companion object{
        const val TAG = "body"
    }
}

fun <T: HtmlBody> List<T>.bodyTagListToString() = joinToString("") { it.toHtmlCode() }

可以看到,body的代码也没什么,就是一些很普通的代码。
我贴出来的代码有不少调用generateHtmlCode这个方法,来看看该方法的实现代码。

fun HtmlTag.generateHtmlCode(): String {
    return generateHtmlCode("")
}

// 大部分body会调用这里的single和group的扩展方法,可以看到,这里的代码最终都会调用toHtmlCode方法
fun <T: HtmlBody> HtmlBodySingle<T>.generateHtmlCode(): String {
    return generateHtmlCode(body?.toHtmlCode() ?: "")
}

fun <T: HtmlBody> HtmlBodyGroup<T>.generateHtmlCode(): String {
    return generateHtmlCode(getBodyList().bodyTagListToString())
}

fun <T: HtmlHeader> HtmlHeaderGroup<T>.generateHtmlCode(): String {
    return generateHtmlCode(getHeaderList().headerTagListToString())
}

fun <T: HtmlTag> HtmlTag.generateHtmlCode(htmlList: List<T>): String {
    return generateHtmlCode(htmlList.tagListToString())

}

// HtmlBodyRoot就是调用这个方法生成html代码
fun HtmlTag.generateHtmlCodeByStringList(codeList: List<String>): String {
    // 这里也只是将String List调用joinToString转换成一个String而已
    return generateHtmlCode(codeList.joinToString(""))
}

// 所有代码最终都会调用该方法,可以直接看该方法的代码
fun HtmlTag.generateHtmlCode(value: String): String {
    // 这里就会调用HtmlTag的getAttributeList方法,将它们转换成first="second"这样的代码,并在转换后的字符串前面加一个空格。如果该List为空,就直接返回空字符串。
    // 为什么要加一个空格,因为如果不加空额,标签代码就会和属性代码贴在一起。如:<ahref=""/>
    val attributeString = getAttributeList().takeIf { it.isNotEmpty() }?.joinToString(" ") {
        "${it.first}=\"${it.second}\""
    }?.let { " $it" } ?: ""
    // 如果value为空,就生成闭标签的代码,否则就生成开标签的代码
    return if(value.isEmpty()) {
        "<${getTagString()}$attributeString/>"
    } else {
        buildString {
            // 配合上面的single和group的generateHtmlCode扩展方法,就不难理解
            // 将子body的代码包在自己的标签里面,而子body也会调用自己的子body包在自己的标签里面
            // 最后一层包一层,就可以输出一串复杂的html代码
            append("<${getTagString()}$attributeString>")
            append(value)
            append("</${getTagString()}>")
        }
    }
}

为什么要将它们作为扩展方法,而不是放到HtmlTag里面?因为这里面有一些方法不是每个实现类都用得上。如果将这些代码放到顶层接口里面,最终会导致每个实现类生成的class文件里面,有很多用不上的代码,所以将这些代码作为扩展方法是比较实际的。而且接口更多是起到定义规范的作用,而不是当工具类使用。

getAttributeList

再提一下getAttributeList的实现,拿a标签举例
HtmlBodyGeneralAttribute.kt

// 属性的根类是HtmlBodyGeneralAttribute,这里面包含了所有基础标签,不过我只是将开发中需要的属性加上去,后续如果还需要其他属性,可以自己加
interface HtmlBodyGeneralAttribute<T: HtmlBodyGeneralAttributeEntity> {
    // 为属性提供默认实现,这样做了之后,实现类就不用手动写这些代码了
    // 这里的attributeEntity就是存放这些属性的实体,如果在这里直接给这个字段编写get方法,每次调用该字段都会重写new一个对象
    // 所以只能交给实现类去new,但也仅仅需要编写这一行代码,所以我认为这没有什么负担
    val attributeEntity: T

    // 通过字段的形式来设置属性,这样外部用起来就比较方便,而不用去调用方法
    // 这些字段默认是否为null,就自己判断了,如果觉得不需要null,也可以将?去掉
    var id: String?
        get() = attributeEntity.id
        set(value) {
            attributeEntity.id = value
        }

    var width: String?
        get() = attributeEntity.width
        set(value) {
            attributeEntity.width = value
        }

    var height: String?
        get() = attributeEntity.height
        set(value) {
            attributeEntity.height = value
        }

    // 最后通过该方法生成一个Pair List
    // toPairByStringValue的代码已经放在下面了,该方法会判断String Value是否为空字符串,如果是,就烦恼会一个空的Pair对象
    // 这里调用listOfNotNull,所以空的Pair对象就会直接被过滤掉
    fun getAttributeList(): List<Pair<String, Any>> {
        return listOfNotNull(
            HtmlBodyAttribute.general.ID toPairByStringValue attributeEntity.id,
            HtmlBodyAttribute.general.WIDTH toPairByStringValue attributeEntity.width,
            HtmlBodyAttribute.general.HEIGHT toPairByStringValue attributeEntity.height,
        )
    }
}

open class HtmlBodyGeneralAttributeEntity {
    var id: String? = null
    var width: String? = null
    var height: String? = null
}

infix fun String.toPairByStringValue(value: String?): Pair<String, String>? {
    return value?.takeIf { it.isNotEmpty() }?.let { this to value }
}

HtmlBodyATag.kt

// 这个类用于存放a标签需要的属性,该类需要继承HtmlBodyGeneralAttribute,并提供attibuteEntity对象类型
// 所以就写一个a的attibuteEntity继承GenernalAttributeEntity
interface HtmlBodyAAttribute: HtmlBodyGeneralAttribute<HtmlBodyAAttributeEntity> {
    var href: String?
        get() = attributeEntity.href
        set(value) {
            attributeEntity.href = value
        }

    var target: String?
        get() = attributeEntity.target
        set(value) {
            attributeEntity.target = value
        }

    // 重点是这里,继承之后,需要重写该方法,将super的结果取出来,并放一个新的List,形成一个二维List
    // 最后再调用list的flatten方法, 将二维List变成一维
    // 当然了,如果觉得这种实现方法不太好,也可以自己换一种更好的实现方式
    override fun getAttributeList(): List<Pair<String, Any>> {
        val superList = super.getAttributeList()
        val currentList = listOfNotNull(
            HtmlBodyAttribute.a.HREF toPairByStringValue attributeEntity.href,
            HtmlBodyAttribute.a.TARGET toPairByStringValue attributeEntity.target,
        )
        return listOf(superList, currentList).flatten()
    }
}

class HtmlBodyAAttributeEntity: HtmlBodyGeneralAttributeEntity() {
    var href: String? = null
    var target: String? = null
}

// 实现HtmlBodyAAttribute接口
class HtmlBodyATag: HtmlBodySingle<HtmlBody>(), HtmlBodyAAttribute {
    // 在a标签里面,只需重写这个字段,其他的都不用做,就拥有了设置id、width、href等功能
    override val attributeEntity: HtmlBodyAAttributeEntity = HtmlBodyAAttributeEntity()

    override fun getTagString(): String = TAG

    override fun toHtmlCode(): String {
        return generateHtmlCode()
    }
    
    // 这里需要override是没办法的事情,因为HtmlTag本身也有一个名称一样的方法,并且没有提供实现
    // 这里调用super就可以将AAttribute的实现直接拿过来用,所以
    override fun getAttributeList(): List<Pair<String, Any>> {
        return super.getAttributeList()
    }

    companion object{
        const val TAG = "a"
    }
}
纯文本

从上面的HtmlBody、HtmlBodySinge和HtmlBodyGroup的代码可以看到,没有一个属性用来编写纯文本,但html想要编写纯文本的代码,只需在空白部分编写就可以显示出来。
鉴于这种情况,我编写了HtmlBodyTextTag来实现这个功能,代码非常简单

class HtmlBodyTextTag: HtmlBody {
    var text: String = ""

    override fun getTagString(): String = ""

    override fun toHtmlCode(): String {
        return text
    }

    override fun getAttributeList(): List<Pair<String, Any>> = emptyList()
}

如果某个标签需要纯文本的内容,就可以设置这样一个body

dsl代码

上面将body的基本架构都介绍完了,接下来写一下dsl的代码是怎么写的
htmlDSL.kt

inline fun html(action: HtmlRoot.() -> Unit): HtmlRoot {
    return HtmlRoot().also {
        it.action()
    }
}

inline fun HtmlRoot.header(action: HtmlHeaderRoot.() -> Unit): HtmlHeaderRoot {
    return HtmlHeaderRoot().also {
        it.action()
        header = it
    }
}

inline fun HtmlRoot.body(action: HtmlBodyRoot.() -> Unit): HtmlBodyRoot {
    return HtmlBodyRoot().also {
        it.action()
        body = it
    }
}

代码还是比较简单的,想要写注释,但不知道要写什么
有关body dsl的代码还是有点多的,所以拿一部分出来讲
htmlBodyGenenralDSL.kt

// 给single扩展方法之后,在single里面就可以直接调用a了,而不需要用body = getA这种麻烦的形式
inline fun HtmlBodySingle<HtmlBody>.a(action: HtmlBodyATag.() -> Unit) {
    getA.also {
        it.action()
        body = it
    }
}

// 由于HtmlBodyRoot也是一个HtmlBodyGroup对象,所以这样写了之后,就能为body添加一个a标签
// 返回类型:返回自己本身。为什么要这样做?因为返回自己之后,就能链式调用代码
// 比如写一个a标签之后,如果不想换行去写一个text标签,就可以直接在a标签的代码后面.去调用,否则需要手动写";"才能调用
inline fun HtmlBodyGroup<HtmlBody>.a(action: HtmlBodyATag.() -> Unit): HtmlBodyGroup<HtmlBody> {
    getA.also {
        it.action()
        addHtmlBody(it)
    }
    return this
}

htmlBodyGenenralGetDSL.kt

inline val getA: HtmlBodyATag
    get() = HtmlBodyATag()

给group扩展之后,就不只是body可以使用,像div、li、th、td这些,也都可以通过这种方法add一个body,使用起来非常方便。
从上面还能看到,有一个getA,为什么要这样做?
可以想象一下,如果外部需要new一个A标签的对象,那就需要记住A标签的对象名称,但名称又那么长,每次写起来都挺麻烦的。而如果用这种形式,就可以让开发者无需记住对象名称,需要使用时,只需在标签前面加个get就可以拿到对应的标签对象,这不是很方便吗?
大部分标签会提供get方法,并放到GetDSL里面。
text

inline fun HtmlBodySingle<HtmlBody>.text(text: String){
    getText.also {
        it.text = text
        body = it
    }
}

var HtmlBodySingle<HtmlBody>.text: String
    get() = (body as? HtmlBodyTextTag)?.text ?: ""
    set(value) {
        val body = body as? HtmlBodyTextTag ?: getText.also {
            body = it
        }
        body.text = value
    }

inline fun HtmlBodyGroup<HtmlBody>.text(text: String): HtmlBodyGroup<HtmlBody> {
    getText.also {
        it.text = text
        addHtmlBody(it)
    }
    return this
}

text除了提供2个方法,还为single提供了text这个字段,这样如果想要给某个标签设置text,就可以通过这种方式
dsl的代码基本都是a、text标签这种形式,其他代码都是大同小异,所以其他代码我就不贴出来了。

字数统计
最后补充一个字数统计的功能,如果要统计一个网页的字数,可以使用js进行统计,但该框架没有提供js相关的api,所以写起来有点麻烦,自然地,我也不打算用js去统计字数。
除了js,我还想到用正则表达式,将html的文本找出来,但试了几个正则表达式,我都没能将文本找出来。
最后就想到,直接提取body里面所有text对象,并获取text对象的文本内容,最后计算出字数。
fun HtmlRoot.getTextLength(): Int {
    // 通过递归调用,就可以获取到所有text对象,获取完成后,就可以通过text里面的text字段计算字数
    return body?.getTextBodyList()?.sumOf {
        it.text.length
    } ?: 0
}

fun HtmlBody.getTextBodyList(): List<HtmlBodyTextTag>? {
    return when(this) {
        is HtmlBodyTextTag -> {
            arrayListOf(this)
        }
        is HtmlBodySingle<*> -> {
            getTextBodyList()
        }
        is HtmlBodyGroup<*> -> {
            getTextBodyList()
        }
        else -> null
    }
}

fun HtmlBodyGroup<*>.getTextBodyList(): List<HtmlBodyTextTag>? {
    val list = ArrayList<HtmlBodyTextTag>()
    getBodyList().forEach {
        // 这里就会调用上面的HtmlBody的getTextBodyList方法
        // 如果获取到一个空对象,就不会addAll到list里面
        it.getTextBodyList()?.also(list::addAll)
    }
    // 如果list不为空,就返回该List,否则就返回null
    return list.takeIf { it.isNotEmpty() }
}

fun HtmlBodySingle<*>.getTextBodyList(): List<HtmlBodyTextTag>? {
    return body?.getTextBodyList()
}

我自己没有想出来如何用正则表达式统计字数,所以抱着试一试的想法问了chatGPT,想不到还真得到我想要的代码。而且比我想象中的简单

在这里插入图片描述

fun getTextByHtmlCode(html: String): String {
    // 移除 HTML 标签
    var text = html.replace("\\<.*?\\>".toRegex(), "")
    // 移除 HTML 转义字符
    text = text.replace("&.*?;".toRegex(), "")
    // 移除多余空格和换行符
    text = text.trim { it <= ' ' }.replace("[\\s\\t]+".toRegex(), " ")
    return text
}

可以看到,直接暴力匹配<>这个括号就行,不管里面有什么内容。而且我也用一段5000多文本的html页面测试过了,得到的结果时一样的,所以代码是不存在什么问题的。只不过执行效率我就不清楚了,我不知道正则表达式的替换效率是怎么样的。而我提供的代码,本质是递归调用,也好不到哪去。
从chatGPT发送的代码可以看到,最后还会将字符串转换成字符数组,这段在我看来是没必要的,所以我就去掉了。

后记

上面简单地讲解了该框架的使用方法和实现方式,其他代码麻烦看看github里面的代码。
由于该框架主要是为了方便自己,所以提供的标签和属性都不全,如果自己需要哪个标签,可以根据我编写的代码,自己做一套实现。模板代码已经写出来了,剩下的就是体力活了。

写几句没什么用的:
24年11月13日更新,已经学完html+css+js,学完之后才明白css和js要写的知识挺多的,而且要适配到这个项目里面也挺麻烦的。目前而言,用这个项目开发一些简单的网页是没问题,但如果是比较复杂的页面,肯定是不行的。这个项目主要是可以比较方便的在kotlin里面编写html代码。

P4 开发实践 — 软件 DSL 架构 P4 实现了一种 Programmable Match-Action Pipeline(流水线可编程)架构,作为 Pipeline,就是:开发者可以自定义 Match Fields、自定义 Action Type、从而实现自定义的 Match-Action Table,再由多张这样的 Tables 进而形成一条 Pipeline。 阅读详情

相关推荐

40、Ktor框架:HTML DSL、会话支持与客户端功能详解

本文详细介绍了Ktor框架的核心功能,包括HTML DSL的使用、会话支持机制以及客户端功能。通过示例展示了如何使用HTML DSL构建动态Web页面,如何利用Ktor进行会话管理以在请求之间持久化数据,以及如何使用HttpClient实现高效的HTTP通信。这些内容为使用Ktor框架开发高效、连接的客户端/服务器应用提供了全面的基础。

s8t9u0v1w的博客 94

引领Web开发新纪元:kotlinx.html - 构建HTML的优雅方式

[kotlinx.html](https://github.com/kotlin/kotlinx.html) 是一个由JetBrains官方支持的开源库,它为Kotlin开发者提供了一种声明式的Domain Specific Language(DSL),用于构建HTML。无论是针对Java虚拟机(JVM)还是JavaScript环境,这个库都能让编写和操作HTML变得简单而直观。通过结合Kotli...

gitblog_00067的博客 496

在 Kotlin 中创建 Node.js 服务器与 DSL 应用

本文详细介绍了如何在 Kotlin 中创建一个 Node.js 服务器,涵盖了使用 Kotlin 外部函数与 JavaScript 代码的互操作性、动态类型及 DSL 的使用,展示了如何利用 Kotlin 的类型安全特性来构建一个聊天应用程序。

weixin_35748962的博客 324

10天学会kotlin DAY9 高阶函数DSL 手撕HTML

使用纯代码 加 注释的方式,可以更快的理解源码 如果你喜欢,请点个赞,后期会不断的深入讲解先看一张图 二、使用 DSL 解析HTML 先来张图

半身风雪 1160

Kotlin实战应用:从Web到桌面应用的全解析

本文深入探讨了Kotlin语言在不同领域的应用,涵盖Web开发、移动应用、桌面应用等多方面。从天气预报Web应用到Node.js聊天应用,从REST API新闻订阅到桌面字典应用,每个章节通过实践案例展示了Kotlin在项目中的强大功能和灵活性。

weixin_42186015的博客 782

kotlinx.html:用于HTML的Kotlin DSL

kotlinx.html kotlinx.html库提供DSL,以在JVM和浏览器(或其他JavaScript引擎)上为 / 或DOM构建HTML,以更好地进行Web的。 开始吧 有关如何包括该库的详细信息,请参见页面。 DOM 您可以自然地使用JVM和JS构建DOM树 查看以JavaScript为目标的Kotlin的示例 window.setInterval({ val myDiv = document.create.div( " panel " ) { p { + " Here is " a( " https://kotlinlang.org " ) { + " official Kotlin site " } } } document.getElementById( " container " ) !! .appendChild(myDiv) document.getElementById( " container " ) !! .append {

《Kotlin实战》-第11章:DSL构建

《Kotlin实战》-第11章:DSL构建

crazyf2015的博客 2562

kotlin-html:用于HTML和CSS的Kotlin DSL

Kotlin HTML JVM使用 repositories { jcenter() } dependencies { // JVM version of the HTML library implementation( " dev.scottpierce.kotlin-html:kotlin-html-writer-jvm:0.7.24 " ) // If you are using Ktor, you can use this as well implementation( " dev.scottpierce.kotlin-html:kotlin-html-ktor:0.7.24 " ) } 多平台使用 repositories { jcenter() } kotlin { commonMain { dependencies { implementation( " dev.scottpierce.kotlin-html:kotlin-html-writer:0.7.24 " )

fritz2框架入门:如何用Kotlin轻松构建响应式Web应用?

fritz2是一个基于Kotlin的响应式Web应用框架,它利用协程和数据流(flows)帮助开发者轻松构建现代化的Web应用。本指南将带您快速了解fritz2的核心概念和入门步骤,让您用Kotlin开发Web应用变得简单高效。 ## 🚀 为什么选择fritz2构建响应式Web应用? 在传统的Web开发中,管理UI状态和数据流向往往复杂且容易出错。fritz2通过以下核心优势解决这些问题:

gitblog_00020的博客 357

安卓设备安全相关技术

当前安卓设备使用的加密方案是基于Soc中一块可写入的rom实现,称为熔丝,其结果不可逆。 1、secureboot 2、AVB(Android Verify Boot) 3、dm-verity(device mapper) 4、keymaster 5、crypto 6、rpmb(replay private memory block) 7、TEE(Trust execute environment) 8、SELinux .........

weixin_43797328的博客 4838

微型纵向加密在新能源场站中的实战部署与运维避坑指南

本文详细解析了微型纵向加密在新能源场站中的实战部署与运维技巧。通过国密算法SM4实现数据加密,确保新能源场站通信安全,同时提供非侵入式部署方案和运维避坑指南,帮助提升网络稳定性和运维效率。

weixin_30505225的博客 597

学霸带你游戏化 Kotlin DSL 提升代码可读性

Kotlin DSL 的重要性游戏《只狼:影逝二度》(Sekiro: Shadows Die Twice)展示了复杂的战斗机制和场景设计,而 Kotlin DSL 能有效简化这些复杂性的管理。通过 DSL,开发者可以以更自然的方式定义游戏元素,提高可读性与维护性。使用简短的函数名和链式调用,使 DSL 更加流畅,提升开发者的体验。DSL 的易读性与简洁性在《巫师 3:狂猎》(The Witcher 3: Wild Hunt)中,复杂的任务系统通过清晰的 DSL 表达出来,便于快速迭代和更新任务逻辑。

纽雪澳诺加海美德的博客 1170

安卓15_ROM修改定制化_____修改rom的前提条件 去除各种验证 安卓设备通用

通过博文了解💝💝💝 1-------💝💝💝了解安卓设备的启动流程与核心验证机制 2-------💝💝💝了解安卓设备修改rom中的各种验证机制以及对应的去除方法 3-------💝💝💝了解avb验证与dm验证的本质区别与对应的操作方式 4-------💝💝💝修改rom前的循序步骤 避免验证机制导致的不开机

小马哥的专栏 507

ktor_教程:使用Ktor在Kotlin中编写微服务-互联系统的多平台框架

ktor 重要要点 Ktor是由JetBrains创建和维护的OSS Apache 2项目。 它可用于创建异步连接的系统。 Ktor大量使用Kotlin功能,包括协程和语言结构。 这是低礼节,因为它只需要很少的代码和配置即可启动和运行系统。 Ktor是多平台的,可以在多种系统和服务器容器技术上运行。 什么是Ktor? Ktor(发音为Kay-tor)是使用K...

cunfu6353的博客 3263

delphi 调用deepseek r1 api 并流式返回结果

摘要:本文展示了一个使用Delphi调用DeepSeek API的代码示例,包含两种调用方式:一次性获取结果和流式返回结果。代码实现了完整的API请求处理,包括HTTP客户端设置、JSON数据处理和UI界面交互。其中流式调用通过TStreamReader处理服务器发送事件(SSE),实时更新到TRichEdit控件并保持滚动条位置。文章还提到界面美化优化,为Delphi开发者提供了调用AI服务的实用参考方案。(150字)

全粘工程师 495

Easy系列PLC远程IO模块的安装配置和地址访问(EtherCAT总线)

EtherCAT分布式时钟DC(SOEM源码解析)EtherCAT分布式时钟DC(SOEM源码解析)_soem dc同步-优快云博客文章浏览阅读1.2k次。分布式时钟(DC,Dsitributed Clock)可以使所有 EtherCAT设备使用相同的系统时间,从而控制各设备任务的同步执行。_soem dc同步1200PLC连接分布式IO组态编程应用(ES200SP固件版本不同解决方法)

RXXW_Dor的博客 175

Kunafa 项目常见问题解决方案

Kunafa 项目常见问题解决方案 项目基础介绍和主要编程语言 Kunafa 是一个易于使用的高级框架,专为前端 Web 开发而设计。它使用 Kotlin 作为主要的编程语言,旨在帮助开发者创建 Web 应用程序,而无需使用 HTML、CSS 或 JavaScript。Kunafa 提供了一个直观的 DSL(领域特定语言)来创建视图,并支持类型安全的 CSS DSL,以便完全控制视图的外观。此外,...

gitblog_00920的博客 634

YOLOv11改进 | 特殊场景改进篇 | 2024最新改进CPA-Enhancer链式思考网络(适用低照度、图像去雾、雨天、雪天)

本文给大家带来的改进机制是2024.3月份最新改进,由CPA-Enhancer: Chain-of-Thought Prompted Adaptive Enhancer for Object Detection under Unknown Degradations论文提出的CPA-Enhancer链式思考网络CPA-Enhancer通过引入链式思考提示机制,实现了对未知退化条件下图像的自适应增强。该方法的核心在于能够利用CoT提示对图像退化进行动态分析和适应,从而显著提升物体检测性能。

Snu77的博客 2014

ktor框架用到了netty吗_Ktor 入门

最近要使用 netty 作为消息推送,之前一直想着集成在 Springboot 中使用,但是转念一想, netty 本身就可以作为服务器,为什么不再开一个服务呢?于是就想到了使用 Ktor 构建一个服务。Ktor 是一个使用强大的 Kotlin 语言 在互联系统中构建异步 服务器 与客户端的框架。并且正在努力的向其他平台构建,比如 JavaScript , iOS 和 Android ,且逐步尝试...

weixin_40008870的博客 349

7、Python高光谱分析工具(PyHAT)简介

Python高光谱分析工具(PyHAT)是一个开源、跨平台的Python库及图形用户界面,用于行星光谱数据的可视化与分析。它支持多种高光谱传感器数据,如火星勘测轨道飞行器的CRISM、月船1号的M3以及SELENE Kaguya SP点光谱仪,提供预处理、衍生产品生成、统计学习和机器学习分析功能。PyHAT基于Pandas和NumPy构建,兼容Jupyter和QGIS环境,旨在解决现有光谱分析方法缺乏开源实现、难以探索性分析和算法分散的问题。其模块化架构涵盖数据输入/输出、通用光谱变换、测绘与点观测工具及高

aa123的博客 138

15、目标检测模型的评估指标与YOLO算法解析

本文详细解析了目标检测模型的评估指标,包括精确率、召回率、AP、mAP及IoU的定义与作用,并深入介绍了YOLO算法的核心原理与推理流程。从网格划分、锚框引入到边界框细化与后处理,全面阐述了YOLO如何实现快速准确的目标检测。同时总结了YOLO在智能安防、自动驾驶和工业检测等领域的应用,并展望了其未来在准确性提升、多类别支持和技术融合方面的发展趋势。

work3的博客 65

Kotlin系列——DSL

本文章已授权微信公众号郭霖(guolin_blog)转载。 什么是DSL? DSL是domin specific language的缩写,中文名叫做领域特定语言,指的是专注于某个应用程序领域的计算机语言,比如显示网页的HTML、用于数据库处理的SQL、用于检索或者替换文本的正则表达式,它们都是DSL。与DSL相对的就是GPL,GPL是General Purpose Language的简称,即通用编...

qq_20417381的博客 3754

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Android Gradle开发与应用 (八) :Kotlin DSL

本文介绍了Gradle Kotlin DSL相关的一些知识点

朱豪凯的博客 2950

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Kotlin DSL 学习

领域特定语言(domain-specific language),这个翻译不够直观,它应该叫“特定领域的语言”。字符串匹配网络请求视图树构建数据库操作…这些都是一个 App 项目中随处出现的场景,而我们可以用一个语言来全部处理它们,例如 Java、C++、Python,这些语言被称为领域通用语言(General Purpose Language, GPL),它就是 DSL 的对立概念,也是我们每天都在使用的东西。

RikkaTheWorld 1399

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Kotlin DSL 的 使用

Kotlin DSL 的强大功能 :Gradle 的配置文件 build.gradle (Groovy),以及前面我们涉及到的Gradle Script Kotlin(Kotlin)、Anko(Kotlin)等,都是 DSL。我们可以看出,使用DSL的编程风格,可以让程序更加简单干净、直观简洁。当然,我们也可以创建自己的 DSL。 Android 中使用下面这样的 嵌套DSL 风格的代码来替代 XML 式风格的视图文件 UI { // AnkoContext

pureChilder的博客 921

Kotlin | DSL

这里写目录标题KotlinDSL篇(一)前言一、What's the DSL?二、使用步骤1.引入库2.读入数据总结二、使用步骤1.引入库2.读入数据总结三级目录 Kotlin DSL篇(一) 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录KotlinDSL篇(一)前言一、What's the DSL?二、使用步骤1.引入库2.读入数据总结二、使用步骤

qq_37196748的博客 2840
上一篇: android EditText设置后缀
下一篇: Android accessibility开发笔记
杜壁奇
博客等级 码龄10年 10粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值