This commit is contained in:
2025-05-12 14:25:25 +02:00
parent ab2db755ef
commit 9e378ca2b7
2719 changed files with 46505 additions and 60181 deletions

View File

@@ -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"
}
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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()
{

View File

@@ -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
);
}

View File

@@ -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;
}