A semantic markup tree for MoonBit: HTML, SVG and MathML, with a tolerant parser, a printer with pretty, compact and minified modes, and structured diagnostics
Dependencies
///|
test "parse then print" {
let p = @html.parse("<div class=\"card\"><p>hi</p></div>")
inspect(
@html.to_html(p.document()),
content=(
#|<div class="card">
#| <p>hi</p>
#|</div>
#|
),
)
inspect(
@html.to_html(p.document(), style=Minified),
content="<div class=card><p>hi</p></div>",
)
}///|
test "an implied end tag is recognised, and stays implied" {
let p = @html.parse("<ul><li>one<li>two</ul>")
// Two list items, not one containing the other -- and printed flat, because
// an element with no end tag leaks: a newline written after it would become
// part of its text.
match p.document().children[0] {
Element(ul) => inspect(ul.children.length(), content="2")
_ => fail("expected a ul")
}
inspect(
@html.to_html(p.document()),
content=(
#|<ul><li>one<li>two</ul>
#|
),
)
}///|
test "a stray end tag does not take the document with it" {
let p = @html.parse("<div>a</span>b</div>")
inspect(@html.to_html(p.document(), style=Minified), content="<div>ab</div>")
inspect(p.diagnostics().length(), content="1")
inspect(p.diagnostics()[0].kind.code(), content="html::stray_end_tag")
}///|
test "inline content is never reflowed" {
let p = @html.parse("<p>Read <em>this</em> now</p>")
inspect(
@html.to_html(p.document()),
content=(
#|<p>Read <em>this</em> now</p>
#|
),
)
}///|
test "foreign content keeps the case its vocabulary spells names with" {
let p = @html.parse(
"<svg viewbox=\"0 0 1 1\"><lineargradient/><circle r=\"1\"/></svg>",
)
inspect(
@html.to_html(p.document(), style=Minified),
content="<svg viewBox=\"0 0 1 1\"><linearGradient/><circle r=1 /></svg>",
)
}///|
test "a character reference keeps its spelling" {
let p = @html.parse("<p>a & b c</p>")
inspect(
@html.to_html(p.document(), style=Minified),
content="<p>a & b c</p>",
)
}///|
test "what goes in comes back out" {
let p = @html.parse("<p title=\"x\">y</p>")
let hostile = "</p><script>alert(1)</script>"
match p.document().children[0] {
Element(e) => {
let d : @ast.Document = {
children: [Element({ ..e, children: [Text(@ast.Text::new(hostile))], })],
span: @span.nowhere,
}
let printed = @html.to_html(d, style=Minified)
inspect(
printed,
content="<p title=x></p><script>alert(1)</script></p>",
)
match @html.parse(printed).document().children[0] {
Element(back) =>
inspect(@html.text_content(Element(back)), content=hostile)
_ => fail("expected a p")
}
}
_ => fail("expected a p")
}
}///|
test "lenses read an untyped tree" {
let p = @html.parse("<input id=\"q\" class=\"a b\" size=\"12\" disabled>")
match @value.by_id(p.document(), "q") {
Some(e) => {
inspect(@html.class_list(e).length(), content="2")
assert_eq(@value.as_int(e, "size"), Some(12))
// HTML ignores a boolean attribute's value, so this lens does too.
inspect(@value.as_bool(e, "disabled"), content="true")
}
None => fail("expected to find #q")
}
}| package | what | depends on |
|---|---|---|
| span | source ranges, in UTF-16 code units | nothing |
| kind | the closed set of error kinds, named after the specification's | nothing |
| names | the tables: void, raw text, implied end tags, default display, SVG/MathML case adjust, 2,231 named references | nothing |
| ast | the tree | span, kind |
| write | the printer, three modes and one whitespace policy | ast, names |
| error | diagnostics, and the one bridge to error-report | span, kind |
| token | the markup tokenizer | span, names |
| value | typed lenses over elements and attributes | ast, names |
| parse | markup text to a tree | all of the above |
Install
Download zipA semantic markup tree for MoonBit: HTML, SVG and MathML, with a tolerant parser, a printer with pretty, compact and minified modes, and structured diagnostics
Dependencies