Skip to content

HTML Entity Encoder/Decoder — TypeScript source

Encode text to HTML entities and decode entities back to text (named + numeric). UTF-8 safe, runs entirely in your browser, with a shareable link to your exact input.

This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

// HTML entity encode / decode. Pure string transforms, never throw.
import { HTML_ENTITY_TABLE } from './htmlEntityTable';

export interface EncodeOptions {
  /** When true, also encode every non-ASCII character as a decimal numeric
   *  reference (e.g. (c) -> ©), producing ASCII-safe output. Default false. */
  ascii?: boolean;
}

// Encode escapes the five HTML-significant characters. `&` is replaced first so
// the entities we emit are never re-escaped. With `ascii: true`, every non-ASCII
// code point is additionally converted to a decimal numeric reference.
export function encodeHtml(text: string, options?: EncodeOptions): string {
  let out = text
    .replace(/&/g, '&')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/'/g, '&apos;')
    .replace(/"/g, '&quot;');
  if (options?.ascii) {
    // \P{ASCII} (u flag) matches each non-ASCII code point as one unit, so
    // astral characters like emoji encode as a single reference.
    out = out.replace(/\P{ASCII}/gu, (ch) => `&#${ch.codePointAt(0)};`);
  }
  return out;
}

// Matches a well-formed reference: `&#xHH;`, `&#NN;`, or `&name;`.
const ENTITY = /&(#[xX][0-9a-fA-F]+|#[0-9]+|[A-Za-z][A-Za-z0-9]*);/g;

function decodeReference(match: string, body: string): string {
  if (body.charCodeAt(0) === 0x23 /* '#' */) {
    const hex = body.charCodeAt(1) === 0x78 /* 'x' */ || body.charCodeAt(1) === 0x58 /* 'X' */;
    const num = parseInt(hex ? body.slice(2) : body.slice(1), hex ? 16 : 10);
    // Lone surrogates are not valid standalone code points — keep the original.
    if (num >= 0xd800 && num <= 0xdfff) return match;
    // String.fromCodePoint throws a RangeError for code points > U+10FFFF;
    // fall back to the original text in that case (never throw).
    try {
      return String.fromCodePoint(num);
    } catch {
      return match;
    }
  }
  return Object.prototype.hasOwnProperty.call(HTML_ENTITY_TABLE, body) ? HTML_ENTITY_TABLE[body] : match;
}

// Decode resolves named (`&amp;`, `&copy;`, ...), decimal (`&#169;`), and hex
// (`&#xA9;`) references. Unknown / malformed references pass through untouched.
export function decodeHtml(text: string): string {
  return text.replace(ENTITY, decodeReference);
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →