Documentation
¶
Overview ¶
Command widows joins the last two words of every heading with a non-breaking space, so a heading cannot wrap with one word alone on the last line.
The rule is old typesetting practice and the implementation is a one-line regular expression in most languages. It is not one here, and the reason is the ordering constraint: an insertion can only go where the rewriter has not been. The gap to replace is the last one in the heading, and you do not know which gap is the last one until the heading ends - by which time the gap has been written out.
<h1>The quick brown fox</h1>
^ this space, known only at </h1>
So this program buys the ability to edit backwards, and the price is a buffer: the heading's content is removed as it arrives, rebuilt in memory, and written back at the end tag with the gap replaced. A heading is small, so the buffer is bounded - and bounded by a number, not by hope, because a document can put anything inside an <h1>. Past MaxBuffer bytes the heading is flushed where it stands and passed through, and counted.
Rebuilding a region makes this program the serialiser for it, and three measured things follow.
Text and attribute values are reported as the document spells them: character references are not decoded, so "a & b" arrives with the six characters of the reference in it. Writing them back verbatim is therefore right, and escaping them would double every ampersand on the page. The one character that has to be escaped is the quote inside an attribute value, because a single-quoted attribute can contain a bare double quote and this program writes double-quoted attributes: see the package documentation on building markup yourself.
An element's end tag is not always its own. In "<h1>a <em>b</h1>" the em is closed by "</h1>", and taking the em's tags away takes that token with them - so the heading loses its closing tag unless the program writes it back. Nothing reports a problem; the output parses, with everything after the heading inside it. This program watches for an end tag whose name is not the element's own, which is the guard lolhtml.Element.OnEndTag documents, and uses it to decide what to write rather than only where.
An element that is never closed gets no end tag callback at all, so "<h1>a <em>b" would lose its buffered content entirely. OnDocumentEnd is the backstop: whatever is still buffered when the document ends is written there.
An element whose content is not markup cannot be rebuilt this way at all - unwrapping it turns its text into elements, which is the hazard lolhtml.Element.RemoveAndKeepContent documents. A <script> inside a heading is unusual and entirely legal, so the program asks lolhtml.IsRawText and gives up on that heading rather than rewriting a payload into markup.
The join itself is applied to the buffered text and not to the markup, so a gap before an inline element is still a gap:
<h1>A long <em>title</em></h1> -> <h1>A long <em>title</em></h1>
and a heading whose last word already carries a non-breaking space - as a character or as a reference - is left alone, which is what makes running this twice the same as running it once.