Go crazy? Well, maybe not..
I had another fun encounter with Internet Explorer this week. Apparently IE (6-9) enforces a hard limit for the number of CSS rules that it will accept in a single style sheet. Yup, you read that correctly. Just when I started to forget how much I disliked IE, it goes and proves to me once again just how worthy of my disdain it truly is.
I also feel the need to mention that the only browser that had this problem was IE. But, anyways.. I'm not bitter.
How on Earth Do You End Up With 4095+ CSS Rules?
Now, I feel I should start with a little back story to get you up to speed with how I got here. So, I've been using an application called pdf2htmlEX to convert gigantic PDF files (500MB+, 700+ pages) to HTML. To get the HTML to look almost exactly like the original PDF, the application uses a lot of absolute positioning of text; sometimes even individual letters are positioned absolutely by themselves. Needless to say, this leads to quite a lot of CSS rules. All in all, after converting one of these giant PDF files, I ended up with a style sheet with almost 12,000 CSS rules.
Before I went crazy with writing some code to chop up my giant CSS files into something IE will digest, I decided to test to make sure this was actually the problem. Using my IDE, I manually chopped up the CSS file into chunks of no more than 4095 rules each. Sure enough once I did this, IE decided it would render everything just fine. Amazing.
Great, Now What to Do About It?
Now that I knew for sure what the problem was, and how to fix it, I just had to code it. Here's a mini-library for CodeIgniter that I used to remedy this situation:
0 && isset($matches[0][$max_num_rules]) ) { $pos1 = $matches[0][$max_num_rules - 1][1] + strlen($matches[0][$max_num_rules - 1][0]); $pos2 = $matches[0][$max_num_rules][1]; $chunks[] = substr($css, $offset, $pos1 - $offset); $offset = $pos1; } // The left-overs. $chunks[] = substr($css, $offset); if (count($chunks) > 1) // We may have broken media queries in the CSS, let's fix them. $chunks = $this->fix_broken_media_queries($chunks); return $chunks; } /* Looks for and closes any open media queries in the given CSS. */ protected function fix_broken_media_queries($chunks) { $open_media_queries = array(); foreach ($chunks as $i => $css) { if (count($open_media_queries) > 0) foreach ($open_media_queries as $media_query) $css = $media_query . $css; $n = 0; $offset = 0; while (preg_match('~@media [^\{]+\{~', $css, $matches, PREG_OFFSET_CAPTURE, $offset) === 1) { // Add to the array of open media queries. $open_media_queries[$n] = $matches[0][0]; /* Set 'pos1' to just after the media query declaration. */ $pos1 = $matches[0][1] + strlen($matches[0][0]); $offset = $pos1; // Get the closing bracket of the media query. if (($pos2 = $this->find_next_closing_bracket($css, $offset)) !== false) { // Found it! $offset = ($pos2 + 1); // Remove this media query from the array of open media queries. if (isset($open_media_queries[$n])) unset($open_media_queries[$n]); } else { // Couldn't find the closing bracket for this media query. // Close the open media query for this chunk. $css .= '}'; } $n++; } $chunks[$i] = $css; } return $chunks; } /* Finds the next closing bracket in the given CSS, starting at the given offset. */ protected function find_next_closing_bracket($css, &$offset) { while ( ($pos2 = strpos($css, '}', $offset)) !== false && ($pos3 = strpos($css, '{', $offset)) !== false && $pos3 < $pos2 ) $offset = ($pos2 + 1); return $pos2; } } /* End of file Css.php */ /* Location: ./application/libraries/Css.php */
And an example of the fix in action:
load->library('Css'); $chunks = $this->css->chop($css, 4095); foreach ($chunks as $i => $chunk) { $file = '/file/path/to/css/directory/chunk-' . ($i + 1) . '.css'; file_put_contents($file, $chunk); } } }