Properly Parsing HTML in HTML5

Photo Credit: Random image from https://unsplash.it

Properly Parsing HTML in HTML5

Parsing HTML in Javascript is harder than one would expect. You would think that the browser, a HTML parser by its very nature, would give access to easy HTML parsing. But you’d be wrong, and here is an example of why:

1
2
3
4
5
6
7
8
9
> d = document.createElement('div')
<div></div>
> d.innerHTML = '<tr></tr>'
"<tr></tr>"

> d.innerHTML
""
// ^^ Uh oh.  Where'd it go?

The problem is that the

1
<tr>
tag in HTML requires a parent of
1
<table>
or
1
<tbody>
or
1
<thead>
or
1
<tfoot>
, according to the W3C HTML5 spec.

What this means is that in HTML a

1
<tr>
is never a child of a
1
<div>
tag, and the browser automatically fixes this for you.

The jQuery and Knockout parsers get around this by wrapping the HTML in the parent nodes that are required. There are a few HTML elements that need parents of a specific sort:

1
area
,
1
thead
,
1
tbody
,
1
tr
,
1
td
, and others.

Two popular parsing implementations, jQuery and Knockout, have problems.

The

1
$.parseHTML
method of jQuery v1.11.3 and v2.1.4 incorrectly parse custom elements that start with one of the nodes that requires wrapping, such as
1
<tr-something>
or
1
<area-custom-element>
.

The Knockout 3.3.0 parser has trouble with comments that precede an element such as

1
<!-- ... --><b>...
. (Though we are working to fix this!)

These problems with

1
knockout
and
1
jQuery
are edge cases, but they can be difficult to debug and onerous to work around.

An elegant – and arguably the “correct” – answer becomes available when a browser supports the

1
<template>
tag. Let’s have a look at the example above, but using a template tag:

1
2
3
4
5
6
7
8
9
> t = document.createElement('template')
<template>​…​</template>
> t.innerHTML = '<tr></tr>'
"<tr></tr>"

> t.innerHTML
"<tr></tr>"
// ^^ Happy dance?

This works because the template tag spec permits its content to be nodes of any of the elements that require us working around.

You can get the parsed nodes by iterating over

1
t.childNodes
(if you want HTML comments and text nodes) or
1
t.children
(if you do not). You can also inject the parsed content into the document as follows, but bear in mind that the parental rule requirements apply after insertion.

1
2
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);

The result is that you have access to the browser’s native HTML parsing, without those relatively complex (and possibly slow) workarounds that aspire to but do not quite reach parity with builtin browser parser.

Most browsers in their most recent incarnations support the template tag, including Internet Explorer Edge 13. Until the latest browsers are more widespread we will have to live with some compromises, but the future of HTML parsing with Javascript in browsers looks bright.