Strip HTML (don't parse it)
Stripping HTML with regex (and when not to): the trade-offs, the safer paths, and the patterns for quick text cleanup.
Regex — HTML strip (defensively)
EXAMPLE
// ===== The famous warning =====
// 'You can't parse HTML with regex' — true for full parsing.
// HTML is not a regular language; nested tags, attributes, comments, scripts break naive regexes.
// ===== When regex is OK =====
// - Quick + dirty cleanup of trusted markup
// - Logging / display where small errors are tolerable
// - Pre-processing before a real sanitiser
// When regex is NOT OK:
// - Sanitising untrusted input for XSS protection
// - Extracting structured data from arbitrary HTML
// - Anywhere correctness matters
// For untrusted input always use a real sanitiser (DOMPurify, sanitize-html, bleach, HTMLPurifier).
// ===== Naive strip =====
const stripped = html.replace(/<[^>]*>/g, '');
// Removes simple tags. Misses:
// - Scripts (still leaves the content)
// - Comments
// - Entities (< etc — leaves as text)
// - Nested < > inside attributes (rare but exists)
// ===== Slightly better =====
function stripHtmlBasic(s) {
return s
.replace(/<script[\s\S]*?<\/script>/gi, '') // drop script + contents
.replace(/<style[\s\S]*?<\/style>/gi, '') // drop style + contents
.replace(/<!--[\s\S]*?-->/g, '') // drop comments
.replace(/<[^>]+>/g, ' ') // strip remaining tags
.replace(/ /g, ' ').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'")
.replace(/\s+/g, ' ').trim();
}
// Still imperfect: unknown entities, malformed tags, CDATA. For real cleanup -> use a parser.
// ===== Safer: use DOMParser (browser) =====
function stripHtmlSafe(s) {
const doc = new DOMParser().parseFromString(s, 'text/html');
// Drop scripts / styles in case of edge cases:
doc.querySelectorAll('script, style').forEach(el => el.remove());
return doc.body.textContent || '';
}
// ===== Node: use a parser =====
// npm install node-html-parser
import { parse } from 'node-html-parser';
function stripHtmlNode(s) {
return parse(s).textContent;
}
// Or jsdom for a more complete DOM:
import { JSDOM } from 'jsdom';
const dom = new JSDOM(s);
const text = dom.window.document.body.textContent;
// ===== Sanitiser (for keeping SOME markup) =====
// npm install sanitize-html
import sanitizeHtml from 'sanitize-html';
const clean = sanitizeHtml(s, {
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li', 'br'],
allowedAttributes: { a: ['href', 'title'] },
allowedSchemes: ['http', 'https', 'mailto'],
});
// ===== Common quick-cleanup recipes =====
// Strip tabs + multiple spaces:
text.replace(/\s+/g, ' ').trim();
// Remove HTML comments only:
html.replace(/<!--[\s\S]*?-->/g, '');
// Replace <br> with newlines before stripping:
html.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '');
// Extract text inside a single tag:
const m = html.match(/<h1[^>]*>(.*?)<\/h1>/s);
const title = m?.[1].replace(/<[^>]+>/g, '').trim();
// ===== Patterns to internalise =====
// - For UNTRUSTED input -> sanitiser library
// - For TRUSTED quick cleanup -> regex is fine, but layered
// - For STRUCTURED extraction -> DOMParser / jsdom / cheerio
// - Always unescape entities after stripping tags
// ===== Pitfalls =====
// - Naive <[^>]*> misses scripts / styles / comments
// - Entity-encoded < > survive a naive strip
// - Attribute values with > can confuse [^>]*
// - Trusting 'just regex' for XSS prevention -> WHY YOU SHOULD NOT
Why it matters
Regex HTML strip is fine for trusted, low-stakes cleanup. For untrusted input, use a real sanitiser (DOMPurify, sanitize-html) and for structured extraction, use a parser (DOMParser, cheerio, jsdom). The naive <[^>]*> recipe leaks scripts, styles, and entities; layer carefully or skip regex entirely.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Stripping tags is fine for plain text. Parsing HTML with regex is NOT — use a parser. const plain = html.replace(/<[^>]+>/g, '');Try it Yourself »
Discussion
Loading…