///|
/// Recursively walk a `@html_dsl.Html` node and print the MoonBit source
/// code that defines it, using the html_dsl builder functions.
///
/// Example: a `div` with id/class attributes wrapping a paragraph becomes
/// `div(id="divid-01", class="flex flex-row", [p([text("hello")])])`.
///
/// Attributes that a tag's builder cannot express as labelled arguments are
/// kept in an `attrs=[Attr::...]` list, and tags without a dedicated builder
/// fall back to the generic `node("tag", attrs=[...], [children])` builder.
fn print_html_dsl(node : @h.Html) -> String {
match node {
@h.Html::Text(s) => "text(\{str_lit(s)})"
@h.Html::Nothing => "nothing()"
@h.Html::Unsafe(s) => "raw_html(\{str_lit(s)})"
@h.Html::Fragment(children) => "fragment([\{print_children(children)}])"
@h.Html::Comment(data) => "comment(\{str_lit(data)})"
@h.Html::Node(tag, attrs, children) => print_element(tag, attrs, children)
}
}
///|
/// Join the printed children of an element or fragment.
fn print_children(children : Array[@h.Html]) -> String {
children.map(print_html_dsl).join(", ")
}
///|
/// Print an element node: known tags use their dedicated builder with
/// labelled arguments, unknown tags fall back to the generic `node` builder.
fn print_element(
tag : String,
attrs : Array[@h.Attr],
children : Array[@h.Html],
) -> String {
if tag == "script" {
return print_script(attrs, children)
}
if tag == "title" {
return print_title(attrs, children)
}
if tag == "head" {
if attrs.is_empty() {
return "head([\{print_children(children)}])"
}
return node_literal(tag, attrs, children)
}
if tag == "html" {
return print_html_el(attrs, children)
}
if tag == "br" || tag == "hr" {
if attrs.is_empty() {
return "\{tag}()"
}
return node_literal(tag, attrs, children)
}
if tag == "embed" || tag == "source" || tag == "track" {
return print_media(tag, attrs, children)
}
match svg_tag_shape(tag) {
Some(shape) => return print_svg_element(tag, attrs, children, shape)
None => ()
}
match tag_shape(tag) {
None => node_literal(tag, attrs, children)
Some(shape) => {
for required in shape.required {
if !has_named(attrs, required) {
return node_literal(tag, attrs, children)
}
}
if !shape.children && !children.is_empty() && !is_void_tag(tag) {
return node_literal(tag, attrs, children)
}
let parts : Array[String] = []
let rest : Array[String] = []
for attr in attrs {
match attr_named(attr) {
Some((name, value)) if shape.args.contains(name) =>
parts.push("\{name}=\{value}")
_ => rest.push(attr_literal(attr))
}
}
if !rest.is_empty() {
parts.push("attrs=[\{rest.join(", ")}]")
}
if shape.children {
parts.push("[\{print_children(children)}]")
}
"\{shape.name}(\{parts.join(", ")})"
}
}
}
///|
/// Print a `