All questions

HTML Tokenizer & Parser

Premium

HTML Tokenizer & Parser

Turning <ul><li>a</li><li>b</li></ul> into a tree of nodes is what DOMParser, template compilers (Vue, Svelte), sanitizers, and syntax highlighters all do under the hood. It's a two-stage classic: a tokenizer scans the string into a flat list of tags and text, and a parser folds that list into a nested tree using a stack. The wrinkles that make it real are attributes, void elements like <img> that never close, and correct nesting.

Implement htmlTokenizerParser(html) returning an array of top-level nodes. See MDN: DOMParser.

Signature

function htmlTokenizerParser(html) {
  // returns an array of nodes:
  //   { type: 'element', tag, attributes: {}, children: [] }
  //   { type: 'text', value }
}

Examples

htmlTokenizerParser('<p class="lead">Hi <b>there</b></p>');
// [{ type:'element', tag:'p', attributes:{ class:'lead' }, children:[
//     { type:'text', value:'Hi ' },
//     { type:'element', tag:'b', attributes:{}, children:[{ type:'text', value:'there' }] },
//   ] }]
htmlTokenizerParser('<div><img src="a.png"><span>x</span></div>');
// img is a void element: no children, and the span is its SIBLING, not its child

Notes

  • Two phases — tokenize (open/close/text) first, then build the tree with a stack: push on an open tag, pop on a close tag, append text to the current parent.
  • Attributes — support name="v", name='v', name=v, and bare name (boolean → ''). Lowercase tag names.
  • Void & self-closingimg, br, input, hr, meta, link, … take no close tag and hold no children; <br/> works too. Don't push them on the stack.
  • Text — keep text between tags, but drop nodes that are entirely whitespace (formatting indentation).

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium