Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reset css rules when new css is set #87

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
143 changes: 80 additions & 63 deletions src/CssToInlineStyles.php
Expand Up @@ -22,11 +22,11 @@ class CssToInlineStyles
private $css;

/**
* The processed CSS rules
* The processed CSS rules cache
*
* @var array
*/
private $cssRules;
private $cssRulesCache = array();

/**
* Should the generated HTML be cleaned
Expand Down Expand Up @@ -122,6 +122,7 @@ public function convert($outputXHTML = false)
throw new Exception('No HTML provided.');
}

$cssRules = array();
// should we use inline style-block
if ($this->useInlineStylesBlock) {
// init var
Expand All @@ -130,17 +131,27 @@ public function convert($outputXHTML = false)
// match the style blocks
preg_match_all('|<style(.*)>(.*)</style>|isU', $this->html, $matches);

$inlineCss = '';
// any style-blocks found?
if (!empty($matches[2])) {
// add
foreach ($matches[2] as $match) {
$this->css .= trim($match) . "\n";
$inlineCss .= trim($match) . "\n";
}
}

$cssRules = $this->processCSS($inlineCss);
}

// process css
$this->processCSS();
if ($this->css) {
$cssRules = array_merge($cssRules, $this->processCSS($this->css));
}

// sort based on specificity
if (!empty($cssRules)) {
usort($cssRules, array(__CLASS__, 'sortOnSpecificity'));
}

// create new DOMDocument
$document = new \DOMDocument('1.0', $this->getEncoding());
Expand All @@ -155,9 +166,9 @@ public function convert($outputXHTML = false)
$xPath = new \DOMXPath($document);

// any rules?
if (!empty($this->cssRules)) {
if (!empty($cssRules)) {
// loop rules
foreach ($this->cssRules as $rule) {
foreach ($cssRules as $rule) {
try {
$query = CssSelector::toXPath($rule['selector']);
} catch (ExceptionInterface $e) {
Expand Down Expand Up @@ -413,88 +424,94 @@ private function getEncoding()
/**
* Process the loaded CSS
*
* @return void
* @param string $css
* @return array parsed css rules
*/
private function processCSS()
private function processCSS($css)
{
// init vars
$css = (string) $this->css;
$hash = md5($css);
if (!isset($this->cssRulesCache[$hash])) {
//reset current set of rules
$cssRules = array();
// init vars
$css = (string) $css;

// remove newlines
$css = str_replace(array("\r", "\n"), '', $css);
// remove newlines
$css = str_replace(array("\r", "\n"), '', $css);

// replace double quotes by single quotes
$css = str_replace('"', '\'', $css);
// replace double quotes by single quotes
$css = str_replace('"', '\'', $css);

// remove comments
$css = preg_replace('|/\*.*?\*/|', '', $css);
// remove comments
$css = preg_replace('|/\*.*?\*/|', '', $css);

// remove spaces
$css = preg_replace('/\s\s+/', ' ', $css);
// remove spaces
$css = preg_replace('/\s\s+/', ' ', $css);

if ($this->excludeMediaQueries) {
$css = preg_replace('/@media [^{]*{([^{}]|{[^{}]*})*}/', '', $css);
}
if ($this->excludeMediaQueries) {
$css = preg_replace('/@media [^{]*{([^{}]|{[^{}]*})*}/', '', $css);
}

// rules are splitted by }
$rules = (array) explode('}', $css);
// rules are splitted by }
$rules = (array) explode('}', $css);

// init var
$i = 1;
// init var
$i = 1;

// loop rules
foreach ($rules as $rule) {
// split into chunks
$chunks = explode('{', $rule);
// loop rules
foreach ($rules as $rule) {
// split into chunks
$chunks = explode('{', $rule);

// invalid rule?
if (!isset($chunks[1])) {
continue;
}
// invalid rule?
if (!isset($chunks[1])) {
continue;
}

// set the selectors
$selectors = trim($chunks[0]);
// set the selectors
$selectors = trim($chunks[0]);

// get cssProperties
$cssProperties = trim($chunks[1]);
// get cssProperties
$cssProperties = trim($chunks[1]);

// split multiple selectors
$selectors = (array) explode(',', $selectors);
// split multiple selectors
$selectors = (array) explode(',', $selectors);

// loop selectors
foreach ($selectors as $selector) {
// cleanup
$selector = trim($selector);
// loop selectors
foreach ($selectors as $selector) {
// cleanup
$selector = trim($selector);

// build an array for each selector
$ruleSet = array();
// build an array for each selector
$ruleSet = array();

// store selector
$ruleSet['selector'] = $selector;
// store selector
$ruleSet['selector'] = $selector;

// process the properties
$ruleSet['properties'] = $this->processCSSProperties(
$cssProperties
);
// process the properties
$ruleSet['properties'] = $this->processCSSProperties(
$cssProperties
);

// calculate specificity
$ruleSet['specificity'] = Specificity::fromSelector($selector);

// calculate specificity
$ruleSet['specificity'] = Specificity::fromSelector($selector);
// remember the order in which the rules appear
$ruleSet['order'] = $i;

// remember the order in which the rules appear
$ruleSet['order'] = $i;
// add into global rules
$cssRules[] = $ruleSet;
}

// add into global rules
$this->cssRules[] = $ruleSet;
// increment
$i++;
}

// increment
$i++;
$this->cssRulesCache[$hash] = $cssRules;
}

// sort based on specificity
if (!empty($this->cssRules)) {
usort($this->cssRules, array(__CLASS__, 'sortOnSpecificity'));
}

return $this->cssRulesCache[$hash];
}

/**
Expand Down
41 changes: 41 additions & 0 deletions tests/CssToInlineStylesTest.php
Expand Up @@ -6,6 +6,9 @@

class CssToInlineStylesTest extends \PHPUnit_Framework_TestCase
{
/**
* @var CssToInlineStyles
*/
protected $cssToInlineStyles;

public function setUp()
Expand Down Expand Up @@ -169,6 +172,44 @@ public function testEncoding()
$this->runHTMLToCSS($html, $css, $expected);
}

public function testCssRulesResetDuringSecondLoad()
{
$html = "<p></p>";
$css = 'p { margin: 10px; }';
$expected = '<p style="margin: 10px;"></p>';

$this->runHTMLToCSS($html, $css, $expected);

$html = "<p></p>";
$css = 'p { padding: 10px; }';
$expected = '<p style="padding: 10px;"></p>';
$this->runHTMLToCSS($html, $css, $expected);
}

public function testCssRulesInlineResetDuringSecondLoad()
{
$html = "<p></p>";
$css = 'p { margin: 10px; }';
$expected = '<p style="margin: 10px;"></p>';

$this->runHTMLToCSS($html, $css, $expected);

$html = <<<HTML
<style>
p {
padding: 10px;
}
</style>
<p></p>
HTML;

$css = 'p { margin: 10px; }';

$this->runHTMLToCSS($html, $css, '<p style="margin: 10px;"></p>');
$this->cssToInlineStyles->setUseInlineStylesBlock(true);
$this->runHTMLToCSS('<p></p>', $css, '<p style="margin: 10px;"></p>');
}

private function runHTMLToCSS($html, $css, $expected, $asXHTML = false)
{
$cssToInlineStyles = $this->cssToInlineStyles;
Expand Down