pub fn inject_before_body_close(html: &str, payload: &str) -> StringExpand description
Rewrites every element with its attributes in name order.
Attribute order carries no meaning in HTML, but it does change the bytes, so a golden-file suite sees a reordering as a diff even though nothing observable changed. Sorting first makes those comparisons stable — #466 asks for exactly this.
Real parsing via lol_html rather than scanning for < and quotes:
attribute values legitimately contain both, and hand-rolled tag
surgery over arbitrary HTML is the failure mode that put escaped
<span> tags on a published page in v0.0.58.
§Examples
use ssg::util::html_rewriter::sort_attributes;
let out = sort_attributes(r#"<img src="a.png" alt="x" id="i">"#).unwrap();
assert_eq!(out, r#"<img alt="x" id="i" src="a.png">"#);§Errors
Appends payload immediately before the document’s </body> end tag.
The counterpart to crate::util::head_dom::inject_before_head_close,
and added for the same reason: five call sites across islands,
view_transitions and search were splicing at html.rfind("</body>").
A byte search cannot tell the document’s end tag from the characters
</body> sitting inside a comment, a script string or a <pre> block,
and when it picks the wrong one the payload lands somewhere inert with
nothing reporting it (ssg#570).
Only the first </body> in document order is used, so a page carrying a
nested document — the generator wraps an already-complete document in a
layout — gets one copy, in its own body, rather than one per match.
Returns the input unchanged when the document has no <body> or when the
rewrite fails, so callers keep whatever fallback they had.
§Examples
use ssg::util::html_rewriter::inject_before_body_close;
let html = "<html><body><p>hi</p></body></html>";
let out = inject_before_body_close(html, "<script src=\"x.js\"></script>");
assert!(out.contains("</script></body>"));