🔧
This commit is contained in:
@@ -12,13 +12,15 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.5 || ^7.0 || ^8.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
|
||||
"symfony/css-selector": "^5.4 || ^6.0 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10"
|
||||
"phpunit/phpunit": "^8.5.21 || ^9.5.10",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -32,7 +34,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.2.x-dev"
|
||||
"dev-master": "2.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class Processor
|
||||
{
|
||||
$css = '';
|
||||
$matches = array();
|
||||
$htmlNoComments = preg_replace('|<!--.*?-->|s', '', $html);
|
||||
$htmlNoComments = preg_replace('|<!--.*?-->|s', '', $html) ?? $html;
|
||||
preg_match_all('|<style(?:\s.*)?>(.*)</style>|isU', $htmlNoComments, $matches);
|
||||
|
||||
if (!empty($matches[1])) {
|
||||
@@ -55,15 +55,15 @@ class Processor
|
||||
private function doCleanup($css)
|
||||
{
|
||||
// remove charset
|
||||
$css = preg_replace('/@charset "[^"]++";/', '', $css);
|
||||
$css = preg_replace('/@charset "[^"]++";/', '', $css) ?? $css;
|
||||
// remove media queries
|
||||
$css = preg_replace('/@media [^{]*+{([^{}]++|{[^{}]*+})*+}/', '', $css);
|
||||
$css = preg_replace('/@media [^{]*+{([^{}]++|{[^{}]*+})*+}/', '', $css) ?? $css;
|
||||
|
||||
$css = str_replace(array("\r", "\n"), '', $css);
|
||||
$css = str_replace(array("\t"), ' ', $css);
|
||||
$css = str_replace('"', '\'', $css);
|
||||
$css = preg_replace('|/\*.*?\*/|', '', $css);
|
||||
$css = preg_replace('/\s\s++/', ' ', $css);
|
||||
$css = preg_replace('|/\*.*?\*/|', '', $css) ?? $css;
|
||||
$css = preg_replace('/\s\s++/', ' ', $css) ?? $css;
|
||||
$css = trim($css);
|
||||
|
||||
return $css;
|
||||
|
||||
@@ -50,8 +50,8 @@ class Processor
|
||||
$string = str_replace(array("\r", "\n"), '', $string);
|
||||
$string = str_replace(array("\t"), ' ', $string);
|
||||
$string = str_replace('"', '\'', $string);
|
||||
$string = preg_replace('|/\*.*?\*/|', '', $string);
|
||||
$string = preg_replace('/\s\s+/', ' ', $string);
|
||||
$string = preg_replace('|/\*.*?\*/|', '', $string) ?? $string;
|
||||
$string = preg_replace('/\s\s+/', ' ', $string) ?? $string;
|
||||
|
||||
$string = trim($string);
|
||||
$string = rtrim($string, ';');
|
||||
@@ -66,7 +66,7 @@ class Processor
|
||||
*
|
||||
* @return Property|null
|
||||
*/
|
||||
public function convertToObject($property, Specificity $specificity = null)
|
||||
public function convertToObject($property, ?Specificity $specificity = null)
|
||||
{
|
||||
if (strpos($property, ':') === false) {
|
||||
return null;
|
||||
@@ -91,7 +91,7 @@ class Processor
|
||||
*
|
||||
* @return Property[]
|
||||
*/
|
||||
public function convertArrayToObjects(array $properties, Specificity $specificity = null)
|
||||
public function convertArrayToObjects(array $properties, ?Specificity $specificity = null)
|
||||
{
|
||||
$objects = array();
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ final class Property
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @var Specificity
|
||||
* @var Specificity|null
|
||||
*/
|
||||
private $originalSpecificity;
|
||||
|
||||
@@ -27,7 +27,7 @@ final class Property
|
||||
* @param string $value
|
||||
* @param Specificity|null $specificity
|
||||
*/
|
||||
public function __construct($name, $value, Specificity $specificity = null)
|
||||
public function __construct($name, $value, ?Specificity $specificity = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
@@ -57,7 +57,7 @@ final class Property
|
||||
/**
|
||||
* Get originalSpecificity
|
||||
*
|
||||
* @return Specificity
|
||||
* @return Specificity|null
|
||||
*/
|
||||
public function getOriginalSpecificity()
|
||||
{
|
||||
|
||||
@@ -31,8 +31,8 @@ class Processor
|
||||
$string = str_replace(array("\r", "\n"), '', $string);
|
||||
$string = str_replace(array("\t"), ' ', $string);
|
||||
$string = str_replace('"', '\'', $string);
|
||||
$string = preg_replace('|/\*.*?\*/|', '', $string);
|
||||
$string = preg_replace('/\s\s+/', ' ', $string);
|
||||
$string = preg_replace('|/\*.*?\*/|', '', $string) ?? $string;
|
||||
$string = preg_replace('/\s\s+/', ' ', $string) ?? $string;
|
||||
|
||||
$string = trim($string);
|
||||
$string = rtrim($string, '}');
|
||||
@@ -88,7 +88,7 @@ class Processor
|
||||
*/
|
||||
public function calculateSpecificityBasedOnASelector($selector)
|
||||
{
|
||||
$idSelectorsPattern = " \#";
|
||||
$idSelectorCount = preg_match_all("/ \#/ix", $selector, $matches);
|
||||
$classAttributesPseudoClassesSelectorsPattern = " (\.[\w]+) # classes
|
||||
|
|
||||
\[(\w+) # attributes
|
||||
@@ -105,6 +105,7 @@ class Processor
|
||||
|only-child|only-of-type
|
||||
|empty|contains
|
||||
))";
|
||||
$classAttributesPseudoClassesSelectorCount = preg_match_all("/{$classAttributesPseudoClassesSelectorsPattern}/ix", $selector, $matches);
|
||||
|
||||
$typePseudoElementsSelectorPattern = " ((^|[\s\+\>\~]+)[\w]+ # elements
|
||||
|
|
||||
@@ -114,11 +115,16 @@ class Processor
|
||||
|selection
|
||||
)
|
||||
)";
|
||||
$typePseudoElementsSelectorCount = preg_match_all("/{$typePseudoElementsSelectorPattern}/ix", $selector, $matches);
|
||||
|
||||
if ($idSelectorCount === false || $classAttributesPseudoClassesSelectorCount === false || $typePseudoElementsSelectorCount === false) {
|
||||
throw new \RuntimeException('Failed to calculate specificity based on selector.');
|
||||
}
|
||||
|
||||
return new Specificity(
|
||||
preg_match_all("/{$idSelectorsPattern}/ix", $selector, $matches),
|
||||
preg_match_all("/{$classAttributesPseudoClassesSelectorsPattern}/ix", $selector, $matches),
|
||||
preg_match_all("/{$typePseudoElementsSelectorPattern}/ix", $selector, $matches)
|
||||
$idSelectorCount,
|
||||
$classAttributesPseudoClassesSelectorCount,
|
||||
$typePseudoElementsSelectorCount
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,23 @@
|
||||
|
||||
namespace TijsVerkoyen\CssToInlineStyles;
|
||||
|
||||
use Symfony\Component\CssSelector\CssSelector;
|
||||
use Symfony\Component\CssSelector\CssSelectorConverter;
|
||||
use Symfony\Component\CssSelector\Exception\ExceptionInterface;
|
||||
use TijsVerkoyen\CssToInlineStyles\Css\Processor;
|
||||
use TijsVerkoyen\CssToInlineStyles\Css\Property\Processor as PropertyProcessor;
|
||||
use TijsVerkoyen\CssToInlineStyles\Css\Property\Property;
|
||||
use TijsVerkoyen\CssToInlineStyles\Css\Rule\Processor as RuleProcessor;
|
||||
|
||||
class CssToInlineStyles
|
||||
{
|
||||
/**
|
||||
* @var CssSelectorConverter
|
||||
*/
|
||||
private $cssConverter;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (class_exists('Symfony\Component\CssSelector\CssSelectorConverter')) {
|
||||
$this->cssConverter = new CssSelectorConverter();
|
||||
}
|
||||
$this->cssConverter = new CssSelectorConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,10 +52,10 @@ class CssToInlineStyles
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline the given properties on an given DOMElement
|
||||
* Inline the given properties on a given DOMElement
|
||||
*
|
||||
* @param \DOMElement $element
|
||||
* @param Css\Property\Property[] $properties
|
||||
* @param Property[] $properties
|
||||
*
|
||||
* @return \DOMElement
|
||||
*/
|
||||
@@ -91,7 +92,7 @@ class CssToInlineStyles
|
||||
*
|
||||
* @param \DOMElement $element
|
||||
*
|
||||
* @return Css\Property\Property[]
|
||||
* @return Property[]
|
||||
*/
|
||||
public function getInlineStyles(\DOMElement $element)
|
||||
{
|
||||
@@ -130,12 +131,25 @@ class CssToInlineStyles
|
||||
// retrieve the document element
|
||||
// we do it this way to preserve the utf-8 encoding
|
||||
$htmlElement = $document->documentElement;
|
||||
|
||||
if ($htmlElement === null) {
|
||||
throw new \RuntimeException('Failed to get HTML from empty document.');
|
||||
}
|
||||
|
||||
$html = $document->saveHTML($htmlElement);
|
||||
|
||||
if ($html === false) {
|
||||
throw new \RuntimeException('Failed to get HTML from document.');
|
||||
}
|
||||
|
||||
$html = trim($html);
|
||||
|
||||
// retrieve the doctype
|
||||
$document->removeChild($htmlElement);
|
||||
$doctype = $document->saveHTML();
|
||||
if ($doctype === false) {
|
||||
$doctype = '';
|
||||
}
|
||||
$doctype = trim($doctype);
|
||||
|
||||
// if it is the html5 doctype convert it to lowercase
|
||||
@@ -158,6 +172,7 @@ class CssToInlineStyles
|
||||
return $document;
|
||||
}
|
||||
|
||||
/** @var \SplObjectStorage<\DOMElement, array<string, Property>> $propertyStorage */
|
||||
$propertyStorage = new \SplObjectStorage();
|
||||
|
||||
$xPath = new \DOMXPath($document);
|
||||
@@ -166,12 +181,7 @@ class CssToInlineStyles
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
try {
|
||||
if (null !== $this->cssConverter) {
|
||||
$expression = $this->cssConverter->toXPath($rule->getSelector());
|
||||
} else {
|
||||
// Compatibility layer for Symfony 2.7 and older
|
||||
$expression = CssSelector::toXPath($rule->getSelector());
|
||||
}
|
||||
$expression = $this->cssConverter->toXPath($rule->getSelector());
|
||||
} catch (ExceptionInterface $e) {
|
||||
continue;
|
||||
}
|
||||
@@ -183,6 +193,7 @@ class CssToInlineStyles
|
||||
}
|
||||
|
||||
foreach ($elements as $element) {
|
||||
\assert($element instanceof \DOMElement);
|
||||
$propertyStorage[$element] = $this->calculatePropertiesToBeApplied(
|
||||
$rule->getProperties(),
|
||||
$propertyStorage->contains($element) ? $propertyStorage[$element] : array()
|
||||
@@ -200,12 +211,12 @@ class CssToInlineStyles
|
||||
/**
|
||||
* Merge the CSS rules to determine the applied properties.
|
||||
*
|
||||
* @param Css\Property\Property[] $properties
|
||||
* @param Css\Property\Property[] $cssProperties existing applied properties indexed by name
|
||||
* @param Property[] $properties
|
||||
* @param array<string, Property> $cssProperties existing applied properties indexed by name
|
||||
*
|
||||
* @return Css\Property\Property[] updated properties, indexed by name
|
||||
* @return array<string, Property> updated properties, indexed by name
|
||||
*/
|
||||
private function calculatePropertiesToBeApplied(array $properties, array $cssProperties)
|
||||
private function calculatePropertiesToBeApplied(array $properties, array $cssProperties): array
|
||||
{
|
||||
if (empty($properties)) {
|
||||
return $cssProperties;
|
||||
@@ -223,6 +234,8 @@ class CssToInlineStyles
|
||||
//overrule if current property is important and existing is not, else check specificity
|
||||
$overrule = !$existingProperty->isImportant() && $property->isImportant();
|
||||
if (!$overrule) {
|
||||
\assert($existingProperty->getOriginalSpecificity() !== null, 'Properties created for parsed CSS always have their associated specificity.');
|
||||
\assert($property->getOriginalSpecificity() !== null, 'Properties created for parsed CSS always have their associated specificity.');
|
||||
$overrule = $existingProperty->getOriginalSpecificity()->compareTo($property->getOriginalSpecificity()) <= 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user