AniUI Academy

Decode HTML entities in one pass

mediumDOM & Utilities+250 XPPractice

Content from a CMS or an RSS feed arrives entity-encoded, and you have to decode it before you can search it, count its characters, or send it somewhere that is not HTML. Doing it with repeated replaces creates a real security bug: decode &amp; first and then scan again, and the input &amp;lt; turns into <, which the author explicitly escaped.

Implement unescapeHtml(input). It decodes, in a single left-to-right pass over the string, any entity matching &name;, &#123; or &#x1F;.

The named entities you must support are exactly &amp; to &, &lt; to <, &gt; to >, &quot; to ", &apos; to ' and &nbsp; to a normal space. Numeric entities are decimal (&#39;) or hexadecimal with a lower or upper case x (&#x2F;), and decode with String.fromCodePoint of that value. Any entity you do not recognise — &frac12;, &copyright; — is left in the output exactly as written.

Because it is one pass, text produced by decoding is never rescanned: "&amp;lt;" decodes to "&lt;" and stops there. If input is null or undefined, return the empty string "".

Arguments

inputstring | null | undefined
The entity-encoded text to decode. null and undefined both resolve to "".

Returns

string — The decoded text. Unrecognised entities are left exactly as written and decoded output is never rescanned.

Examples

unescapeHtml("&lt;p&gt;Hi &amp; bye&lt;/p&gt;");
// => "<p>Hi & bye</p>"

The common named entities decode in place.

unescapeHtml("&amp;lt;");
// => "&lt;"

A single pass means the decoded &lt; is not re-decoded.

unescapeHtml("&#39;a&#x2F;b&#39;");
// => "'a/b'"

Decimal and hexadecimal numeric entities both decode via String.fromCodePoint.

What it has to do

  • Decode &amp;, &lt;, &gt;, &quot;, &apos; and &nbsp;, with &nbsp; becoming a normal space.
  • Decode decimal &#NN; and hexadecimal &#xNN; entities, case-insensitively on the x.
  • Leave unrecognised entities untouched.
  • Decode in a single pass, so &amp;lt; becomes &lt; and not <.
  • Return "" for null and undefined.

Notes

  • A single global .replace() with a callback is what makes it one pass — the regex never looks at text the callback produced, so decoding &amp;lt; cannot cascade into <.
  • A real browser decodes hundreds of named entities (&copy;, &mdash;, …). This smaller list is enough for round-tripping anything your own escape helper produced.

Stuck? The javascript course covers everything this challenge needs.

Try it yourself
Loading playground...
5 tests · +250 XP

Discussion

Sign in to post
Sign in to join the conversation.Sign in

No comments yet. Be the first to say something.