diff --git a/Laminarize.scala b/Laminarize.scala index 85e8c15..6e129d6 100644 --- a/Laminarize.scala +++ b/Laminarize.scala @@ -20,10 +20,15 @@ object Laminarize extends ZIOAppDefault: class LaminarNodeVisitor() extends NodeVisitor: + private var written: Boolean = false + private val code: java.lang.StringBuilder = new java.lang.StringBuilder - private val attrMap: Map[String, String] = - Map("class" -> "cls", "type" -> "tpe", "viewbox" -> "viewBox", "stroke-linecap" -> "strokeLineCap", "stroke-linejoin" -> "strokeLineJoin").withDefault { cls => + + private def append(s: String): java.lang.StringBuilder = code.append(s) + private def append(c: Char): java.lang.StringBuilder = code.append(c) + + private def mangleAttrName(attr: String): String = def toCap(words: List[String]): String = words match { case h :: t => (h :: t.map(_.capitalize)).mkString("") case _ => "" @@ -31,50 +36,69 @@ def dashToCap(c: String): String = toCap(c.split("-").toList) - if (cls.startsWith("aria")) { - s"aria.${toCap(cls.split("-").toList.tail)}" + if (attr.startsWith("aria")) { + s"aria.${toCap(attr.split("-").toList.tail)}" } else { - dashToCap(cls) + dashToCap(attr) } - } + + private def mapAttr(k: String): String = + Map( + "class" -> "cls", + "type" -> "tpe", + "viewbox" -> "viewBox", + "stroke-linecap" -> "strokeLineCap", + "stroke-linejoin" -> "strokeLineJoin" + ).withDefault(mangleAttrName)(k) + + private def mapValue(k: String, v: String): String = + val unquoted = List("aria-hidden") + if (unquoted.contains(k)) v else s"\"${v}\"" + override def head(node: Node, depth: Int): Unit = node match { - case el: Element => Some(() => writeElement(el, depth)) - case t: TextNode if !t.text.isBlank => Some(() => writeTripleQuoted(t.text.trim)) + case el: Element => Some(() => appendElement(el, depth)) + case t: TextNode if !t.text.isBlank => Some(() => appendTripleQuoted(t.text.trim)) case _ => None - } map (writeNext(depth)) + } map (appendNext(depth)) + override def tail(node: Node, depth: Int): Unit = node match { case el: Element => - code.append("\n") - writeIndent(depth) - code.append(")") + append("\n") + appendIndent(depth) + append(")") case _ => () } + def getCode(): String = code.toString() - private def writeNext(depth: Int)(f: () => Unit): Unit = + + private def appendNext(depth: Int)(f: () => Unit): Unit = if (written) { - code.append(",") + append(",") } - code.append("\n") + append("\n") written = true - writeIndent(depth) + appendIndent(depth) f() - private def writeIndent(size: Int): Unit = code.append("\t" * size) - private def writeQuoted(t: String): Unit = - code.append('"').append(t.replaceAll("\"", "\\\"")).append('"') - private def writeTripleQuoted(t: String): Unit = - code.append(""""""""").append(t).append(""""""""") - private def writeElement(el: Element, depth: Int): Unit = - code.append(el.normalName).append("(\n") + + private def appendIndent(size: Int): Unit = code.append("\t" * size) + + private def appendQuoted(t: String): Unit = + append('"').append(t.replaceAll("\"", "\\\"")).append('"') + + private def appendTripleQuoted(t: String): Unit = + append(""""""""").append(t).append(""""""""") + + private def appendElement(el: Element, depth: Int): Unit = + append(el.normalName).append("(\n") val attrs = el.attributes.asList.asScala attrs.zipWithIndex.foreach { (attribute, idx) => - if (idx != 0) code.append(",\n") - code.append("\t" * (depth + 1)) - code - .append(attrMap(attribute.getKey)) - .append(" := ") - writeQuoted(attribute.getValue) + if (idx != 0) append(",\n") + appendIndent(depth + 1) + append(mapAttr(attribute.getKey)) + .append(" := ") + append(mapValue(attribute.getKey, attribute.getValue)) } if (attrs.isEmpty) written = false