Chunk CSS into smaller chunks
<?php
function chunk($css, $every = 4095) {
$chunks = array();
$offsets = array();
$offset = 0;
$selectorcount = 0;
$lastvalidoffset = 0;
$lastvalidoffsetselectorcount = 0;
$inrule = 0;
$inmedia = false;
$mediacoming = false;
// Remove the comments. Because it's easier, safer and probably a lot of other good reasons.
$css = preg_replace('#/\*(.*?)\*/#m', '', $css);
$strlen = strlen($css);
// Walk through the CSS content character by character.
for ($i = 1; $i <= $strlen; $i++) {
$char = $css[$i - 1];
$offset = $i;
// Is that a media query that I see coming towards us?
if ($char === '@') {
if (!$inmedia && substr($css, $offset, 5) === 'media') {
$mediacoming = true;
}
}
// So we are entering a rule or a media query...
if ($char === '{') {
if ($mediacoming) {
$inmedia = true;
$mediacoming = false;
} else {
$inrule++;
$selectorcount++;
}
}
// Let's count the number of selectors, but only if we are not in a rule as they
// can contain commas too.
if (!$inrule && $char === ',') {
$selectorcount++;
}
// We reached the end of something.
if ($char === '}') {
// Oh, we are in a media query.
if ($inmedia) {
if (!$inrule) {
// This is the end of the media query, let's note that it is safe to split here.
$inmedia = false;
$lastvalidoffset = $offset;
$lastvalidoffsetselectorcount = $selectorcount;
} else {
// We were in a rule, in the media query.
$inrule--;
}
} else {
$inrule--;
if (!$inrule) {
// We exited the rule, it is safe to split here.
$lastvalidoffset = $offset;
$lastvalidoffsetselectorcount = $selectorcount;
}
}
}
// Alright, this is splitting time...
if ($selectorcount >= $every) {
if (!$lastvalidoffset) {
// We must have reached more selectors into one set than we were allowed. That means that either
// the chunk size value is too small, or that we have a gigantic group of selectors, or that a media
// query contains more selectors than the chunk size. We have to ignore this because we do not
// support split inside a group of selectors or media query.
debugging('Could not find a safe place to split at ' . $offset . ', ignoring...', DEBUG_DEVELOPER);
} else {
// We identify the offset to split at and reset the number of selectors found from there.
$offsets[] = $lastvalidoffset;
$selectorcount = $selectorcount - $lastvalidoffsetselectorcount;
$lastvalidoffset = 0;
}
}
}
// Now that we have got the offets, we can chunk the CSS.
$offsetcount = count($offsets);
foreach ($offsets as $key => $index) {
$start = 0;
if ($key > 0) {
$start = $offsets[$key - 1];
}
// From somewhere up to the offset.
$chunks[] = substr($css, $start, $index - $start);
}
// Add the last chunk, from the last offset to the end of the string.
$chunks[] = substr($css, end($offsets));
return $chunks;
}