Skip to content

Instantly share code, notes, and snippets.

@HonmaMasaru
Last active November 3, 2022 09:43

Revisions

  1. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 15 additions and 8 deletions.
    23 changes: 15 additions & 8 deletions ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -59,7 +59,6 @@ extension HTMLElement {
    }

    // MARK: - resultBuilder

    /// HTMLを書き出すresultBuilder
    @resultBuilder struct HTMLBuilder {
    static func buildBlock(_ elements: HTMLElement...) -> String {
    @@ -76,7 +75,6 @@ extension HTMLElement {
    }

    // MARK: - 既存のstructの拡張

    extension String: Element {
    func render() -> String {
    self
    @@ -90,7 +88,6 @@ extension Int: Element {
    }

    // MARK: - タグ

    /// Doctype
    struct Doctype: HTMLElement {
    let tag = "!DOCTYPE html"
    @@ -100,6 +97,7 @@ struct Doctype: HTMLElement {
    // 属性と内部タグを無効にするために初期化とレンダーをオーバーライド
    init() {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}

    func render() -> String {
    "<!DOCTYPE html>"
    }
    @@ -140,13 +138,22 @@ struct Br: HTMLElement {
    var attribute: [String: String]?
    var innerHTML: [Element]?

    // 属性と内部タグを無効にするために初期化をオーバーライド
    // 内部タグを無効にするために初期化とレンダーをオーバーライド
    init() {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {
    self.attribute = attribute
    }

    func render() -> String {
    // 属性
    let a = attribute?.reduce(into: "") {
    $0 += $1.value.isEmpty ? " \($1.key)" : #" \#($1.key)="\#($1.value)""#
    } ?? ""
    return "<\(tag)\(a)>"
    }
    }

    // MARK: - Test

    // 外部で要素を作れる
    var p = P { "段落" }
    p.attribute = ["data-test1": "333"]
    @@ -168,10 +175,10 @@ p.attribute?["data-test2"] = "444"
    P() // オープンタグ
    P {} // クローズタグ
    Br()
    Br(["style": "color:red"]) { "無視される" }
    Br(["data-test3": ""]) { "内部タグは無視される" }
    }
    }
    }
    print(html())

    // <!DOCTYPE html><html><head><title>タイトル</title></head><body><p style="color:red">テキスト<p data-test1="333" data-test2="444">段落</p>1</p><p><p></p><br><br></body></html>
    // <!DOCTYPE html><html><head><title>タイトル</title></head><body><p style="color:red">テキスト<p data-test1="333" data-test2="444">段落</p>1</p><p><p></p><br><br data-test3></body></html>
  2. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -97,9 +97,12 @@ struct Doctype: HTMLElement {
    var attribute: [String: String]?
    var innerHTML: [Element]?

    // 属性と内部タグを無効にするために初期化をオーバーライド
    // 属性と内部タグを無効にするために初期化とレンダーをオーバーライド
    init() {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}
    func render() -> String {
    "<!DOCTYPE html>"
    }
    }

    struct Html: HTMLElement {
  3. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -97,7 +97,7 @@ struct Doctype: HTMLElement {
    var attribute: [String: String]?
    var innerHTML: [Element]?

    // 属性と内部タグを無効にするために初期化とレンダーをオーバーライド
    // 属性と内部タグを無効にするために初期化をオーバーライド
    init() {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}
    }
    @@ -137,7 +137,7 @@ struct Br: HTMLElement {
    var attribute: [String: String]?
    var innerHTML: [Element]?

    // 属性と内部タグを無効にするために初期化とレンダーをオーバーライド
    // 属性と内部タグを無効にするために初期化をオーバーライド
    init() {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}
    }
  4. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -89,7 +89,7 @@ extension Int: Element {
    }
    }

    // MARK: -
    // MARK: - タグ

    /// Doctype
    struct Doctype: HTMLElement {
  5. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -165,9 +165,10 @@ p.attribute?["data-test2"] = "444"
    P() // オープンタグ
    P {} // クローズタグ
    Br()
    Br(["style": "color:red"]) { "無視される" }
    }
    }
    }
    print(html())

    // <!DOCTYPE html><html><head><title>タイトル</title></head><body><p style="color:red">テキスト<br><p data-test2="444" data-test1="333">段落</p>1</p><p><p></p><br></body></html>
    // <!DOCTYPE html><html><head><title>タイトル</title></head><body><p style="color:red">テキスト<p data-test1="333" data-test2="444">段落</p>1</p><p><p></p><br><br></body></html>
  6. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 62 additions and 48 deletions.
    110 changes: 62 additions & 48 deletions ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -1,57 +1,81 @@

    import Foundation

    /// 要素
    protocol Element {
    /// HTMLを書き出すレンダー
    /// - Returns: HTML
    func render() -> String
    }

    // MARK: -

    @resultBuilder struct HTMLBuilder {
    static func buildBlock(_ elements: HTMLElement...) -> String {
    elements.reduce(into: "") { $0 += $1.render() }
    }
    }

    @resultBuilder struct ElementBuilder {
    static func buildBlock(_ elements: Element...) -> [Element] {
    elements
    }
    }

    // MARK: -

    /// HTML要素
    protocol HTMLElement: Element {
    /// タグ名
    var tag: String { get }

    /// 属性
    var attribute: [String: String]? { get set }

    /// タグ内部
    var innerHTML: [Element]? { get set }

    /// 初期化
    init()

    /// 初期化
    /// - Parameters:
    /// - attribute: 属性
    /// - innerHTML: タグ内部
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element])
    }

    extension HTMLElement {
    /// 初期化のデフォルト実装
    init(_ attribute: [String: String]? = nil, @ElementBuilder innerHTML: () -> [Element]) {
    self.init()
    self.innerHTML = innerHTML()
    self.attribute = attribute
    }

    /// レンダーのデフォルト実装
    func render() -> String {
    // 属性と内部タグが設定されていない場合はオープンタグ
    if attribute == nil, innerHTML == nil {
    return "<\(tag)>"
    }

    // 属性
    let a = attribute?.reduce(into: "") {
    $0 += $1.value.isEmpty ? " \($1.key)" : #" \#($1.key)="\#($1.value)""#
    } ?? ""

    // 内部タグ
    let i = innerHTML?.reduce(into: "") {
    $0 += $1.render()
    } ?? ""

    return "<\(tag)\(a)>\(i)</\(tag)>"
    }
    }

    // MARK: -
    // MARK: - resultBuilder

    /// HTMLを書き出すresultBuilder
    @resultBuilder struct HTMLBuilder {
    static func buildBlock(_ elements: HTMLElement...) -> String {
    elements.reduce(into: "") { $0 += $1.render() }
    }
    }

    /// 内部タグを取得するresultBuilder
    /// HTMLElementをネストさせるために使用
    @resultBuilder struct ElementBuilder {
    static func buildBlock(_ elements: Element...) -> [Element] {
    elements
    }
    }

    // MARK: - 既存のstructの拡張

    extension String: Element {
    func render() -> String {
    @@ -67,93 +91,83 @@ extension Int: Element {

    // MARK: -

    /// Doctype
    struct Doctype: HTMLElement {
    let tag = ""
    var innerText: String?
    var innerHTML: [Element]?
    let tag = "!DOCTYPE html"
    var attribute: [String: String]?
    var innerHTML: [Element]?

    // 属性と内部タグを無効にするために初期化とレンダーをオーバーライド
    init() {}

    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}

    func render() -> String {
    "<!DOCTYPE html>"
    }
    }

    struct Html: HTMLElement {
    let tag = "html"
    var innerText: String?
    var innerHTML: [Element]?
    var attribute: [String: String]?
    var innerHTML: [Element]?
    }

    struct Head: HTMLElement {
    let tag = "head"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Title: HTMLElement {
    let tag = "title"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Body: HTMLElement {
    let tag = "body"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct P: HTMLElement {
    let tag = "p"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Span: HTMLElement {
    let tag = "span"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Br: HTMLElement {
    let tag = "br"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?

    // 属性と内部タグを無効にするために初期化とレンダーをオーバーライド
    init() {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}
    }

    // MARK: - Test

    var span = Span { "スパン" }
    span.attribute = ["data-test1": "333"]
    span.attribute?["data-test2"] = "444"
    // 外部で要素を作れる
    var p = P { "段落" }
    p.attribute = ["data-test1": "333"]
    p.attribute?["data-test2"] = "444"

    // HTMLの作成
    @HTMLBuilder func html() -> String {
    Doctype()
    Html {
    Head {
    Title { "title" }
    Title { "タイトル" }
    }
    Body {
    P(["style": "color:red"]) {
    "テキスト"
    span
    1
    "テキスト" // String
    p
    1 // Int
    }
    P()
    P() // オープンタグ
    P {} // クローズタグ
    Br()
    }
    }
    }
    print(html())

    // <!DOCTYPE html><html><head><title>title</title></head><body><p style="color:red">テキスト<span data-test2="444" data-test1="333">スパン</span>1</p><p><br></body></html>
    // <!DOCTYPE html><html><head><title>タイトル</title></head><body><p style="color:red">テキスト<br><p data-test2="444" data-test1="333">段落</p>1</p><p><p></p><br></body></html>
  7. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 60 additions and 44 deletions.
    104 changes: 60 additions & 44 deletions ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@

    import Foundation

    protocol Element {
    @@ -6,21 +7,15 @@ protocol Element {

    // MARK: -

    @resultBuilder public struct HTMLBuilder {
    static func buildBlock(_ root: RootElement) -> String {
    root.render()
    }
    }

    @resultBuilder private struct ElementBuilder {
    static func buildBlock(_ element: Element...) -> [Element] {
    element
    @resultBuilder struct HTMLBuilder {
    static func buildBlock(_ elements: HTMLElement...) -> String {
    elements.reduce(into: "") { $0 += $1.render() }
    }
    }

    @resultBuilder private struct TextBuilder {
    static func buildBlock(_ text: String) -> String {
    text
    @resultBuilder struct ElementBuilder {
    static func buildBlock(_ elements: Element...) -> [Element] {
    elements
    }
    }

    @@ -29,12 +24,10 @@ protocol Element {
    protocol HTMLElement: Element {
    var tag: String { get }
    var attribute: [String: String]? { get set }
    var innerText: String? { get set }
    var innerHTML: [Element]? { get set }

    init()
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element])
    init(@TextBuilder innerText: () -> String)
    }

    extension HTMLElement {
    @@ -44,100 +37,123 @@ extension HTMLElement {
    self.attribute = attribute
    }

    init(@TextBuilder innerText: () -> String) {
    self.init()
    self.innerText = innerText()
    func render() -> String {
    if attribute == nil, innerHTML == nil {
    return "<\(tag)>"
    }
    let a = attribute?.reduce(into: "") {
    $0 += $1.value.isEmpty ? " \($1.key)" : #" \#($1.key)="\#($1.value)""#
    } ?? ""
    let i = innerHTML?.reduce(into: "") {
    $0 += $1.render()
    } ?? ""
    return "<\(tag)\(a)>\(i)</\(tag)>"
    }
    }

    // MARK: -

    extension String: Element {
    func render() -> String {
    if tag.isEmpty, let innerText {
    return innerText
    }
    let i: String
    if innerText?.isEmpty == false {
    i = innerText!
    } else {
    i = innerHTML?.reduce(into: "") { $0 += $1.render() } ?? ""
    }
    let a = attribute?.reduce(into: "") { $0 += #" \#($1.key)="\#($1.value)""# }
    return "<\(tag)\(a ?? "")>\(i)</\(tag)>"
    self
    }
    }

    protocol RootElement: HTMLElement {}
    extension Int: Element {
    func render() -> String {
    "\(self)"
    }
    }

    // MARK: -

    public struct Text: HTMLElement {
    var tag = ""
    var attribute: [String : String]?
    struct Doctype: HTMLElement {
    let tag = ""
    var innerText: String?
    var innerHTML: [Element]?
    var attribute: [String: String]?

    init() {}

    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}

    func render() -> String {
    "<!DOCTYPE html>"
    }
    }

    public struct Html: RootElement {
    struct Html: HTMLElement {
    let tag = "html"
    var innerText: String?
    var innerHTML: [Element]?
    var attribute: [String: String]?
    }

    public struct Head: HTMLElement {
    struct Head: HTMLElement {
    let tag = "head"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    public struct Title: HTMLElement {
    struct Title: HTMLElement {
    let tag = "title"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    public struct Body: HTMLElement {
    struct Body: HTMLElement {
    let tag = "body"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    public struct P: HTMLElement {
    struct P: HTMLElement {
    let tag = "p"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    public struct Span: HTMLElement {
    struct Span: HTMLElement {
    let tag = "span"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Br: HTMLElement {
    let tag = "br"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    // MARK: - Test

    var span = Span { "2" }
    var span = Span { "スパン" }
    span.attribute = ["data-test1": "333"]
    span.attribute?["data-test2"] = "444"

    @HTMLBuilder func html() -> String {
    Doctype()
    Html {
    Head {
    Title { "title" }
    }
    Body {
    P(["style": "color:red"]) {
    Text { "1" }
    "テキスト"
    span
    Text { "3" }
    1
    }
    P {}
    P()
    Br()
    }
    }
    }
    print(html())

    // <html><head><title>title</title></head><body><p style="color:red">1<span data-test2="444" data-test1="333">2</span>3</p><p></p></body></html>
    // <!DOCTYPE html><html><head><title>title</title></head><body><p style="color:red">テキスト<span data-test2="444" data-test1="333">スパン</span>1</p><p><br></body></html>
  8. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 51 additions and 31 deletions.
    82 changes: 51 additions & 31 deletions ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -6,113 +6,133 @@ protocol Element {

    // MARK: -

    @resultBuilder struct HTMLBuilder {
    @resultBuilder public struct HTMLBuilder {
    static func buildBlock(_ root: RootElement) -> String {
    root.render()
    }
    }

    @resultBuilder struct ElementBuilder {
    @resultBuilder private struct ElementBuilder {
    static func buildBlock(_ element: Element...) -> [Element] {
    element
    }
    }

    @resultBuilder private struct TextBuilder {
    static func buildBlock(_ text: String) -> String {
    text
    }
    }

    // MARK: -

    protocol HTMLElement: Element {
    var tag: String { get }
    var innerHTML: [Element]? { get set }
    var attribute: [String: String]? { get set }
    var innerText: String? { get set }
    var innerHTML: [Element]? { get set }

    init()
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element])
    init(@TextBuilder innerText: () -> String)
    }

    extension HTMLElement {
    init(_ attribute: [String: String]? = nil, @ElementBuilder innerHTML: () -> [Element]) {
    self.init()
    self.attribute = attribute
    self.innerHTML = innerHTML()
    self.attribute = attribute
    }


    init(@TextBuilder innerText: () -> String) {
    self.init()
    self.innerText = innerText()
    }

    func render() -> String {
    if tag.isEmpty, let innerText {
    return innerText
    }
    let i: String
    if innerText?.isEmpty == false {
    i = innerText!
    } else {
    i = innerHTML?.reduce(into: "") { $0 += $1.render() } ?? ""
    }
    let a = attribute?.reduce(into: "") { $0 += #" \#($1.key)="\#($1.value)""# }
    let i = innerHTML?.reduce(into: "") { $0 += $1.render() }
    return "<\(tag)\(a ?? "")>\(i ?? "")</\(tag)>"
    return "<\(tag)\(a ?? "")>\(i)</\(tag)>"
    }
    }

    protocol RootElement: HTMLElement {}

    // MARK: -

    struct Text: Element {
    var innerText = ""

    init(_ innerText: String) {
    self.innerText = innerText
    }

    func render() -> String {
    innerText
    }
    public struct Text: HTMLElement {
    var tag = ""
    var attribute: [String : String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Html: RootElement {
    public struct Html: RootElement {
    let tag = "html"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    var attribute: [String: String]?
    }

    struct Head: HTMLElement {
    public struct Head: HTMLElement {
    let tag = "head"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Title: HTMLElement {
    public struct Title: HTMLElement {
    let tag = "title"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Body: HTMLElement {
    public struct Body: HTMLElement {
    let tag = "body"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct P: HTMLElement {
    public struct P: HTMLElement {
    let tag = "p"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    struct Span: HTMLElement {
    public struct Span: HTMLElement {
    let tag = "span"
    var attribute: [String: String]?
    var innerText: String?
    var innerHTML: [Element]?
    }

    // MARK: -
    // MARK: - Test

    var span = Span { Text("2") }
    var span = Span { "2" }
    span.attribute = ["data-test1": "333"]
    span.attribute?["data-test2"] = "444"

    @HTMLBuilder func html() -> String {
    Html {
    Head {
    Title {
    Text("title")
    }
    Title { "title" }
    }
    Body {
    P(["style": "color:red"]) {
    Text("1")
    Text { "1" }
    span
    Text("3")
    Text { "3" }
    }
    P {}
    }
  9. HonmaMasaru revised this gist Nov 3, 2022. 1 changed file with 22 additions and 20 deletions.
    42 changes: 22 additions & 20 deletions ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -4,22 +4,38 @@ protocol Element {
    func render() -> String
    }

    // MARK: -

    @resultBuilder struct HTMLBuilder {
    static func buildBlock(_ root: RootElement) -> String {
    root.render()
    }
    }

    @resultBuilder struct ElementBuilder {
    static func buildBlock(_ element: Element...) -> [Element] {
    element
    }
    }

    // MARK: -

    protocol HTMLElement: Element {
    var tag: String { get }
    var innerHTML: [Element]? { get set }
    var attribute: [String: String]? { get set }

    init()
    init(attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]?)
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element])
    }

    extension HTMLElement {
    init(attribute: [String: String]? = nil, @ElementBuilder innerHTML: () -> [Element]?) {
    init(_ attribute: [String: String]? = nil, @ElementBuilder innerHTML: () -> [Element]) {
    self.init()
    self.attribute = attribute
    self.innerHTML = innerHTML()
    }

    func render() -> String {
    let a = attribute?.reduce(into: "") { $0 += #" \#($1.key)="\#($1.value)""# }
    let i = innerHTML?.reduce(into: "") { $0 += $1.render() }
    @@ -33,7 +49,7 @@ protocol RootElement: HTMLElement {}

    struct Text: Element {
    var innerText = ""

    init(_ innerText: String) {
    self.innerText = innerText
    }
    @@ -81,20 +97,6 @@ struct Span: HTMLElement {

    // MARK: -

    @resultBuilder struct HTMLBuilder {
    static func buildBlock(_ root: RootElement) -> String {
    root.render()
    }
    }

    @resultBuilder struct ElementBuilder {
    static func buildBlock(_ element: Element...) -> [Element] {
    element
    }
    }

    // MARK: -

    var span = Span { Text("2") }
    span.attribute = ["data-test1": "333"]
    span.attribute?["data-test2"] = "444"
    @@ -107,7 +109,7 @@ span.attribute?["data-test2"] = "444"
    }
    }
    Body {
    P(attribute: ["style": "color:red"]) {
    P(["style": "color:red"]) {
    Text("1")
    span
    Text("3")
  10. HonmaMasaru revised this gist Jun 12, 2021. No changes.
  11. HonmaMasaru created this gist Jun 2, 2021.
    121 changes: 121 additions & 0 deletions ResultBuilderDemo.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,121 @@
    import Foundation

    protocol Element {
    func render() -> String
    }

    protocol HTMLElement: Element {
    var tag: String { get }
    var innerHTML: [Element]? { get set }
    var attribute: [String: String]? { get set }

    init()
    init(attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]?)
    }

    extension HTMLElement {
    init(attribute: [String: String]? = nil, @ElementBuilder innerHTML: () -> [Element]?) {
    self.init()
    self.attribute = attribute
    self.innerHTML = innerHTML()
    }

    func render() -> String {
    let a = attribute?.reduce(into: "") { $0 += #" \#($1.key)="\#($1.value)""# }
    let i = innerHTML?.reduce(into: "") { $0 += $1.render() }
    return "<\(tag)\(a ?? "")>\(i ?? "")</\(tag)>"
    }
    }

    protocol RootElement: HTMLElement {}

    // MARK: -

    struct Text: Element {
    var innerText = ""

    init(_ innerText: String) {
    self.innerText = innerText
    }

    func render() -> String {
    innerText
    }
    }

    struct Html: RootElement {
    let tag = "html"
    var attribute: [String: String]?
    var innerHTML: [Element]?
    }

    struct Head: HTMLElement {
    let tag = "head"
    var attribute: [String: String]?
    var innerHTML: [Element]?
    }

    struct Title: HTMLElement {
    let tag = "title"
    var attribute: [String: String]?
    var innerHTML: [Element]?
    }

    struct Body: HTMLElement {
    let tag = "body"
    var attribute: [String: String]?
    var innerHTML: [Element]?
    }

    struct P: HTMLElement {
    let tag = "p"
    var attribute: [String: String]?
    var innerHTML: [Element]?
    }

    struct Span: HTMLElement {
    let tag = "span"
    var attribute: [String: String]?
    var innerHTML: [Element]?
    }

    // MARK: -

    @resultBuilder struct HTMLBuilder {
    static func buildBlock(_ root: RootElement) -> String {
    root.render()
    }
    }

    @resultBuilder struct ElementBuilder {
    static func buildBlock(_ element: Element...) -> [Element] {
    element
    }
    }

    // MARK: -

    var span = Span { Text("2") }
    span.attribute = ["data-test1": "333"]
    span.attribute?["data-test2"] = "444"

    @HTMLBuilder func html() -> String {
    Html {
    Head {
    Title {
    Text("title")
    }
    }
    Body {
    P(attribute: ["style": "color:red"]) {
    Text("1")
    span
    Text("3")
    }
    P {}
    }
    }
    }
    print(html())

    // <html><head><title>title</title></head><body><p style="color:red">1<span data-test2="444" data-test1="333">2</span>3</p><p></p></body></html>