Kotlin / kotlinx.html

Kotlin DSL for HTML
Apache License 2.0
1.61k stars 131 forks source link

JSDOMBuilder converts raw string contents to HTML tags #206

Open aSemy opened 1 year ago

aSemy commented 1 year ago

I want to display a Map<String, String> in an HTML page.

import kotlinx.browser.document
import kotlinx.html.*
import kotlinx.html.dom.*

fun main() {
  document.body!!.append.div {
    pre {
      code {
        unsafe {
          raw("""
            val map: Map<String, String> = mapOf(
              "key1" to "value1",
              "key2" to "value2",
            )
          """.trimIndent())
        }
      }
    }
  }

The code is not display 'raw' though, and the generic parameters are converted into HTML tags <string, string=""> - very strange!

<div>
  <pre>
    <code>
      val map: Map<string, string=""> = mapOf(
        "key1" to "value1",
        "key2" to "value2",
      )
      </string,>
    </code>
  </pre>
</div>

The code rendered in my browser does not match the raw string.

image

Expected result

If I don't use JSDOMBBuilder, then the HTML matches the raw content:

import kotlinx.html.*
import kotlinx.html.dom.createHTMLDocument
import kotlinx.html.stream.createHTML

fun main() {
  println(createHTML(prettyPrint = true).div {
    pre {
      code {
        unsafe {
          raw("""
            val map: Map<String, String> = mapOf(
              "key1" to "value1",
              "key2" to "value2",
            )
          """.trimIndent())
        }
      }
    }
  })
}
<div>
  <pre><code>val map: Map<String, String> = mapOf(
  "key1" to "value1",
  "key2" to "value2",
)</code></pre>
</div>

Compose Web

With Compose Web v1.2.2 I am also able to output raw content

import org.jetbrains.compose.web.*
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*

fun main() {
  renderComposable(rootElementId = "root") {
    Div {
      Pre {
        Code {
          Text(
            """
              val map: Map<String, String> = mapOf(
                "key1" to "value1",
                "key2" to "value2",
              )
            """.trimIndent()
          )
        }
      }
    }
  }
}

However, Compose Web escapes the angled brackets.

<div><pre><code>
val map: Map&lt;String, String&gt; = mapOf(
  "key1" to "value1",
  "key2" to "value2",
)</code></pre></div>

The result matches the raw input:

image

Versions

severn-everett commented 4 months ago

Because your code includes angled brackets, it actually should be escaped. The un-escaped code of

<div>
  <pre><code>val map: Map<String, String> = mapOf(
  "key1" to "value1",
  "key2" to "value2",
)</code></pre>
</div>

will display like this in a browser:

val map: Map = mapOf(
              "key1" to "value1",
              "key2" to "value2",
            )

i.e. the type declaration of the map does not render.

Instead, you should just use the normal text appending functionality in the library if you want to inject Kotlin code into a <code> block, e.g.


fun main() {
  document.body!!.append.div {
    pre {
      code {
        +"""
            val map: Map<String, String> = mapOf(
              "key1" to "value1",
              "key2" to "value2",
            )
          """.trimIndent()
      }
    }
  }
}
aSemy commented 4 months ago

Thanks for the suggestion @severn-everett. You are right, that technically the code should be escaped.

It's been a while since I looked at this. IIRC I was trying to use a JavaScript library for code syntax highlighting, which required the angled brackets were rendered as-is, because it needed to parse the code. It would then perform its own escaping.