Skip to main content

inject_before_body_close

Function inject_before_body_close 

Source
pub fn inject_before_body_close(html: &str, payload: &str) -> String
Expand 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>"));