diff --git a/scala3doc/src/dotty/renderers/html.scala b/scala3doc/src/dotty/renderers/html.scala
index a97f325fa348..28d0572ea96e 100644
--- a/scala3doc/src/dotty/renderers/html.scala
+++ b/scala3doc/src/dotty/renderers/html.scala
@@ -14,7 +14,7 @@ object HTML:
def apply(attrs: AttrArg*)(tags: TagArg*): AppliedTag = {
val sb = StringBuilder()
sb.append(s"<$name")
- attrs.foreach{
+ attrs.filter(_ != Nil).foreach{
case s: Seq[AppliedAttr] =>
s.foreach(sb.append(" ").append)
case e: AppliedAttr =>
@@ -25,19 +25,26 @@ object HTML:
case t: AppliedTag =>
sb.append(t)
case s: String =>
- sb.append(s)
+ sb.append(s.escapeReservedTokens)
case s: Seq[AppliedTag | String] =>
s.foreach{
case a: AppliedTag =>
sb.append(a)
case s: String =>
- sb.append(s)
+ sb.append(s.escapeReservedTokens)
}
}
sb.append(s"$name>")
sb
}
+ extension (s: String) private def escapeReservedTokens: String =
+ s.replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace("\"", """)
+ .replace("'", "'")
+
case class Attr(name: String):
def :=(value: String): AppliedAttr = AppliedAttr(s"""$name="$value"""")
@@ -48,6 +55,7 @@ object HTML:
val div = Tag("div")
val span = Tag("span")
val a = Tag("a")
+ val p = Tag("p")
val h1 = Tag("h1")
val h2 = Tag("h2")
val h3 = Tag("h3")
diff --git a/scala3doc/test/dotty/dokka/renderers/HtmlTagsTest.scala b/scala3doc/test/dotty/dokka/renderers/HtmlTagsTest.scala
new file mode 100644
index 000000000000..fca9c3067041
--- /dev/null
+++ b/scala3doc/test/dotty/dokka/renderers/HtmlTagsTest.scala
@@ -0,0 +1,197 @@
+package dotty.dokka.renderers
+
+import org.junit.{Test, Rule}
+import org.junit.Assert.{assertSame, assertTrue, assertEquals}
+import dotty.dokka.HTML._
+
+class HtmlTagsTest {
+
+ @Test
+ def simpleDiv = {
+ val actual = div().toString
+ val expect = "
"
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def divWithStyles = {
+ val actual = div(style := "some: style;")().toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def divWithChildren = {
+ val actual = div(h1(), span()).toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def divWithTextInside = {
+ val actual = div(h1(), span("Some text"), "Some more of the text").toString
+ val expect = """Some textSome more of the text"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def escapeAmpersand = {
+ val actual = div("Some & text").toString
+ val expect = """Some & text
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def escapeLessThan = {
+ val actual = div("Some < text").toString
+ val expect = """Some < text
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def escapeGreaterThan = {
+ val actual = div("Some > text").toString
+ val expect = """Some > text
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def escapeQuotationMark = {
+ val actual = div("Some \" text").toString
+ val expect = """Some " text
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def escapeApostrophe = {
+ val actual = div("Some ' text").toString
+ val expect = """Some ' text
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def nestedTagsWithAttributes = {
+ val actual = html(
+ head(
+ script(src:="..."),
+ script(raw("alert('Hello World')"))
+ ),
+ body(
+ div(
+ h1(id:="title")("This is a title"),
+ p("This is a big paragraph of text")
+ )
+ )
+ ).toString
+ val expect = """This is a title
This is a big paragraph of text
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def anotherNestedTagsWithAttributes = {
+ val actual = html(
+ head(
+ script("some script")
+ ),
+ body(
+ h1(style:="background-color: blue; color: red;")("This is my title"),
+ div(style:="background-color: blue; color: red;")(
+ p(cls :="contentpara first")(
+ "This is my first paragraph"
+ ),
+ a(style:="opacity: 0.9;")(
+ p(cls := "contentpara")("Goooogle")
+ )
+ )
+ )
+ ).toString
+ val expect = """This is my title
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def appliedTagAndSeqAppliedTag = {
+ val actual = div(h1("AppliedTag"), Seq(h1("SeqAppliedTag"))).toString
+ val expect = """AppliedTag
SeqAppliedTag
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def stringAndSeqString = {
+ val actual = div("String", Seq("SeqString")).toString
+ val expect = """StringSeqString
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def mixingAllTagArgs = {
+ val actual = div("String", Seq("SeqString"), h1("AppliedTag"), Seq(h1("SeqAppliedTag")), Seq("SeqString"), h1("AppliedTag")).toString
+ val expect = """StringSeqString
AppliedTag
SeqAppliedTag
SeqStringAppliedTag
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def appliedAttrAndSeqAppliedAttr = {
+ val actual = div(cls := "someClass", Seq(style := "some: style;")).toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def seqAppliedAttrAndAppliedAttr = {
+ val actual = div(Seq(cls := "someClass"), style := "some: style;").toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def seqAppliedAttrAndSeqAppliedAttr = {
+ val actual = div(Seq(cls := "someClass"), Seq(style := "some: style;")).toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def mixingAllTagArgsAndAllAttrArgs = {
+ val actual = div(Seq(cls := "someClass"), id := "myId", Seq(style := "some: style;"))("String", Seq("SeqString"), h1("AppliedTag"), Seq(h1("SeqAppliedTag")), Seq("SeqString"), h1("AppliedTag")).toString
+ val expect = """StringSeqString
AppliedTag
SeqAppliedTag
SeqStringAppliedTag
"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def nilTagArg = {
+ val nil: TagArg = Nil
+ val actual = div(nil).toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def nilAttrArg = {
+ val nil: AttrArg = Nil
+ val actual = div(nil).toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def nilAmongTags = {
+ val actual = div("name", Nil, div("ala")).toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def nilAmongArgs = {
+ val actual = div(cls := "someClass", Nil, style := "some: style;").toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+
+ @Test
+ def nilAmongArgsAndTags = {
+ val actual = div(cls := "someClass", Nil, style := "some: style;")("name", Nil, div("ala")).toString
+ val expect = """"""
+ assertEquals(expect, actual)
+ }
+}
\ No newline at end of file