Skip to content

Commit

Permalink
[FEATURE] Merge #562 into CssInliner
Browse files Browse the repository at this point in the history
Closes #280.
  • Loading branch information
JakeQZ committed Apr 6, 2018
1 parent b24a7ab commit 7ee93d4
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 25 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Copy matching rules with dynamic pseudo-classes or pseudo-elements in
selectors to the style element
([#280](https://github.com/MyIntervals/emogrifier/issues/280),
[#562](https://github.com/MyIntervals/emogrifier/pull/562))
[#562](https://github.com/MyIntervals/emogrifier/pull/562),
[#567](https://github.com/MyIntervals/emogrifier/pull/567))
- Add a CssToAttributeConverter
([#546](https://github.com/jjriv/emogrifier/pull/546))
- Expose the DOMDocument in AbstractHtmlProcessor
Expand Down
48 changes: 39 additions & 9 deletions src/Emogrifier/CssInliner.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ class CssInliner
*/
const CACHE_KEY_COMBINED_STYLES = 3;

/**
* Regular expression component matching a static pseudo class in a selector, without the preceding ":",
* for which the applicable elements can be determined (by converting the selector to an XPath expression).
* (Contains alternation without a group and is intended to be placed within a capturing, non-capturing or lookahead
* group, as appropriate for the usage context.)
*
* @var string
*/
const PSEUDO_CLASS_MATCHER = '\\S+\\-(?:child|type\\()|not\\([[:ascii:]]*\\)';

/**
* @var string
*/
Expand Down Expand Up @@ -436,23 +446,21 @@ private function parseCssRules($css)
// don't process pseudo-elements and behavioral (dynamic) pseudo-classes;
// only allow structural pseudo-classes
$hasPseudoElement = strpos($selector, '::') !== false;
$hasAnyPseudoClass = (bool)preg_match('/:[a-zA-Z]/', $selector);
$hasSupportedPseudoClass = (bool)preg_match(
'/:(\\S+\\-(child|type\\()|not\\([[:ascii:]]*\\))/i',
$hasUnsupportedPseudoClass = (bool)preg_match(
'/:(?!' . static::PSEUDO_CLASS_MATCHER . ')[\\w-]/i',
$selector
);
if ($hasPseudoElement || ($hasAnyPseudoClass && !$hasSupportedPseudoClass)) {
continue;
}
$hasUnmatchablePseudo = $hasPseudoElement || $hasUnsupportedPseudoClass;

$parsedCssRule = [
'media' => $cssRule['media'],
'selector' => trim($selector),
'hasUnmatchablePseudo' => $hasUnmatchablePseudo,
'declarationsBlock' => $cssDeclaration,
// keep track of where it appears in the file, since order is important
'line' => $key,
];
$ruleType = ($cssRule['media'] === '') ? 'inlineable' : 'uninlineable';
$ruleType = ($cssRule['media'] === '' && !$hasUnmatchablePseudo) ? 'inlineable' : 'uninlineable';
$cssRules[$ruleType][] = $parsedCssRule;
}
}
Expand Down Expand Up @@ -829,8 +837,12 @@ private function copyUninlineableCssToStyleNode(\DOMDocument $xmlDocument, \DOMX
{
$cssRulesRelevantForDocument = array_filter(
$cssRules,
function ($cssRule) use ($xPath) {
return $this->existsMatchForCssSelector($xPath, $cssRule['selector']);
function (array $cssRule) use ($xPath) {
$selector = $cssRule['selector'];
if ($cssRule['hasUnmatchablePseudo']) {
$selector = $this->removeUnmatchablePseudoComponents($selector);
}
return $this->existsMatchForCssSelector($xPath, $selector);
}
);

Expand All @@ -847,6 +859,24 @@ function ($cssRule) use ($xPath) {
$this->addStyleElementToDocument($xmlDocument, $cssConcatenator->getCss());
}

/**
* Removes pseudo-elements and dynamic pseudo-classes from a CSS selector, replacing them with "*" if necessary.
*
* @param string $selector
*
* @return string Selector which will match the relevant DOM elements if the pseudo-classes are assumed to apply,
* or in the case of pseudo-elements will match their originating element.
*/
private function removeUnmatchablePseudoComponents($selector)
{
$pseudoComponentMatcher = ':(?!' . static::PSEUDO_CLASS_MATCHER . '):?+[\\w-]++(?:\\([^\\)]*+\\))?+';
return preg_replace(
['/(\\s|^)' . $pseudoComponentMatcher . '/i', '/' . $pseudoComponentMatcher . '/i'],
['$1*', ''],
$selector
);
}

/**
* Copies $cssRule into the style attribute of $node.
*
Expand Down
Loading

0 comments on commit 7ee93d4

Please sign in to comment.