iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0

昨天看過了 call.respondHtml 的實作之後,今天我們來看看

val name = "Ktor"
head {
	title {
		+name
	}
}

裡面的 headtitle 等等函數是怎麼實作的。

我們先來看看 head

@HtmlTagMarker
inline fun HTML.head(crossinline block : HEAD.() -> Unit = {}) : Unit = HEAD(emptyMap, consumer).visit(block)

這邊宣告了 HEAD 類別

@Suppress("unused")
open class HEAD(initialAttributes : Map<String, String>, override val consumer : TagConsumer<*>) : HTMLTag("head", consumer, initialAttributes, null, false, false), HtmlHeadTag

這邊可以看到 HEAD 繼承了 HTMLTag 類別

open class HTMLTag(
    override val tagName: String,
    override val consumer: TagConsumer<*>,
    initialAttributes: Map<String, String>,
    override val namespace: String? = null,
    override val inlineTag: Boolean,
    override val emptyTag: Boolean
) : Tag {

    override val attributes: DelegatingMap = DelegatingMap(initialAttributes, this) { consumer }

    override val attributesEntries: Collection<Map.Entry<String, String>>
        get() = attributes.immutableEntries
}

以及實作了 HtmlHeadTag 介面

interface HtmlHeadTag : CommonAttributeGroupFacade, MetaDataContent {
}

HEAD 裡面覆寫(override)了四個函數,我們一個個看

首先是 Entities.unaryPlus()

override operator fun Entities.unaryPlus() : Unit {
	@Suppress("DEPRECATION") entity(this)
}

這邊覆蓋的是 + (unaryPlus)這個操作子,往下呼叫的是 entity

override fun entity(e : Entities) : Unit {
	super<HTMLTag>.entity(e)
}

這個 entity 實作則是

fun entity(e: Entities) {
	consumer.onTagContentEntity(e)
}

到這邊,我們終於確定了 Entities.unaryPlus() 實際會做什麼:呼叫 TagConsumer 產生這個標籤裡面的 Content

釐清這段邏輯之後,String.unaryPlus() 看起來也就很簡單了

@Deprecated("This tag most likely doesn't support text content or requires unsafe content (try unsafe {}")
override operator fun String.unaryPlus() : Unit {
	@Suppress("DEPRECATION") text(this)
}
@Deprecated("This tag most likely doesn't support text content or requires unsafe content (try unsafe {}")
override fun text(s : String) : Unit {
	super<HTMLTag>.text(s)
}
fun text(s: String) {
	consumer.onTagContent(s)
}

以上將 HEAD 定義完畢,接著我們看看 visit

inline fun <T : Tag> T.visit(crossinline block: T.() -> Unit) = visitTag { block() }

visitTag 就是之前看過的

inline fun <T : Tag> T.visitTag(block: T.() -> Unit) {
    consumer.onTagStart(this)
    this.block()
    consumer.onTagEnd(this)
}

到這邊看完了 head 的內容,往下看 title 的實作

/**
 * Document title
 */
@HtmlTagMarker
inline fun MetaDataContent.title(crossinline block : TITLE.() -> Unit = {}) : Unit = TITLE(emptyMap, consumer).visit(block)

TITLE 類別的實作則是

@Suppress("unused")
open class TITLE(initialAttributes : Map<String, String>, override val consumer : TagConsumer<*>) : HTMLTag("title", consumer, initialAttributes, null, false, false), HtmlHeadTag {  

}

到這邊,我們應該發現了一個固定的模式。

這些函數最後所建立的內容,其實都是某個 HTMLTag 物件。建立了物件之後,由於他們都繼承了 HTMLTag 內所建立的函數,所以他們可以透過這些函數,產出對應的 HTML 內容。

使用這樣的語法,我們可以將產出內容的邏輯切換,改變成所使用類別的邏輯切換:你需要生成 head 內容就使用 HEAD 物件,需要生成 title 內容則使用 TITLE 物件,避免在控制時需要撰寫一個巨大的 switch-case 來生成內容。

另外,這種寫法利用 Kotlin 專屬的語法,如果要生成內容時,可以讓開發端用如此簡潔的寫法

val name = "Ktor"
head {
	title {
		+name
	}
}

生成以下的內容

<head>
    <title>Ktor</title>
</head>

今天我們就先看到這邊,明天我們一起來看

body {
	h1 {
		+"Hello from $name!"
	}
}

這段背後的實作


上一篇
Day 09:生成 HTML 內容的 call.respondHtml()
下一篇
Day 11:生成 HTML Body 和 H1 標籤的 body 與 h1
系列文
深入解析 Kotlin 專案 Ktor 的程式碼,探索 Ktor 的強大功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言