HTML was originally formalized on top of SGML, and in a way which was permissive (SGML allowed this, because it was supposed to be a flexible generic way to specify markup languages). This meant, for example, that you could omit closing tags for many elements, and something that processed HTML was supposed to know the rules for when and where to infer the existence of a closing tag. There were also whole elements which could simply be omitted -- if no tags indicated where they were or what they contained, they too could be inferred.

The permissiveness had a drawback, though, which was that many people didn't learn correctly what could and couldn't be omitted, or what was and wasn't required. And so badly-formed HTML dominated the Web. Browsers had to bend over backwards to make sense of this and display something, since at the time there was heated competition in the browser space: if Netscape just bailed out with a parsing error and IE didn't, people would use IE because they'd blame the failure on Netscape instead of on the HTML (same thing for IE -- it couldn't bail out with an error because Netscape might look better in comparison by rendering the page).

XHTML -- HTML reformulated on XML instead of SGML -- tried to bring XML's strict well-formedness handling to HTML, but turned out to be an utter failure. See Evan Goer's classic "XHTML 100" for an example of how even experts routinely could not do XHTML properly and had to be saved by browsers which never really applied the strictness correctly:

http://goer.org/Journal/2003/04/the_xhtml_100.html

There was an attempt at XHTML2, but it collapsed under its own ambition.

Enter HTML5, which began as a response to the perceived stagnation of W3C in iterating the HTML standard (since much of their effort was being expended off in markup-astronaut space on XHTML2). HTML5 introduced a bunch of useful things people had been asking for for a while, but also took the approach of codifying how to parse HTML. And not just valid HTML, but anything claiming to be HTML. If you run the HTML5 algorithms, you can get a parse tree out of damn near any kind of junk, and that process is now standardized. In pragmatic fashion, it mostly codified the hacks and workarounds browsers had already come up with.

And that's how we get the HTML5 legacy color parsing algorithm, which can turn almost any junk you stick in a declaration into an RGB color value. Most of what it's doing is trying to filter out obvious cases (matches for named colors and certain hex specifications) early, then figure out a way to turn random junk consistently into a hexadecimal number.

If you're interested, I maintain a Python library for working with HTML/CSS color values, and an implementation of the legacy color parsing algorithm with directives from the spec interspersed as comments so you can step through it and see what's going on:

https://github.com/ubernostrum/webcolors/blob/master/webcolo...

Its documentation also covers the history of how colors are specified in HTML/CSS:

http://webcolors.readthedocs.io/en/1.7/colors.html

But a simple description of the way it works is:

1. Bail out if the value is not something the algorithm can work with. Non-Unicode inputs can't be parsed into a color, and an empty string can't be parsed into a color.

2. Strip leading/trailing whitespace, and look to see if the input matches a CSS named color, or the three-hexadecimal-digit shortcut format of CSS2, shortcutting to parsing via those rules if possible.

3. Run the junk normalizer. This is where it looks complicated, but mostly it's just finding anything that can't possibly be part of a hexadecimal number and replacing it with one or more zeroes, then doing some padding and truncation to get the end result to come out as a hexadecimal string specifying a 24-bit integer. Then that 24-bit integer is the color value.