encoding.html
Turning HTML entities, named and numeric, into text, and text into safe HTML.
import encoding.html
HTML cannot hold a bare < or & in its text, so they travel as entities: <, &, é or a number such as é and é. Unescape turns every entity in a string back into the character it names; Escape and EscapeText go the other way. Use them when you read text out of a page or a feed, and when you put text of unknown origin into a page.
Unescape follows the HTML5 rules the way Python's html.unescape does, and gives the same answer on every input tried against it: all the named entities of the HTML standard (including the ones that stand for two characters), decimal and hexadecimal numbers, the older names that browsers accept without the closing ; (©, &), numbers 128 to 159 read as Windows-1252 characters, and the replacement character for �, surrogates and numbers beyond Unicode. Anything that is not an entity stays as it is.
What people get wrong:
- Unescaping text before using it as HTML. After
Unescapethe text is plain text again; if it goes back into a page, escape it again. - Using
EscapeTextinside an attribute. It leaves quotes alone, so a value with"in it breaks out ofhref="...". UseEscapefor attributes. - Expecting
Escapeto make a URL or a script safe. It only protects the places where HTML text or a quoted attribute goes. Ajavascript:link or text inside<script>needs more than escaping. - Unescaping twice.
&lt;is the text<; one pass gives that, a second pass would give<.
The template package has its own EscapeHTML; for XML text and attributes see encoding.xml.
Escaping and getting the text back
Escaping and unescaping are inverses: whatever Escape produces, Unescape turns back into the original. The example escapes three comments and checks the round trip.
import encoding.html
func main:
comments := ["5 < 6 && 7 > 3", `say "hi" & wave`, "it's <b>bold</b>"]
each comment in comments:
escaped := html.Escape(comment)
println escaped
println html.Unescape(escaped) == comment
end
endBuilding markup from untrusted text
A name that comes from a user can hold &, < or a quote. Put it in a tag's text with EscapeText and in an attribute value with Escape, which also covers both kinds of quote. In the other direction, text scraped from a page has to go through Unescape before it is compared or shown.
import encoding.html
func card(name: str, note: str): str:
ret `<div class="card" title="` + html.Escape(name) + `"><h2>` + html.EscapeText(name) + `</h2><p>` + html.EscapeText(note) + `</p></div>`
end
func main:
println card("Tom & Jerry", "<b>not bold</b>")
println card(`He said "hi"`, "it's fine")
scraped := "José <open> & 5 < 6 – €3"
println html.Unescape(scraped)
endFunctions
Unescape
func Unescape(s: str): stré), decimal (é) and hexadecimal (é), with the same rules as Python's html.unescape. Text that is not a valid entity is left unchanged. The result is a new string.import encoding.html
func main:
page := "<p>Fiancé & crème — €5</p>"
println html.Unescape(page)
println html.Unescape("Tom & Jerry © 2026, a < b")
safe := html.Escape(`<a href="x">it's</a>`)
println safe
println html.EscapeText(`<a href="x">it's</a>`)
println html.Unescape(safe)
endEscape
func Escape(s: str): str&, <, >, " and ' by &, <, >, " and ', so the text is safe in HTML text and in a quoted attribute.The example for this item is shown with the ones above: encoding-html-Unescape.salam
EscapeText
func EscapeText(s: str): str&, < and >. Safe in HTML text, not in an attribute.The example for this item is shown with the ones above: encoding-html-Unescape.salam