使用Kotlin做一个简单的HTML构造器

简介: 最近在学习Kotlin,看到了Kotlin Koans上面有一个HTML构造器的例子很有趣。今天来为大家介绍一下。最后实现的效果类似Groovy 标记模板或者Gradle脚本,就像下面(这是一个Groovy标记模板)这样的。

最近在学习Kotlin,看到了Kotlin Koans上面有一个HTML构造器的例子很有趣。今天来为大家介绍一下。最后实现的效果类似Groovy 标记模板或者Gradle脚本,就像下面(这是一个Groovy标记模板)这样的。

html(lang:'en') {                                                                   
    head {                                                                          
        meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')      
        title('My page')                                                            
    }                                                                               
    body {                                                                          
        p('This is an example of HTML contents')                                    
    }                                                                               
}   

基础语法

HTML构造器主要依靠Kotlin灵活的lambda语法。所以我们先来学习一下Kotlin的lambda表达式。如果学习过函数式编程的话,对lambda表达式应该很熟悉了。

首先,Kotlin中的lambda表达式可以赋给一个变量,然后我们可以“调用”该变量。这时候lambda表达式需要大括号包围起来。

    val lambda = { a: String -> println(a) }
    lambda("lambda表达式")

lambda表达式还可以用作函数参数。

fun doSomething(name: String, func: (e: String) -> Unit) {
    func(name)
}

Kotlin的lambda表达式还有一项特性,指定接收器。语法就是在lambda表达式的括号前添加接收器和点号.。在指定了接收器的lambda表达式内部,我们可以直接调用接收器对象上的任意方法,不需要额外的前缀。

fun buildString(build: StringBuilder.() -> Unit): String {
    val sb = StringBuilder()
    sb.build()
    return sb.toString()
}

然后我们就可以用非常简洁的语法来创建字符串了。需要注意这里的大括号中包围起来的是lambda表达式,它是buildString函数的参数而非函数体。这一点非常重要,在后面理解HTML构造器的时候,我们需要明确这一点。

    val str = buildString {
        for (i in 1..9) append("$i ")
        toString()
    }

Kotlin提供了一个apply函数,它的作用是直接调用给定的lambda表达式。上面这个例子使用apply方法改写如下。

fun buildStringWithApply() {
    val str = StringBuilder().apply {
        for (i in 1..9) append(i)
        toString()
    }
    println("字符串构造结果是:$str")
}

构造HTML

在了解了Kotlin的lambda语法之后,我们就可以创建HTML构造器了。

首先我们创建属性类、标签类和文本类。属性类包含属性名称和值,并重写了toString方法以便输出类似name="value"这样的字符串。标签类则是HTML标签的抽象,包括一组属性和子标签。这里属性和子标签都声明为了MutableList类型,它是Kotlin类库中的可变列表,存储内容是可以修改的。最后的文本类非常简单,直接返回文本。

class Attribute(var name: String, var value: String) {
    override fun toString(): String {
        return """$name="$value" """
    }
}

open class Tag(var name: String) {
    val children: MutableList<Tag> = ArrayList()
    val attributes: MutableList<Attribute> = ArrayList()
    override fun toString(): String {
        return """<$name${if (attributes.isEmpty()) "" else attributes.joinToString(prefix = " ", separator = " ")}>
${if (children.isEmpty()) "" else children.joinToString(separator = "\n")}
</$name> """
    }
}

class Text(val text: String) : Tag("") {
    override fun toString(): String = text
}

仅仅有这几个类并不够。我们还需要针对HTML实现一些具体的类。这些类非常简单,继承Tag类即可。这些类里面有一个类比较特殊,它就是TableElement。这个类同时是Thead和Tbody的父类。它的作用在下面会提到。

class Html : Tag("html")
class Body : Tag("body")
class Head : Tag("head")
class Script : Tag("script")
class H1 : Tag("h1")
class Table : Tag("table")
open class TableElement(name: String) : Tag(name)
class Thead : TableElement("thead")
class Tbody : TableElement("tbody")
class Th : Tag("th")
class Tr : Tag("tr")
class Td : Tag("td")
class P : Tag("p")

然后我们需要几个工具函数。doInit函数接受一个标签和一个lambda表达式,作用是调用该lambda表达式并将给定的标签添加到子标签列表中,返回的仍然是这个标签,方便后面链式调用。set函数更简单了,直接使用参数给定的名称和值设定标签的属性,返回值也是标签以便链式调用。这两个工具方法这么写的原因,等到我们完成了这个例子,实际显示效果的时候就可以看到了。

fun <T : Tag> Tag.doInit(tag: T, init: T.() -> Unit): T {
    tag.init()
    children.add(tag)
    return tag
}

fun <T : Tag> T.set(name: String, value: String?): T {
    if (value != null) {
        attributes.add(Attribute(name, value))
    }
    return this
}

最后是一组扩展方法。大部分方法都相同,我们先看看html方法 。它接受一个额外参数lang,作为html标签的属性;另一个参数是lambda表达式,由apply方法调用来初始化。由于我们的工具方法返回标签本身,所以这里可以链式调用多个方法。

剩下的方法基本一样,我们以table方法为例。table方法是Body上的扩展方法,也就是说table方法只能在Body上调用。table方法上的lambda表达式使用Table类作为接收器init: Table.() -> Unit。这里接收器的类型实际上就是init参数lambda表达式的上下文。doInit工具方法中,子元素被添加到的标签正是这里定义的上下文。因为tr标签既可以在thead标签中使用,也可以在tbody标签中使用。所以我们需要添加一个TableElement类,让这两个类继承它。这样HTML标签才能正常生成。

fun html(lang: String = "en", init: Html.() -> Unit): Html = Html().apply(init).set("lang", lang)
fun Html.head(init: Head.() -> Unit) = doInit(Head(), init)
fun Html.body(init: Body.() -> Unit) = doInit(Body(), init)
fun Body.h1(init: H1.() -> Unit) = doInit(H1(), init)
fun Head.script(init: Script.() -> Unit) = doInit(Script(), init)
fun Body.p(init: P.() -> Unit) = doInit(P(), init)
fun Table.thead(init: Thead.() -> Unit) = doInit(Thead(), init)
fun Table.tbody(init: Tbody.() -> Unit) = doInit(Tbody(), init)
fun Body.table(init: Table.() -> Unit) = doInit(Table(), init)
fun TableElement.tr(init: Tr.() -> Unit) = doInit(Tr(), init)
fun Tr.th(init: Th.() -> Unit) = doInit(Th(), init)
fun Tr.td(init: Td.() -> Unit) = doInit(Td(), init)
fun Tag.text(s: Any?) = doInit(Text(s.toString()), {})

到此为止HTML构造器已经准备就绪了。我们来实际看看效果。可以看到这里的语法非常奇怪,甚至都不像代码,但是它确确实实是标准的Kotlin代码。

val text = html(lang = "zh") {
    head {
        script {
            text("alert('123')")
        }
    }
    body {
        h1 {
            text("Hello")
        }
        table {
            thead {
                tr {
                    th { text("name") }
                    th { text("age") }
                }
            }
            tbody {
                tr {
                    td { text("yitian") }
                    td { text("24") }
                }
                tr {
                    td { text("liu6") }
                    td { text("16") }
                }
            }
        }
        p {
            text("This is some words")
        }
    }
}
println("html构造器使用实例:\n$text")

其实也很好理解,只要我们为这些方法添加小括号即可。

html({
    head({.......)}
    body({.......)}
})

这只是一个小例子。如果技术够硬的话,你甚至可以自己做一个脚本语言或者其他什么东西。当然现在已经有项目开始使用这种语法了,例如Kara Web框架视图以及用Kotlin写Gradle脚本

相关文章
|
Kotlin
Kotlin 之Html
Kotlin 之Html
151 0
|
前端开发 Kotlin
想使用 Kotlin DSL for HTML 来这样写前端的代码码?
private fun renderBooks(data: Array) { val books = document.create.
1119 0
|
Web App开发 JavaScript
使用Kotlin 和 Jsoup库实现一个极简的HTML Parser库
当我们有了一个网页的源代码HTML,这个时候我们很想像在JavaScript中的DOM API一样操作解析这个页面的元素。 比如说,百度首页,我们在浏览器console中执行js document.getElementsByTagName("title")[0].innerHTML 我们会得到 百度一下,你就知道 我们使用后端代码怎样搞呢? 有很多API库。
1269 0
|
8天前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
27 4
|
1月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
67 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
28天前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
26 8
|
1月前
|
安全 Java Android开发
探索Android应用开发中的Kotlin语言
【7月更文挑战第19天】在移动应用开发的浩瀚宇宙中,Kotlin这颗新星以其简洁、安全与现代化的特性,正迅速在Android开发者之间获得青睐。从基本的语法结构到高级的编程技巧,本文将引导读者穿梭于Kotlin的世界,揭示其如何优化Android应用的开发流程并提升代码的可读性与维护性。我们将一起探究Kotlin的核心概念,包括它的数据类型、类和接口、可见性修饰符以及高阶函数等特性,并了解这些特性是如何在实际项目中得以应用的。无论你是刚入门的新手还是寻求进阶的开发者,这篇文章都将为你提供有价值的见解和实践指导。
|
1月前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
29 6
|
1月前
|
存储 前端开发 测试技术
Android Kotlin中使用 LiveData、ViewModel快速实现MVVM模式
使用Kotlin实现MVVM模式是Android开发的现代实践。该模式分离UI和业务逻辑,借助LiveData、ViewModel和DataBinding增强代码可维护性。步骤包括创建Model层处理数据,ViewModel层作为数据桥梁,以及View层展示UI。添加相关依赖后,Model类存储数据,ViewModel类通过LiveData管理变化,而View层使用DataBinding实时更新UI。这种架构提升代码可测试性和模块化。
99 2
|
1月前
|
Android开发 Kotlin
Android面试题之kotlin中怎么限制一个函数参数的取值范围和取值类型等
在Kotlin中,限制函数参数可通过类型系统、泛型、条件检查、数据类、密封类和注解实现。例如,使用枚举限制参数为特定值,泛型约束确保参数为Number子类,条件检查如`require`确保参数在特定范围内,数据类封装可添加验证,密封类限制为一组预定义值,注解结合第三方库如Bean Validation进行校验。
37 6

热门文章

最新文章