Не могу понять как реализовать функцию attributes {"one" to "two"}

Рейтинг: 0Ответов: 1Опубликовано: 18.05.2023
open class Tag(val name: String, val value: String)
{
    var children: MutableList<Tag> = mutableListOf()
    fun add(tag: Tag) = children.add(tag)

    override fun toString(): String = "<${name}>${value}${children.joinToString (separator = " ")}</${name}>"

}

class TABLE(value: String) : Tag("TABLE", value)
class TR(value: String) : Tag("TR", value)
class TD(value: String) : Tag("TD", value)

open class TagBuilder {
    var value: String = ""

    var tags: MutableList<Tag> = mutableListOf()

    operator fun String.unaryPlus()
    {
        value = this
    }
}

class TDBuilder : TagBuilder()
{
    fun build() : TD = TD(value).apply {
        tags.forEach{ add (it) }
    }
}

class TRBuilder : TagBuilder()
{

    fun td(block: TDBuilder.() -> Unit)
    {
        val t = TDBuilder().apply(block).build()
        tags.add(t)
    }
    fun build() : TR = TR(value).apply {
        tags.forEach{ add (it) }
    }
}

class TableBuilder : TagBuilder() {
    fun tr(block: TRBuilder.() -> Unit)
    {
        val t = TRBuilder().apply(block).build()
        tags.add(t)
    }

    fun build() : TABLE = TABLE(value).apply {
        tags.forEach{ add (it) }
    }
}

fun table(block: TableBuilder.() -> Unit) : TABLE = TableBuilder().apply(block).build()

fun main()
{
    val html = table {
        tr {
            attributes {
                "one" to "two"
                "name" to "anders"
            }
            td {
                +"1"
            }
        }
    }

    println(html)
}

Вывод должен быть таким

<table><tr name='anders'><td>1</td></tr></table>

Пытался разными способами и через отдельный класс и через список, но кажется чего то не понимаю.

Ответы

▲ 0

Вкратце:

  1. Нужна функция attributes для билдера тэга (чтобы можно было вызывать ее внутри блоков тегов)
  2. Нужен новый класс-контекст (билдер) для этой функции (чтобы внутри него можно было вызвать будущую функцию to)
  3. В этом контексте нужно переопределить инфиксную функцию to, чтобы она добавляла свои аргументы в контекст
  4. При вызове build билдера атрибутов собирать их в одну строку в виде "tag='value1' tag2='value2'".
  5. Ну и вызывать build из функции attributes из первого пункта.
  6. Сами атрибуты в виде строки нужно передать в конструктор тэга и его потомков, и потом в его методе toString отобразить после имени.

Примерная реализация, все более-менее по аналогии с тем что у вас уже есть:

open class Tag(val name: String, val attributes: String, val value: String)
{
    var children: MutableList<Tag> = mutableListOf()
    fun add(tag: Tag) = children.add(tag)

    override fun toString(): String = "<${name}${attributes}>${value}${children.joinToString (separator = " ")}</${name}>"

}

class TABLE(attributes: String, value: String) : Tag("TABLE", attributes, value)
class TR(attributes: String, value: String) : Tag("TR", attributes, value)
class TD(attributes: String, value: String) : Tag("TD", attributes, value)

open class TagBuilder {
    var value: String = ""
    var attributes: String = ""

    var tags: MutableList<Tag> = mutableListOf()

    operator fun String.unaryPlus()
    {
        value = this
    }

    fun attributes(block: AttributesBuilder.() -> Unit) {
        attributes = AttributesBuilder().apply(block).build()
    }
}

class AttributesBuilder {
    private val attributes: MutableList<Pair<String, String>> = mutableListOf()

    infix fun String.to(that: String): Pair<String, String> {
        val pair = Pair(this, that)
        attributes.add(pair)
        return pair
    }

    fun build(): String = attributes.joinToString(separator = "") {
        " ${it.first}='${it.second}'"
    }
}

class TDBuilder : TagBuilder()
{
    fun build() : TD = TD(attributes, value).apply {
        tags.forEach{ add (it) }
    }
}

class TRBuilder : TagBuilder()
{

    fun td(block: TDBuilder.() -> Unit)
    {
        val t = TDBuilder().apply(block).build()
        tags.add(t)
    }
    fun build() : TR = TR(attributes, value).apply {
        tags.forEach{ add (it) }
    }
}

class TableBuilder : TagBuilder() {
    fun tr(block: TRBuilder.() -> Unit)
    {
        val t = TRBuilder().apply(block).build()
        tags.add(t)
    }

    fun build() : TABLE = TABLE(attributes, value).apply {
        tags.forEach{ add (it) }
    }
}

fun table(block: TableBuilder.() -> Unit) : TABLE = TableBuilder().apply(block).build()

fun main()
{
    val html = table {
        tr {
            attributes {
                "one" to "two"
                "name" to "anders"
            }
            td {
                +"1"
            }
        }
    }

    println(html)
}

Вывод:

<TABLE><TR name='anders'><TD>1</TD></TR></TABLE>

https://pl.kotl.in/dIM81X0Rw