Kotlin学习笔记(五十六)领域特定语言DSL

/**
 * 领域特定语言DSL
 * 概念:只在特定领域使用的语言,如:HTML、Gradle、SQL等等
 * 特点:1.是计算机编程语言
 * 2.具有语言的表达能力
 * 3.有限的表达能力
 * 4.关注某个特定的领域语言
 */
interface Node { //节点接口
    fun render(): String
}

class MapDelegate(val map: MutableMap<String, String>) : 
   ReadWriteProperty<Any, String> { //map代理
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return map[property.name] ?: ""
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        map[property.name] = value
    }
}

/**
 * 此处的参数block表示一个(参数为Tag的拓展函数、无返回值的)函数
 */
fun html(block: Tag.() -> Unit): Tag { //简化html标签的函数
//    return Tag("html").apply { block(this) } //此返回值可直接使用Tag标签内的环境
//此返回值可直接使用Tag标签内的环境(效果与上一句注释掉的相同)
    return Tag("html").apply(block) 
}

fun Tag.head(block: Head.() -> Unit) { //简化head标签的函数
    return this@head + Head().apply(block) //此返回值可直接使用Head标签内的环境
}

fun Tag.body(block: Body.() -> Unit) { //简化body标签的函数
    return this@body +  Body().apply(block) //此返回值可直接使用Body标签内的环境
}

class StringNode(val content: String): Node { //简化标签中添加文字的函数
    override fun render() = content
}

class Head: Tag("head")

class Body: Tag("body") {
    var id by MapDelegate(properties)
    var `class` by MapDelegate(properties)
}

open class Tag(val name: String): Node { //标签类
    val children = ArrayList<Node>() //子节点列表

    val properties = HashMap<String, String>() //属性

    //字符串拓展方法,使用方式 "属性名"("属性值") ,如:"id"("htmlId")
    operator fun String.invoke(value: String) {
        properties[this] = value //this表示字符串本身,value表示构造器中传入的值
    }

    //字符串拓展方法,使用方式 "属性名"("属性值") ,如:"head"{ }
    //此处的参数block表示一个(参数为Tag的拓展函数、无返回值的)函数
    operator fun String.invoke(block: Tag.() -> Unit) {
        children.add(Tag(this).apply(block)) //使用apply调用block函数后返回Tag自身
    }

    //字符串拓展方法,一元运算符重载
    operator fun String.unaryPlus() {
        children.add(StringNode(this)) //将字符串节点添加到子节点列表
    }

    //加法重载,用于添加节点
    operator fun plus(node: Node) {
        children.add(node) //将节点添加到子节点列表
    }

    //<html id="htmlid" style=""><head></head> <body></body> </html>
    override fun render(): String {
        return StringBuilder()
                .append("<") //添加左尖括号
                .append(name) //添加标签的名字
                .let { stringBuilder -> //对StringBuilder进行html节点的设置
                    if (!this.properties.isEmpty()) { //当标签属性不为空
                        stringBuilder.append(" ") //添加空格
                        this.properties.forEach { //遍历属性
                            stringBuilder.append(it.key) //添加属性名
                                    .append("=\"") //添加等于号和左引号
                                    .append(it.value) //添加属性的值
                                    .append("\" ") //加添右引号和空格
                        }
                    }
                    stringBuilder //将设置完成后的stringBuilder作为返回值
                }
                .append(">") //添加右尖括号
                .let { stringBuilder ->  //对StringBuilder进行子节点的设置
                    children.map(Node::render) //将子节点转换成Node类型的列表
                            .map(stringBuilder::append) //用append方法把Node类型的列表连接起来
                    stringBuilder //将设置完成后的stringBuilder作为返回值
                }
                .append("</$name>") //添加右边标签信息
                .toString() //将stringBuilder转换成字符串
    }
}

fun main(args: Array<String>) {
    Tag("html").apply { //生成网页方法1
        properties["id"] = "HtmlId"
        children.add(Tag("head"))
    }.render().let(::println)

    html { //生成网页方法2,此方法直接在函数html()中返回了Tag("Html").apply
        properties["id"] = "HtmlId"
        children.add(Tag("head"))
    }.render().let(::println)


    html { //生成网页方法3,此方法用字符串的拓展函数简化了标签的设置
        "id"("HtmlId")

        "head" {
            "id"("headId")
        }

        body {
            id = "bodyId"
            `class` = "bodyClass"

            "a" {
                "href"("https://www.qq.com")
                +"腾讯网"
            }
        }
    }.render().let(::println)
}
运行结果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容