Skip to content
This repository was archived by the owner on Nov 20, 2025. It is now read-only.

Commit 8c3e6a1

Browse files
Fixed #336: Add support for Html Entities
1 parent 4a1ac5b commit 8c3e6a1

8 files changed

Lines changed: 2213 additions & 9 deletions

File tree

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ lazy val tyrianTags =
139139
commonSettings ++ publishSettings,
140140
Compile / sourceGenerators += codeGen("tyrian", TagGen.gen).taskValue,
141141
Compile / sourceGenerators += codeGen("tyrian", AttributeGen.gen).taskValue,
142+
Compile / sourceGenerators += codeGen("tyrian", HtmlEntityGen.gen).taskValue,
142143
Compile / sourceGenerators += codeGen("CSS", "tyrian", CSSGen.gen).taskValue
143144
)
144145
.jsSettings(

project/CompleteHtmlEntityLists.scala

Lines changed: 2133 additions & 0 deletions
Large diffs are not rendered by default.

project/HtmlEntityGen.scala

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import sbt.*
2+
import scala.sys.process.*
3+
4+
object HtmlEntityGen {
5+
6+
def template(moduleName: String, fullyQualifiedPath: String, contents: String): String =
7+
s"""package $fullyQualifiedPath
8+
|
9+
|// GENERATED by HtmlEntityGen.scala - DO NOT EDIT
10+
|trait $moduleName {
11+
|$contents
12+
|}
13+
""".stripMargin
14+
15+
def gen(fullyQualifiedPath: String, sourceManagedDir: File): Seq[File] = {
16+
17+
val name = "HtmlEntities"
18+
19+
val file: File =
20+
sourceManagedDir / s"$name.scala"
21+
22+
if (!file.exists()) {
23+
println("Generating Html Entities")
24+
25+
val contents: String = {
26+
val all =
27+
CompleteHtmlEntityLists.all.map { case (safeName, entityValue) =>
28+
s""" val _${safeName}_ = HtmlEntity("$entityValue")"""
29+
}
30+
31+
s"""
32+
|${all.mkString("\n")}
33+
|""".stripMargin
34+
}
35+
36+
val newContents: String =
37+
template(name, fullyQualifiedPath, contents)
38+
39+
IO.write(file, newContents)
40+
41+
println("Written: " + file.getCanonicalPath)
42+
}
43+
44+
Seq(file)
45+
}
46+
47+
}

tyrian-next/src/main/scala/tyrian/next/HtmlRoot.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import tyrian.CustomHtml
55
import tyrian.Elem
66
import tyrian.Empty
77
import tyrian.Html
8+
import tyrian.HtmlEntity
89
import tyrian.RawTag
910
import tyrian.Tag
1011
import tyrian.Text
@@ -102,4 +103,7 @@ object HtmlRoot:
102103
case c: CustomHtml[GlobalMsg] =>
103104
List(c)
104105

106+
case e: HtmlEntity =>
107+
List(e)
108+
105109
Batch.fromList(rec(target))

tyrian-tags/shared/src/main/scala/tyrian/HTMLRendering.scala

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ private val spacer = (str: String) => if str.isEmpty then str else " " + str
99
extension [Msg](elem: Elem[Msg])
1010
def render: String =
1111
elem match
12-
case _: Empty.type => ""
13-
case t: Text => t.value
14-
case h: Html[?] => h.render
15-
case c: CustomElem[?] => c.toElems.mkString
12+
case _: Empty.type => ""
13+
case t: Text => t.value
14+
case h: Html[?] => h.render
15+
case c: CustomElem[?] => c.toElems.mkString
16+
case HtmlEntity(value) => value
1617

1718
extension [Msg](html: Html[Msg])
1819
def render: String =
@@ -27,10 +28,11 @@ extension [Msg](html: Html[Msg])
2728
spacer(tag.attributes.map(_.render).filterNot(_.isEmpty).mkString(" "))
2829

2930
val children = tag.children.map {
30-
case _: Empty.type => ""
31-
case t: Text => t.value
32-
case h: Html[?] => h.render
33-
case c: CustomElem[?] => c.render
31+
case _: Empty.type => ""
32+
case t: Text => t.value
33+
case h: Html[?] => h.render
34+
case c: CustomElem[?] => c.render
35+
case HtmlEntity(value) => value
3436
}.mkString
3537

3638
s"""<${tag.name}$attributes>$children</${tag.name}>"""

tyrian-tags/shared/src/main/scala/tyrian/Html.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ sealed trait Html[+M] extends Elem[M]:
4949

5050
/** Object used to provide Html syntax `import tyrian.Html.*`
5151
*/
52-
object Html extends HtmlTags with HtmlAttributes with AttributeSyntax:
52+
object Html extends HtmlTags with HtmlAttributes with AttributeSyntax with HtmlEntities:
5353

5454
def tag[M](name: String)(attributes: Attr[M]*)(children: Elem[M]*): Html[M] =
5555
Tag(name, attributes.toList, children.toList)
@@ -164,3 +164,6 @@ object RawTag:
164164

165165
trait CustomHtml[+M] extends Html[M]:
166166
def toHtml: Html[M]
167+
168+
final case class HtmlEntity(value: String) extends Elem[Nothing]:
169+
def map[N](f: Nothing => N): HtmlEntity = this

tyrian-tags/shared/src/test/scala/tyrian/HTMLRenderingTests.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,17 @@ class HTMLRenderingTests extends munit.FunSuite {
145145
assertEquals(actual, expected)
146146
}
147147

148+
test("Can render an nbsp html entity") {
149+
val view: Model => Html[Msg] =
150+
_ => span(p("Hello,"), _nbsp_, text("world!"))
151+
152+
val actual =
153+
view(model).render
154+
155+
val expected =
156+
"<span><p>Hello,</p>&nbsp;world!</span>"
157+
158+
assertEquals(actual, expected)
159+
}
160+
148161
}

tyrian/src/main/scala/tyrian/runtime/Rendering.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ object Rendering:
147147
case t: Text => List(VNode.text(t.value))
148148
case subHtml: Html[Msg] => List(toVNode(subHtml, onMsg, router))
149149
case c: CustomElem[Msg] => c.toElems.flatMap(cc => elemToVNodes(cc, onMsg, router))
150+
case HtmlEntity(value) => List(VNode.text(value))
150151

151152
private lazy val patch: Patch =
152153
snabbdom.init(

0 commit comments

Comments
 (0)