#5410·cheerio

.css() splits a declaration when a data URI's semicolon is followed by a colon

Author: JaybhadeCreated Aug 6, 2026Updated Aug 6, 2026

.css() still splits a declaration when the part after a semicolon contains a colon, and rewriting an unrelated property then corrupts the style attribute.

Tested on cheerio 1.2.0 (and current main).

Reproduction

javascript
const cheerio = require('cheerio');

const style = `background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>")`;
const $ = cheerio.load(`<div style='${style}'></div>`);
const $div = $('div');

console.log($div.css());
$div.css('color', 'red');
console.log($div.attr('style'));

Expected

javascript
{ background: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>")` }
// background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>"); color: red;

Actual

javascript
{
  background: 'url("data:image/svg+xml',
  "utf8,<svg xmlns='http": "//www.w3.org/2000/svg'/>\")"
}
// background: url("data:image/svg+xml; utf8,<svg xmlns='http: //www.w3.org/2000/svg'/>"); color: red;

Two separate problems:

  1. The declaration is split at the ; inside the data URI, producing a bogus second property.
  2. Because .css(prop, val) re-serialises everything it parsed, setting an unrelated property writes the mangled value back to the element. The URL now contains ; and : , so the image silently stops loading. Nothing in the caller's code touched background.

Why the #1134 fix doesn't cover this

#1134 (and #907 before it) fixed the same split for url(data:image/png;base64,…). The fix keeps a fragment that has no colon as a continuation of the previous value, which works for base64,…. But a data URI whose text after the ; does contain a colon takes the other branch and becomes a new property. xmlns='http://www.w3.org/2000/svg' is the common case here, since inline SVG data URIs almost always carry that attribute.

The same applies to any quoted value holding a ; and a ::

javascript
cheerio.load(`<li style="content: 'a;b:c'">`)('li').css();
// { content: "'a", b: "c'" }   expected: { content: "'a;b:c'" }

Per CSS syntax, a ; or : inside a string or inside parentheses is part of the value and does not separate declarations. Checking the same inputs through a spec-compliant parser (postcss) returns a single declaration in each case.

Fix

Skipping over quoted sections and parentheses when scanning for the separating ; and : handles all of these, and subsumes the base64 case that motivated #1134 rather than special-casing it. Happy to open a PR — I have the change and tests ready.