Standard library · Encoding and data

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: &lt;, &amp;, &eacute; or a number such as &#233; and &#xE9;. 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 ; (&copy, &amp), numbers 128 to 159 read as Windows-1252 characters, and the replacement character for &#0;, 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 Unescape the text is plain text again; if it goes back into a page, escape it again.
  • Using EscapeText inside an attribute. It leaves quotes alone, so a value with " in it breaks out of href="...". Use Escape for attributes.
  • Expecting Escape to make a URL or a script safe. It only protects the places where HTML text or a quoted attribute goes. A javascript: link or text inside <script> needs more than escaping.
  • Unescaping twice. &amp;lt; is the text &lt;; 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.

encoding-html-guide-roundtrip.salam
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
end
Output5 &lt; 6 &amp;&amp; 7 &gt; 3 true say &quot;hi&quot; &amp; wave true it&#x27;s &lt;b&gt;bold&lt;/b&gt; true

Building 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.

encoding-html-guide-page.salam
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&eacute; &lt;open&gt; &amp; 5 &lt; 6 &#8211; &euro;3"
    println html.Unescape(scraped)
end
Output<div class="card" title="Tom &amp; Jerry"><h2>Tom &amp; Jerry</h2><p>&lt;b&gt;not bold&lt;/b&gt;</p></div> <div class="card" title="He said &quot;hi&quot;"><h2>He said "hi"</h2><p>it's fine</p></div> José <open> & 5 < 6 – €3

Functions

Unescape

func Unescape(s: str): str
Replaces every HTML entity in the text by the characters it stands for: named (&eacute;), decimal (&#233;) and hexadecimal (&#xE9;), 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.
encoding-html-Unescape.salam
import encoding.html

func main:
    page := "&lt;p&gt;Fianc&eacute; &amp; cr&egrave;me &#8212; &#x20AC;5&lt;/p&gt;"
    println html.Unescape(page)
    println html.Unescape("Tom &amp; Jerry &copy 2026, a &lt 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)
end
Output<p>Fiancé & crème — €5</p> Tom & Jerry © 2026, a < b &lt;a href=&quot;x&quot;&gt;it&#x27;s&lt;/a&gt; &lt;a href="x"&gt;it's&lt;/a&gt; <a href="x">it's</a>

Escape

func Escape(s: str): str
Replaces &, <, >, " and ' by &amp;, &lt;, &gt;, &quot; and &#x27;, 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
Replaces only &, < and >. Safe in HTML text, not in an attribute.

The example for this item is shown with the ones above: encoding-html-Unescape.salam