From bc9c82443bea26ffe70f27dd7f330695f339c8cc Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 17:09:55 +0100
Subject: [PATCH 01/39] Beter .travis.yml file
---
.travis.yml | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 1fd67f5..2ee4b33 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,20 +1,20 @@
language: php
-php:
- - 5.3
- - 5.4
- - 5.5
- - 5.6
- - 7.0
- - hhvm
-
matrix:
+ include:
+ - php: 5.4
+ - php: 5.5
+ - php: 5.6
+ - php: 7
+ - php: nightly
+ - php: hhvm
allow_failures:
- - php: 7.0
+ - php: nightly
+ - php: hhvm
before_script:
- - travis_retry composer self-update
- - travis_retry composer install --no-interaction --prefer-source --dev
+ - composer self-update
+ - composer install --prefer-source --no-interaction --no-scripts
script:
- - vendor/bin/phpunit --verbose --coverage-text
+ - bin/phpunit --verbose --coverage-text
From 2e811ee07fc979064d3d10357cc6bc300eda1348 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 17:10:53 +0100
Subject: [PATCH 02/39] use .dist-file
---
phpunit.xml => phpunit.xml.dist | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
rename phpunit.xml => phpunit.xml.dist (86%)
diff --git a/phpunit.xml b/phpunit.xml.dist
similarity index 86%
rename from phpunit.xml
rename to phpunit.xml.dist
index b28724e..b08d7f6 100644
--- a/phpunit.xml
+++ b/phpunit.xml.dist
@@ -1,9 +1,7 @@
-
-
+
src
+ tests
vendor
From d914bfc7b66f2b3acbb8ba21c64b5ca0e2924778 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 17:12:39 +0100
Subject: [PATCH 03/39] Gemfile isn't relevant
---
.gitignore | 1 -
Gemfile | 3 ---
2 files changed, 4 deletions(-)
delete mode 100644 Gemfile
diff --git a/.gitignore b/.gitignore
index 0ba6972..8b7ef35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
/vendor
composer.lock
-Gemfile.lock
diff --git a/Gemfile b/Gemfile
deleted file mode 100644
index 54a7b0b..0000000
--- a/Gemfile
+++ /dev/null
@@ -1,3 +0,0 @@
-source 'http://rubygems.org'
-
-gem 'travis-lint'
From 8834d44c70e52949b35df6ed98a64e29bd71b7eb Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 17:27:12 +0100
Subject: [PATCH 04/39] The 2.0 version is a major overhaul, which is *not*
backwards compatible.
* From now on you can re-use the class for multiple mails.
* A lot less complicated options, as in: no more options at all.
* More separate classes which handle their own (tested) methods.
* A lot more tests
The reason why I did this was to made the class more usable.
---
CHANGELOG.md | 11 +
README.md | 29 +-
composer.json | 28 +-
example/config.php | 8 -
example/index.php | 9 +-
src/Css/Processor.php | 42 ++
src/Css/Property/Processor.php | 107 ++++
src/Css/Property/Property.php | 72 +++
src/Css/Rule/Processor.php | 117 ++++
src/Css/Rule/Rule.php | 78 +++
src/Css/Specificity/Specificity.php | 135 +++++
src/CssToInlineStyles.php | 699 +++-------------------
src/Exception.php | 7 +-
src/Specificity.php | 133 ----
tests/Css/ProcessorTest.php | 71 +++
tests/Css/Property/ProcessorTest.php | 74 +++
tests/Css/Property/PropertyTest.php | 35 ++
tests/Css/Rule/ProcessorTest.php | 45 ++
tests/Css/Rule/RuleTest.php | 28 +
tests/Css/Specificity/SpecificityTest.php | 76 +++
tests/CssToInlineStylesTest.php | 241 --------
tests/CssToOnlineStylesTest.php | 110 ++++
tests/SpecificityTest.php | 83 ---
23 files changed, 1100 insertions(+), 1138 deletions(-)
delete mode 100644 example/config.php
create mode 100644 src/Css/Processor.php
create mode 100644 src/Css/Property/Processor.php
create mode 100644 src/Css/Property/Property.php
create mode 100644 src/Css/Rule/Processor.php
create mode 100644 src/Css/Rule/Rule.php
create mode 100644 src/Css/Specificity/Specificity.php
delete mode 100644 src/Specificity.php
create mode 100644 tests/Css/ProcessorTest.php
create mode 100644 tests/Css/Property/ProcessorTest.php
create mode 100644 tests/Css/Property/PropertyTest.php
create mode 100644 tests/Css/Rule/ProcessorTest.php
create mode 100644 tests/Css/Rule/RuleTest.php
create mode 100644 tests/Css/Specificity/SpecificityTest.php
delete mode 100644 tests/CssToInlineStylesTest.php
create mode 100644 tests/CssToOnlineStylesTest.php
delete mode 100644 tests/SpecificityTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99a22f9..59cb213 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+# Changelog since 1.5.4
+
+The 2.0 version is a major overhaul, which is *not* backwards compatible.
+
+* From now on you can re-use the class for multiple mails.
+* A lot less complicated options, as in: no more options at all.
+* More separate classes which handle their own (tested) methods.
+* A lot more tests
+
+The reason why I did this was to made the class more usable.
+
# Changelog since 1.5.3
* Fix properties split on base64 encoded url content, thx to [tguyard](https://github.com/Giga-gg),
diff --git a/README.md b/README.md
index d56f79a..00d226f 100755
--- a/README.md
+++ b/README.md
@@ -22,28 +22,17 @@ $ composer require tijsverkoyen/css-to-inline-styles
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
- // Convert HTML + CSS to HTML with inlined CSS
- $cssToInlineStyles= new CssToInlineStyles();
- $cssToInlineStyles->setHTML($html);
- $cssToInlineStyles->setCSS($css);
- $html = $cssToInlineStyles->convert();
+ // create instance
+ $cssToInlineStyles = new CssToInlineStyles();
- // Or use inline-styles blocks from the HTML as CSS
- $cssToInlineStyles = new CssToInlineStyles($html);
- $cssToInlineStyles->setUseInlineStylesBlock(true);
- $html = $cssToInlineStyles->convert();
+ $html = file_get_contents(__DIR__ . '/examples/sumo/index.htm');
+ $css = file_get_contents(__DIR__ . '/examples/sumo/style.css');
-
-## Documentation
-
-The following properties exists and have get/set methods available:
-
-Property | Default | Description
--------|---------|------------
-cleanup|false|Should the generated HTML be cleaned?
-useInlineStylesBlock |false|Use inline-styles block as CSS.
-stripOriginalStyleTags |false|Strip original style tags.
-excludeMediaQueries |true|Exclude the media queries from the inlined styles.
+ // output
+ echo $cssToInlineStyles->convert(
+ $html,
+ $css
+ );
## Known issues
diff --git a/composer.json b/composer.json
index 4e6b92b..6f6d1b2 100644
--- a/composer.json
+++ b/composer.json
@@ -1,31 +1,25 @@
{
- "name": "tijsverkoyen/css-to-inline-styles",
- "type": "library",
+ "name": "tijsverkoyen/css-to-inline-styles",
+ "type": "library",
"description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.",
- "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
- "license": "BSD",
- "authors": [
+ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
+ "license": "BSD",
+ "authors": [
{
- "name": "Tijs Verkoyen",
+ "name": "Tijs Verkoyen",
"email": "css_to_inline_styles@verkoyen.eu",
- "role": "Developer"
+ "role": "Developer"
}
],
- "require": {
- "php": ">=5.3.0",
- "symfony/css-selector": "~2.1"
+ "require": {
+ "symfony/css-selector": "^2.7"
},
"require-dev": {
- "phpunit/phpunit": "~4.0"
+ "phpunit/phpunit": "~5.0"
},
- "autoload": {
+ "autoload": {
"psr-4": {
"TijsVerkoyen\\CssToInlineStyles\\": "src"
}
- },
- "extra": {
- "branch-alias": {
- "dev-master": "1.5.x-dev"
- }
}
}
diff --git a/example/config.php b/example/config.php
deleted file mode 100644
index 8ca14dc..0000000
--- a/example/config.php
+++ /dev/null
@@ -1,8 +0,0 @@
-setHTML($html);
-$cssToInlineStyles->setCSS($css);
-
// output
-echo $cssToInlineStyles->convert();
+echo $cssToInlineStyles->convert(
+ $html,
+ $css
+);
diff --git a/src/Css/Processor.php b/src/Css/Processor.php
new file mode 100644
index 0000000..9bb8e43
--- /dev/null
+++ b/src/Css/Processor.php
@@ -0,0 +1,42 @@
+doCleanup($css);
+ $rulesProcessor = new RuleProcessor();
+ $rules = $rulesProcessor->splitIntoSeparateRules($css);
+
+ return $rulesProcessor->convertArrayToObjects($rules);
+ }
+
+ /**
+ * @param $css
+ * @return mixed|string
+ */
+ protected function doCleanup($css)
+ {
+ // remove media queries
+ $css = preg_replace('/@media [^{]*{([^{}]|{[^{}]*})*}/', '', $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 = trim($css);
+
+ return $css;
+ }
+}
diff --git a/src/Css/Property/Processor.php b/src/Css/Property/Processor.php
new file mode 100644
index 0000000..dcbd733
--- /dev/null
+++ b/src/Css/Property/Processor.php
@@ -0,0 +1,107 @@
+cleanup($propertiesString);
+
+ $properties = (array) explode(';', $propertiesString);
+ $keysToRemove = array();
+
+ for ($i = 0; $i < count($properties); $i++) {
+ $properties[$i] = trim($properties[$i]);
+
+ // if the new property begins with base64 it is part of the current property
+ if (isset($properties[$i + 1]) && strpos(trim($properties[$i + 1]), 'base64,') === 0) {
+ $properties[$i] .= ';' . trim($properties[$i + 1]);
+ $keysToRemove[] = $i + 1;
+ }
+ }
+
+ if (!empty($keysToRemove)) {
+ foreach ($keysToRemove as $key) {
+ unset($properties[$key]);
+ }
+ }
+
+ return array_values($properties);
+ }
+
+ /**
+ * @param $string
+ * @return mixed|string
+ */
+ protected function cleanup($string)
+ {
+ $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 = trim($string);
+ $string = rtrim($string, ';');
+
+ return $string;
+ }
+
+ /**
+ * Convert a property-string into an object
+ *
+ * @param string $property
+ * @return Property|null
+ */
+ public function convertToObject($property)
+ {
+ $chunks = (array) explode(':', $property, 2);
+
+ if (!isset($chunks[1]) || $chunks[1] == '') {
+ return null;
+ }
+
+ return new Property(trim($chunks[0]), trim($chunks[1]));
+ }
+
+ /**
+ * Convert an array of property-strings into objects
+ *
+ * @param array $properties
+ * @return array
+ */
+ public function convertArrayToObjects(array $properties)
+ {
+ $objects = array();
+
+ foreach ($properties as $property) {
+ $objects[] = $this->convertToObject($property);
+ }
+
+ return $objects;
+ }
+
+ /**
+ * Build the property-string for multiple properties
+ *
+ * @param array $properties
+ * @return string
+ */
+ public function buildPropertiesString(array $properties)
+ {
+ $chunks = array();
+
+ foreach ($properties as $property) {
+ $chunks[] = $property->toString();
+ }
+
+ return implode(' ', $chunks);
+ }
+}
diff --git a/src/Css/Property/Property.php b/src/Css/Property/Property.php
new file mode 100644
index 0000000..1e83f35
--- /dev/null
+++ b/src/Css/Property/Property.php
@@ -0,0 +1,72 @@
+name = $name;
+ $this->value = $value;
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Is this property important?
+ *
+ * @return bool
+ */
+ public function isImportant()
+ {
+ return (stristr($this->value, '!important') !== false);
+ }
+
+ /**
+ * Get the textual representation of the property
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return sprintf(
+ '%1$s: %2$s;',
+ $this->name,
+ $this->value
+ );
+ }
+}
diff --git a/src/Css/Rule/Processor.php b/src/Css/Rule/Processor.php
new file mode 100644
index 0000000..a7309ef
--- /dev/null
+++ b/src/Css/Rule/Processor.php
@@ -0,0 +1,117 @@
+cleanup($rulesString);
+
+ return (array) explode('}', $rulesString);
+ }
+
+ /**
+ * @param $string
+ * @return mixed|string
+ */
+ protected function cleanup($string)
+ {
+ $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 = trim($string);
+ $string = rtrim($string, '}');
+
+ return $string;
+ }
+
+ /**
+ * Convert a rule-string into an object
+ *
+ * @param string $rule
+ * @param int $orginalOrder
+ * @return array
+ */
+ public function convertToObjects($rule, $orginalOrder)
+ {
+ $rule = $this->cleanup($rule);
+
+ $chunks = explode('{', $rule);
+ if (!isset($chunks[1])) {
+ return array();
+ }
+ $propertiesProcessor = new PropertyProcessor();
+ $rules = array();
+ $selectors = (array) explode(',', trim($chunks[0]));
+ $properties = $propertiesProcessor->splitIntoSeparateProperties($chunks[1]);
+
+ foreach ($selectors as $selector) {
+ $selector = trim($selector);
+
+// $ruleSet['specificity'] = Specificity::fromSelector($selector);
+
+ $rules[] = new Rule(
+ $selector,
+ $propertiesProcessor->convertArrayToObjects($properties),
+ Specificity::fromSelector($selector),
+ $orginalOrder
+ );
+ }
+
+ return $rules;
+ }
+
+ /**
+ * @param array $rules
+ * @return array
+ */
+ public function convertArrayToObjects(array $rules)
+ {
+ $objects = array();
+
+ $order = 1;
+ foreach ($rules as $rule) {
+ $objects = array_merge($objects, $this->convertToObjects($rule, $order));
+ $order++;
+ }
+
+ if (!empty($objects)) {
+ usort($objects, array(__CLASS__, 'sortOnSpecificity'));
+ }
+
+ return $objects;
+ }
+
+ /**
+ * Sort an array on the specificity element
+ *
+ * @return int
+ * @param array $e1 The first element.
+ * @param array $e2 The second element.
+ */
+ private static function sortOnSpecificity($e1, $e2)
+ {
+ // Compare the specificity
+ $value = $e1['specificity']->compareTo($e2['specificity']);
+
+ // if the specificity is the same, use the order in which the element appeared
+ if ($value === 0) {
+ $value = $e1['order'] - $e2['order'];
+ }
+
+ return $value;
+ }
+}
diff --git a/src/Css/Rule/Rule.php b/src/Css/Rule/Rule.php
new file mode 100644
index 0000000..2ddcccd
--- /dev/null
+++ b/src/Css/Rule/Rule.php
@@ -0,0 +1,78 @@
+selector = $selector;
+ $this->properties = $properties;
+ $this->specificity = $specificity;
+ $this->order = $order;
+ }
+
+ /**
+ * Get selector
+ *
+ * @return string
+ */
+ public function getSelector()
+ {
+ return $this->selector;
+ }
+
+ /**
+ * Get properties
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * Get specificity
+ *
+ * @return mixed
+ */
+ public function getSpecificity()
+ {
+ return $this->specificity;
+ }
+
+ /**
+ * Get order
+ *
+ * @return int
+ */
+ public function getOrder()
+ {
+ return $this->order;
+ }
+}
diff --git a/src/Css/Specificity/Specificity.php b/src/Css/Specificity/Specificity.php
new file mode 100644
index 0000000..13e5f3b
--- /dev/null
+++ b/src/Css/Specificity/Specificity.php
@@ -0,0 +1,135 @@
+numberOfIdSelectors = $numberOfIdSelectors;
+ $this->numberOfClassAttributesPseudoClassSelectors = $numberOfClassAttributesPseudoClassSelectors;
+ $this->numberOfTypePseudoElementSelectors = $numberOfTypePseudoElementSelectors;
+ }
+
+ /**
+ * Increase the current specificity by adding the three values
+ *
+ * @param int $numberOfIdSelectors The number of ID selectors in the selector
+ * @param int $numberOfClassAttributesPseudoClassSelectors The number of class selectors, attributes selectors, and pseudo-classes in the selector
+ * @param int $numberOfTypePseudoElementSelectors The number of type selectors and pseudo-elements in the selector
+ */
+ public function increase(
+ $numberOfIdSelectors,
+ $numberOfClassAttributesPseudoClassSelectors,
+ $numberOfTypePseudoElementSelectors
+ ) {
+ $this->numberOfIdSelectors += $numberOfIdSelectors;
+ $this->numberOfClassAttributesPseudoClassSelectors += $numberOfClassAttributesPseudoClassSelectors;
+ $this->numberOfTypePseudoElementSelectors += $numberOfTypePseudoElementSelectors;
+ }
+
+ /**
+ * Get the specificity values as an array
+ *
+ * @return array
+ */
+ public function getValues()
+ {
+ return array(
+ $this->numberOfIdSelectors,
+ $this->numberOfClassAttributesPseudoClassSelectors,
+ $this->numberOfTypePseudoElementSelectors
+ );
+ }
+
+ /**
+ * Calculate the specificity based on a CSS Selector string,
+ * Based on the patterns from premailer/css_parser by Alex Dunae
+ *
+ * @see https://github.com/premailer/css_parser/blob/master/lib/css_parser/regexps.rb
+ * @param string $selector
+ * @return static
+ */
+ public static function fromSelector($selector)
+ {
+ $idSelectorsPattern = " \#";
+ $classAttributesPseudoClassesSelectorsPattern = " (\.[\w]+) # classes
+ |
+ \[(\w+) # attributes
+ |
+ (\:( # pseudo classes
+ link|visited|active
+ |hover|focus
+ |lang
+ |target
+ |enabled|disabled|checked|indeterminate
+ |root
+ |nth-child|nth-last-child|nth-of-type|nth-last-of-type
+ |first-child|last-child|first-of-type|last-of-type
+ |only-child|only-of-type
+ |empty|contains
+ ))";
+
+ $typePseudoElementsSelectorPattern = " ((^|[\s\+\>\~]+)[\w]+ # elements
+ |
+ \:{1,2}( # pseudo-elements
+ after|before
+ |first-letter|first-line
+ |selection
+ )
+ )";
+
+ return new static(
+ preg_match_all("/{$idSelectorsPattern}/ix", $selector, $matches),
+ preg_match_all("/{$classAttributesPseudoClassesSelectorsPattern}/ix", $selector, $matches),
+ preg_match_all("/{$typePseudoElementsSelectorPattern}/ix", $selector, $matches)
+ );
+ }
+
+ /**
+ * Returns <0 when $specificity is greater, 0 when equal, >0 when smaller
+ *
+ * @param Specificity $specificity
+ * @return int
+ */
+ public function compareTo(Specificity $specificity)
+ {
+ if ($this->numberOfIdSelectors !== $specificity->numberOfIdSelectors) {
+ return $this->numberOfIdSelectors - $specificity->numberOfIdSelectors;
+ } elseif ($this->numberOfClassAttributesPseudoClassSelectors !== $specificity->numberOfClassAttributesPseudoClassSelectors) {
+ return $this->numberOfClassAttributesPseudoClassSelectors - $specificity->numberOfClassAttributesPseudoClassSelectors;
+ } else {
+ return $this->numberOfTypePseudoElementSelectors - $specificity->numberOfTypePseudoElementSelectors;
+ }
+ }
+}
diff --git a/src/CssToInlineStyles.php b/src/CssToInlineStyles.php
index acf610a..927a305 100644
--- a/src/CssToInlineStyles.php
+++ b/src/CssToInlineStyles.php
@@ -1,682 +1,129 @@
- * @version 1.5.4
- * @copyright Copyright (c), Tijs Verkoyen. All rights reserved.
- * @license Revised BSD License
- */
class CssToInlineStyles
{
- /**
- * The CSS to use
- *
- * @var string
- */
- private $css;
-
- /**
- * The processed CSS rules
- *
- * @var array
- */
- private $cssRules;
-
- /**
- * Should the generated HTML be cleaned
- *
- * @var bool
- */
- private $cleanup = false;
-
- /**
- * The encoding to use.
- *
- * @var string
- */
- private $encoding = 'UTF-8';
-
- /**
- * The HTML to process
- *
- * @var string
- */
- private $html;
-
- /**
- * Use inline-styles block as CSS
- *
- * @var bool
- */
- private $useInlineStylesBlock = false;
-
- /**
- * Strip original style tags
- *
- * @var bool
- */
- private $stripOriginalStyleTags = false;
-
- /**
- * Exclude the media queries from the inlined styles
- *
- * @var bool
- */
- private $excludeMediaQueries = true;
-
- /**
- * Creates an instance, you could set the HTML and CSS here, or load it
- * later.
- *
- * @return void
- * @param string [optional] $html The HTML to process.
- * @param string [optional] $css The CSS to use.
- */
- public function __construct($html = null, $css = null)
- {
- if ($html !== null) {
- $this->setHTML($html);
- }
- if ($css !== null) {
- $this->setCSS($css);
- }
- }
-
- /**
- * Remove id and class attributes.
- *
- * @return string
- * @param \DOMXPath $xPath The DOMXPath for the entire document.
- */
- private function cleanupHTML(\DOMXPath $xPath)
+ public function convert($html, $css)
{
- $nodes = $xPath->query('//@class | //@id');
+ $document = $this->createDomDocumentFromHtml($html);
+ $rules = (new Processor())->getRules($css);
+ $document = $this->inline($document, $rules);
- foreach ($nodes as $node) {
- $node->ownerElement->removeAttributeNode($node);
- }
+ return $this->getHtmlFromDocument($document);
}
/**
- * Converts the loaded HTML into an HTML-string with inline styles based on the loaded CSS
+ * Inline the given properties on a DOMElement
*
- * @return string
- * @param bool [optional] $outputXHTML Should we output valid XHTML?
+ * @param \DOMElement $element
+ * @param array $properties
+ * @return \DOMElement
*/
- public function convert($outputXHTML = false)
+ public function inlineCssOnElement(\DOMElement $element, array $properties)
{
- // redefine
- $outputXHTML = (bool) $outputXHTML;
-
- // validate
- if ($this->html == null) {
- throw new Exception('No HTML provided.');
+ if (empty($properties)) {
+ return $element;
}
- // should we use inline style-block
- if ($this->useInlineStylesBlock) {
- // init var
- $matches = array();
+ $propertyProcessor = new PropertyProcessor();
+ $cssProperties = array();
+ $currentStyles = $element->attributes->getNamedItem('style');
- // match the style blocks
- preg_match_all('||isU', $this->html, $matches);
+ if ($currentStyles !== null) {
+ $currentProperties = $propertyProcessor->convertArrayToObjects(
+ $propertyProcessor->splitIntoSeparateProperties($currentStyles->value)
+ );
- // any style-blocks found?
- if (!empty($matches[2])) {
- // add
- foreach ($matches[2] as $match) {
- $this->css .= trim($match) . "\n";
- }
+ foreach ($currentProperties as $property) {
+ $cssProperties[$property->getName()] = $property;
}
}
- // process css
- $this->processCSS();
-
- // create new DOMDocument
- $document = new \DOMDocument('1.0', $this->getEncoding());
-
- // set error level
- $internalErrors = libxml_use_internal_errors(true);
-
- // load HTML
- $document->loadHTML($this->html);
-
- // Restore error level
- libxml_use_internal_errors($internalErrors);
-
- // create new XPath
- $xPath = new \DOMXPath($document);
-
- // any rules?
- if (!empty($this->cssRules)) {
- // loop rules
- foreach ($this->cssRules as $rule) {
- try {
- $query = CssSelector::toXPath($rule['selector']);
- } catch (ExceptionInterface $e) {
- continue;
- }
-
- // search elements
- $elements = $xPath->query($query);
-
- // validate elements
- if ($elements === false) {
- continue;
- }
-
- // loop found elements
- foreach ($elements as $element) {
- // no styles stored?
- if ($element->attributes->getNamedItem(
- 'data-css-to-inline-styles-original-styles'
- ) == null
- ) {
- // init var
- $originalStyle = '';
- if ($element->attributes->getNamedItem('style') !== null) {
- $originalStyle = $element->attributes->getNamedItem('style')->value;
- }
-
- // store original styles
- $element->setAttribute(
- 'data-css-to-inline-styles-original-styles',
- $originalStyle
- );
-
- // clear the styles
- $element->setAttribute('style', '');
- }
-
- // init var
- $properties = array();
-
- // get current styles
- $stylesAttribute = $element->attributes->getNamedItem('style');
-
- // any styles defined before?
- if ($stylesAttribute !== null) {
- // get value for the styles attribute
- $definedStyles = (string) $stylesAttribute->value;
-
- // split into properties
- $definedProperties = $this->splitIntoProperties($definedStyles);
- // loop properties
- foreach ($definedProperties as $property) {
- // validate property
- if ($property == '') {
- continue;
- }
-
- // split into chunks
- $chunks = (array) explode(':', trim($property), 2);
-
- // validate
- if (!isset($chunks[1])) {
- continue;
- }
-
- // loop chunks
- $properties[$chunks[0]] = trim($chunks[1]);
- }
- }
-
- // add new properties into the list
- foreach ($rule['properties'] as $key => $value) {
- // If one of the rules is already set and is !important, don't apply it,
- // except if the new rule is also important.
- if (
- !isset($properties[$key])
- || stristr($properties[$key], '!important') === false
- || (stristr(implode('', $value), '!important') !== false)
- ) {
- $properties[$key] = $value;
- }
- }
-
- // build string
- $propertyChunks = array();
-
- // build chunks
- foreach ($properties as $key => $values) {
- foreach ((array) $values as $value) {
- $propertyChunks[] = $key . ': ' . $value . ';';
- }
- }
-
- // build properties string
- $propertiesString = implode(' ', $propertyChunks);
-
- // set attribute
- if ($propertiesString != '') {
- $element->setAttribute('style', $propertiesString);
- }
- }
- }
-
- // reapply original styles
- // search elements
- $elements = $xPath->query('//*[@data-css-to-inline-styles-original-styles]');
-
- // loop found elements
- foreach ($elements as $element) {
- // get the original styles
- $originalStyle = $element->attributes->getNamedItem(
- 'data-css-to-inline-styles-original-styles'
- )->value;
-
- if ($originalStyle != '') {
- $originalProperties = array();
- $originalStyles = $this->splitIntoProperties($originalStyle);
-
- foreach ($originalStyles as $property) {
- // validate property
- if ($property == '') {
- continue;
- }
-
- // split into chunks
- $chunks = (array) explode(':', trim($property), 2);
-
- // validate
- if (!isset($chunks[1])) {
- continue;
- }
-
- // loop chunks
- $originalProperties[$chunks[0]] = trim($chunks[1]);
- }
-
- // get current styles
- $stylesAttribute = $element->attributes->getNamedItem('style');
- $properties = array();
-
- // any styles defined before?
- if ($stylesAttribute !== null) {
- // get value for the styles attribute
- $definedStyles = (string) $stylesAttribute->value;
-
- // split into properties
- $definedProperties = $this->splitIntoProperties($definedStyles);
-
- // loop properties
- foreach ($definedProperties as $property) {
- // validate property
- if ($property == '') {
- continue;
- }
-
- // split into chunks
- $chunks = (array) explode(':', trim($property), 2);
-
- // validate
- if (!isset($chunks[1])) {
- continue;
- }
-
- // loop chunks
- $properties[$chunks[0]] = trim($chunks[1]);
- }
- }
-
- // add new properties into the list
- foreach ($originalProperties as $key => $value) {
- $properties[$key] = $value;
- }
-
- // build string
- $propertyChunks = array();
-
- // build chunks
- foreach ($properties as $key => $values) {
- foreach ((array) $values as $value) {
- $propertyChunks[] = $key . ': ' . $value . ';';
- }
- }
-
- // build properties string
- $propertiesString = implode(' ', $propertyChunks);
-
- // set attribute
- if ($propertiesString != '') {
- $element->setAttribute(
- 'style',
- $propertiesString
- );
- }
+ foreach ($properties as $property) {
+ if (isset($cssProperties[$property->getName()])) {
+ // only overrule it if the the not-inline-style property is important
+ if ($property->isImportant()) {
+ $cssProperties[$property->getName()] = $property;
}
-
- // remove placeholder
- $element->removeAttribute(
- 'data-css-to-inline-styles-original-styles'
- );
+ } else {
+ $cssProperties[$property->getName()] = $property;
}
}
- // strip original style tags if we need to
- if ($this->stripOriginalStyleTags) {
- $this->stripOriginalStyleTags($xPath);
- }
-
- // cleanup the HTML if we need to
- if ($this->cleanup) {
- $this->cleanupHTML($xPath);
- }
+ $element->setAttribute(
+ 'style',
+ $propertyProcessor->buildPropertiesString(array_values($cssProperties))
+ );
- // should we output XHTML?
- if ($outputXHTML) {
- // set formating
- $document->formatOutput = true;
-
- // get the HTML as XML
- $html = $document->saveXML(null, LIBXML_NOEMPTYTAG);
-
- // remove the XML-header
- $html = ltrim(preg_replace('/<\?xml (.*)\?>/', '', $html));
- } // just regular HTML 4.01 as it should be used in newsletters
- else {
- // get the HTML
- $html = $document->saveHTML();
- }
-
- // return
- return $html;
+ return $element;
}
/**
- * Split a style string into an array of properties.
- * The returned array can contain empty strings.
- *
- * @param string $styles ex: 'color:blue;font-size:12px;'
- * @return array an array of strings containing css property ex: array('color:blue','font-size:12px')
+ * @param string $html
+ * @return \DOMDocument
*/
- private function splitIntoProperties($styles) {
- $properties = (array) explode(';', $styles);
+ protected function createDomDocumentFromHtml($html)
+ {
+ $document = new \DOMDocument('1.0', 'UTF-8');
+ $internalErrors = libxml_use_internal_errors(true);
+ $document->loadHTML($html);
+ libxml_use_internal_errors($internalErrors);
+ $document->formatOutput = true;
- for ($i = 0; $i < count($properties); $i++) {
- // If next property begins with base64,
- // Then the ';' was part of this property (and we should not have split on it).
- if (isset($properties[$i + 1]) && strpos($properties[$i + 1], 'base64,') === 0) {
- $properties[$i] .= ';' . $properties[$i + 1];
- $properties[$i + 1] = '';
- $i += 1;
- }
- }
- return $properties;
+ return $document;
}
/**
- * Get the encoding to use
- *
+ * @param \DOMDocument $document
* @return string
*/
- private function getEncoding()
- {
- return $this->encoding;
- }
-
- /**
- * Process the loaded CSS
- *
- * @return void
- */
- private function processCSS()
+ protected function getHtmlFromDocument(\DOMDocument $document)
{
- // init vars
- $css = (string) $this->css;
-
- // remove newlines
- $css = str_replace(array("\r", "\n"), '', $css);
-
- // replace double quotes by single quotes
- $css = str_replace('"', '\'', $css);
-
- // remove comments
- $css = preg_replace('|/\*.*?\*/|', '', $css);
-
- // remove spaces
- $css = preg_replace('/\s\s+/', ' ', $css);
-
- if ($this->excludeMediaQueries) {
- $css = preg_replace('/@media [^{]*{([^{}]|{[^{}]*})*}/', '', $css);
- }
-
- // rules are splitted by }
- $rules = (array) explode('}', $css);
-
- // init var
- $i = 1;
-
- // loop rules
- foreach ($rules as $rule) {
- // split into chunks
- $chunks = explode('{', $rule);
-
- // invalid rule?
- if (!isset($chunks[1])) {
- continue;
- }
-
- // set the selectors
- $selectors = trim($chunks[0]);
-
- // get cssProperties
- $cssProperties = trim($chunks[1]);
-
- // split multiple selectors
- $selectors = (array) explode(',', $selectors);
-
- // loop selectors
- foreach ($selectors as $selector) {
- // cleanup
- $selector = trim($selector);
+ $xml = $document->saveXML(null, LIBXML_NOEMPTYTAG);
- // build an array for each selector
- $ruleSet = array();
+ $html = preg_replace(
+ '|<\?xml (.*)\?>|',
+ '',
+ $xml
+ );
- // store selector
- $ruleSet['selector'] = $selector;
-
- // process the properties
- $ruleSet['properties'] = $this->processCSSProperties(
- $cssProperties
- );
-
- // calculate specificity
- $ruleSet['specificity'] = Specificity::fromSelector($selector);
-
- // remember the order in which the rules appear
- $ruleSet['order'] = $i;
-
- // add into global rules
- $this->cssRules[] = $ruleSet;
- }
-
- // increment
- $i++;
- }
-
- // sort based on specificity
- if (!empty($this->cssRules)) {
- usort($this->cssRules, array(__CLASS__, 'sortOnSpecificity'));
- }
+ return ltrim($html);
}
/**
- * Process the CSS-properties
- *
- * @return array
- * @param string $propertyString The CSS-properties.
+ * @param \DOMDocument $document
+ * @param array $rules
+ * @return \DOMDocument
*/
- private function processCSSProperties($propertyString)
+ protected function inline(\DOMDocument $document, array $rules)
{
- // split into chunks
- $properties = $this->splitIntoProperties($propertyString);
+ if (empty($rules)) {
+ return $document;
+ }
- // init var
- $pairs = array();
+ $xPath = new \DOMXPath($document);
+ foreach ($rules as $rule) {
+ /** @var Rule $rule */
- // loop properties
- foreach ($properties as $property) {
- // split into chunks
- $chunks = (array) explode(':', $property, 2);
+ $elements = $xPath->query(
+ CssSelector::toXPath($rule->getSelector())
+ );
- // validate
- if (!isset($chunks[1])) {
+ if ($elements === false) {
continue;
}
- // cleanup
- $chunks[0] = trim($chunks[0]);
- $chunks[1] = trim($chunks[1]);
-
- // add to pairs array
- if (!isset($pairs[$chunks[0]]) ||
- !in_array($chunks[1], $pairs[$chunks[0]])
- ) {
- $pairs[$chunks[0]][] = $chunks[1];
- }
- }
-
- // sort the pairs
- ksort($pairs);
-
- // return
- return $pairs;
- }
-
- /**
- * Should the IDs and classes be removed?
- *
- * @return void
- * @param bool [optional] $on Should we enable cleanup?
- */
- public function setCleanup($on = true)
- {
- $this->cleanup = (bool) $on;
- }
-
- /**
- * Set CSS to use
- *
- * @return void
- * @param string $css The CSS to use.
- */
- public function setCSS($css)
- {
- $this->css = (string) $css;
- }
-
- /**
- * Set the encoding to use with the DOMDocument
- *
- * @return void
- * @param string $encoding The encoding to use.
- *
- * @deprecated Doesn't have any effect
- */
- public function setEncoding($encoding)
- {
- $this->encoding = (string) $encoding;
- }
-
- /**
- * Set HTML to process
- *
- * @return void
- * @param string $html The HTML to process.
- */
- public function setHTML($html)
- {
- $this->html = (string) $html;
- }
-
- /**
- * Set use of inline styles block
- * If this is enabled the class will use the style-block in the HTML.
- *
- * @return void
- * @param bool [optional] $on Should we process inline styles?
- */
- public function setUseInlineStylesBlock($on = true)
- {
- $this->useInlineStylesBlock = (bool) $on;
- }
-
- /**
- * Set strip original style tags
- * If this is enabled the class will remove all style tags in the HTML.
- *
- * @return void
- * @param bool [optional] $on Should we process inline styles?
- */
- public function setStripOriginalStyleTags($on = true)
- {
- $this->stripOriginalStyleTags = (bool) $on;
- }
-
- /**
- * Set exclude media queries
- *
- * If this is enabled the media queries will be removed before inlining the rules
- *
- * @return void
- * @param bool [optional] $on
- */
- public function setExcludeMediaQueries($on = true)
- {
- $this->excludeMediaQueries = (bool) $on;
- }
-
- /**
- * Strip style tags into the generated HTML
- *
- * @return string
- * @param \DOMXPath $xPath The DOMXPath for the entire document.
- */
- private function stripOriginalStyleTags(\DOMXPath $xPath)
- {
- // Get all style tags
- $nodes = $xPath->query('descendant-or-self::style');
-
- foreach ($nodes as $node) {
- if ($this->excludeMediaQueries) {
- //Search for Media Queries
- preg_match_all('/@media [^{]*{([^{}]|{[^{}]*})*}/', $node->nodeValue, $mqs);
-
- // Replace the nodeValue with just the Media Queries
- $node->nodeValue = implode("\n", $mqs[0]);
- } else {
- // Remove the entire style tag
- $node->parentNode->removeChild($node);
+ foreach ($elements as $element) {
+ $this->inlineCssOnElement($element, $rule->getProperties());
}
}
- }
-
- /**
- * Sort an array on the specificity element
- *
- * @return int
- * @param array $e1 The first element.
- * @param array $e2 The second element.
- */
- private static function sortOnSpecificity($e1, $e2)
- {
- // Compare the specificity
- $value = $e1['specificity']->compareTo($e2['specificity']);
-
- // if the specificity is the same, use the order in which the element appeared
- if ($value === 0) {
- $value = $e1['order'] - $e2['order'];
- }
- return $value;
+ return $document;
}
}
diff --git a/src/Exception.php b/src/Exception.php
index 709b055..52f2dc4 100644
--- a/src/Exception.php
+++ b/src/Exception.php
@@ -1,11 +1,8 @@
- */
class Exception extends \Exception
{
+
}
diff --git a/src/Specificity.php b/src/Specificity.php
deleted file mode 100644
index 8e5a2fd..0000000
--- a/src/Specificity.php
+++ /dev/null
@@ -1,133 +0,0 @@
-a = $a;
- $this->b = $b;
- $this->c = $c;
- }
-
- /**
- * Increase the current specificity by adding the three values
- *
- * @param int $a The number of ID selectors in the selector
- * @param int $b The number of class selectors, attributes selectors, and pseudo-classes in the selector
- * @param int $c The number of type selectors and pseudo-elements in the selector
- */
- public function increase($a, $b, $c)
- {
- $this->a += $a;
- $this->b += $b;
- $this->c += $c;
- }
-
- /**
- * Get the specificity values as an array
- *
- * @return array
- */
- public function getValues()
- {
- return array($this->a, $this->b, $this->c);
- }
-
- /**
- * Calculate the specificity based on a CSS Selector string,
- * Based on the patterns from premailer/css_parser by Alex Dunae
- *
- * @see https://github.com/premailer/css_parser/blob/master/lib/css_parser/regexps.rb
- * @param string $selector
- * @return static
- */
- public static function fromSelector($selector)
- {
- $pattern_a = " \#";
- $pattern_b = " (\.[\w]+) # classes
- |
- \[(\w+) # attributes
- |
- (\:( # pseudo classes
- link|visited|active
- |hover|focus
- |lang
- |target
- |enabled|disabled|checked|indeterminate
- |root
- |nth-child|nth-last-child|nth-of-type|nth-last-of-type
- |first-child|last-child|first-of-type|last-of-type
- |only-child|only-of-type
- |empty|contains
- ))";
-
- $pattern_c = " ((^|[\s\+\>\~]+)[\w]+ # elements
- |
- \:{1,2}( # pseudo-elements
- after|before
- |first-letter|first-line
- |selection
- )
- )";
-
- return new static(
- preg_match_all("/{$pattern_a}/ix", $selector, $matches),
- preg_match_all("/{$pattern_b}/ix", $selector, $matches),
- preg_match_all("/{$pattern_c}/ix", $selector, $matches)
- );
- }
-
- /**
- * Returns <0 when $specificity is greater, 0 when equal, >0 when smaller
- *
- * @param Specificity $specificity
- * @return int
- */
- public function compareTo(Specificity $specificity)
- {
- if ($this->a !== $specificity->a) {
- return $this->a - $specificity->a;
- } elseif ($this->b !== $specificity->b) {
- return $this->b - $specificity->b;
- } else {
- return $this->c - $specificity->c;
- }
- }
-}
diff --git a/tests/Css/ProcessorTest.php b/tests/Css/ProcessorTest.php
new file mode 100644
index 0000000..50a0669
--- /dev/null
+++ b/tests/Css/ProcessorTest.php
@@ -0,0 +1,71 @@
+processor = new Processor();
+ }
+
+ public function tearDown()
+ {
+ $this->processor = null;
+ }
+
+ public function testCssWithOneRule()
+ {
+ $css = <<processor->getRules($css);
+
+ $this->assertCount(1, $rules);
+ $this->assertInstanceOf('TijsVerkoyen\CssToInlineStyles\Css\Rule\Rule', $rules[0]);
+ $this->assertEquals('a', $rules[0]->getSelector());
+ $this->assertCount(2, $rules[0]->getProperties());
+ $this->assertEquals('padding', $rules[0]->getProperties()[0]->getName());
+ $this->assertEquals('5px', $rules[0]->getProperties()[0]->getValue());
+ $this->assertEquals('display', $rules[0]->getProperties()[1]->getName());
+ $this->assertEquals('block', $rules[0]->getProperties()[1]->getValue());
+ $this->assertEquals(1, $rules[0]->getOrder());
+ }
+
+ public function testCssWithMediaQueries()
+ {
+ $css = <<processor->getRules($css);
+
+ $this->assertCount(1, $rules);
+ $this->assertInstanceOf('TijsVerkoyen\CssToInlineStyles\Css\Rule\Rule', $rules[0]);
+ $this->assertEquals('a', $rules[0]->getSelector());
+ $this->assertCount(1, $rules[0]->getProperties());
+ $this->assertEquals('color', $rules[0]->getProperties()[0]->getName());
+ $this->assertEquals('red', $rules[0]->getProperties()[0]->getValue());
+ $this->assertEquals(1, $rules[0]->getOrder());
+ }
+}
diff --git a/tests/Css/Property/ProcessorTest.php b/tests/Css/Property/ProcessorTest.php
new file mode 100644
index 0000000..052e5f9
--- /dev/null
+++ b/tests/Css/Property/ProcessorTest.php
@@ -0,0 +1,74 @@
+processor = new Processor();
+ }
+
+ public function tearDown()
+ {
+ $this->processor = null;
+ }
+
+ public function testMostBasicProperty()
+ {
+ $propertiesString = 'padding: 0;';
+ $this->assertEquals(
+ array(
+ 'padding: 0',
+ ),
+ $this->processor->splitIntoSeparateProperties($propertiesString)
+ );
+ }
+
+ public function testInvalidProperty()
+ {
+ $this->assertNull(
+ $this->processor->convertToObject('foo:')
+ );
+ }
+
+ public function testBase64ContainsSemiColon()
+ {
+ $propertiesString = <<assertEquals(
+ array(
+ 'background: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7) no-repeat left center',
+ 'padding: 5px 0 5px 25px',
+ ),
+ $this->processor->splitIntoSeparateProperties($propertiesString)
+ );
+ }
+
+ public function testBuildingPropertiesString()
+ {
+ $properties = array(
+ new Property('padding', '5px'),
+ new Property('display', 'block'),
+ );
+
+ $this->assertEquals(
+ 'padding: 5px; display: block;',
+ $this->processor->buildPropertiesString($properties)
+ );
+ }
+}
diff --git a/tests/Css/Property/PropertyTest.php b/tests/Css/Property/PropertyTest.php
new file mode 100644
index 0000000..4bd0773
--- /dev/null
+++ b/tests/Css/Property/PropertyTest.php
@@ -0,0 +1,35 @@
+assertEquals('padding', $property->getName());
+ $this->assertEquals('5px', $property->getValue());
+ }
+
+ public function testSimplePropertyToString()
+ {
+ $property = new Property('padding', '5px');
+
+ $this->assertEquals(
+ 'padding: 5px;',
+ $property->toString()
+ );
+ }
+
+ public function testIfImportantIsDetected()
+ {
+ $property = new Property('padding', '5px !important');
+ $this->assertTrue($property->isImportant());
+
+ $property = new Property('padding', '5px');
+ $this->assertFalse($property->isImportant());
+ }
+}
diff --git a/tests/Css/Rule/ProcessorTest.php b/tests/Css/Rule/ProcessorTest.php
new file mode 100644
index 0000000..76abee8
--- /dev/null
+++ b/tests/Css/Rule/ProcessorTest.php
@@ -0,0 +1,45 @@
+processor = new Processor();
+ }
+
+ public function tearDown()
+ {
+ $this->processor = null;
+ }
+
+ public function testMostBasicRule()
+ {
+ $css = <<processor->convertToObjects($css, 1);
+
+ $this->assertCount(1, $rules);
+ $this->assertInstanceOf('TijsVerkoyen\CssToInlineStyles\Css\Rule\Rule', $rules[0]);
+ $this->assertEquals('a', $rules[0]->getSelector());
+ $this->assertCount(2, $rules[0]->getProperties());
+ $this->assertEquals('padding', $rules[0]->getProperties()[0]->getName());
+ $this->assertEquals('5px', $rules[0]->getProperties()[0]->getValue());
+ $this->assertEquals('display', $rules[0]->getProperties()[1]->getName());
+ $this->assertEquals('block', $rules[0]->getProperties()[1]->getValue());
+ $this->assertEquals(1, $rules[0]->getOrder());
+ }
+}
diff --git a/tests/Css/Rule/RuleTest.php b/tests/Css/Rule/RuleTest.php
new file mode 100644
index 0000000..555a035
--- /dev/null
+++ b/tests/Css/Rule/RuleTest.php
@@ -0,0 +1,28 @@
+assertEquals('a', $rule->getSelector());
+ $this->assertEquals(array($property), $rule->getProperties());
+ $this->assertEquals($specificity, $rule->getSpecificity());
+ $this->assertEquals(1, $rule->getOrder());
+ }
+}
diff --git a/tests/Css/Specificity/SpecificityTest.php b/tests/Css/Specificity/SpecificityTest.php
new file mode 100644
index 0000000..1fe57db
--- /dev/null
+++ b/tests/Css/Specificity/SpecificityTest.php
@@ -0,0 +1,76 @@
+increase(1, 1, 1);
+
+ $this->assertEquals(
+ array(1, 1, 1),
+ $instance->getValues()
+ );
+ }
+
+ public function testIdBeforeClass()
+ {
+ $idInstance = new Specificity(1, 0, 0);
+ $classInstance = new Specificity(0, 1, 0);
+
+ $this->assertEquals(
+ 1,
+ $idInstance->compareTo($classInstance)
+ );
+ }
+
+ public function testClassBeforeElement()
+ {
+ $idInstance = new Specificity(0, 1, 0);
+ $classInstance = new Specificity(0, 0, 1);
+
+ $this->assertEquals(
+ 1,
+ $idInstance->compareTo($classInstance)
+ );
+ }
+
+ public function testCompareEqualItems()
+ {
+ $instance1 = new Specificity(1, 0, 0);
+ $instance2 = new Specificity(1, 0, 0);
+
+ $this->assertEquals(
+ 0,
+ $instance1->compareTo($instance2)
+ );
+ }
+
+ public function testSingleIdSelector()
+ {
+ $this->assertEquals(
+ array(1, 0, 0),
+ Specificity::fromSelector('#foo')->getValues()
+ );
+ }
+
+ public function testSingleClassSelector()
+ {
+ $this->assertEquals(
+ array(0, 1, 0),
+ Specificity::fromSelector('.foo')->getValues()
+ );
+ }
+
+ public function testSingleElementSelector()
+ {
+ $this->assertEquals(
+ array(0, 0, 1),
+ Specificity::fromSelector('a')->getValues()
+ );
+ }
+}
diff --git a/tests/CssToInlineStylesTest.php b/tests/CssToInlineStylesTest.php
deleted file mode 100644
index 61a7e1a..0000000
--- a/tests/CssToInlineStylesTest.php
+++ /dev/null
@@ -1,241 +0,0 @@
-cssToInlineStyles = new CssToInlineStyles();
- }
-
- public function teardown()
- {
- $this->cssToInlineStyles = null;
- }
-
- public function testSimpleElementSelector()
- {
- $html = '';
- $css = 'div { display: none; }';
- $expected = '';
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testSimpleCssSelector()
- {
- $html = 'nodeContent';
- $css = '.test-class { background-color: #aaa; text-decoration: none; }';
- $expected = 'nodeContent';
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testSimpleIdSelector()
- {
- $html = '
';
- $css = '#IMG1 { border: 1px solid red; }';
- $expected = '
';
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testInlineStylesBlock()
- {
- $html = <<
- a {
- padding: 10px;
- margin: 0;
- }
-
-
-EOF;
- $expected = '';
- $this->cssToInlineStyles->setUseInlineStylesBlock();
- $this->cssToInlineStyles->setHTML($html);
- $actual = $this->findAndSaveNode($this->cssToInlineStyles->convert(), '//a');
- $this->assertEquals($expected, $actual);
- }
-
- public function testStripOriginalStyleTags()
- {
- $html = <<
- a {
- padding: 10px;
- margin: 0;
- }
-
-
-EOF;
- $expected = '';
- $this->cssToInlineStyles->setStripOriginalStyleTags();
- $this->cssToInlineStyles->setHTML($html);
- $actual = $this->findAndSaveNode($this->cssToInlineStyles->convert(), '//a');
- $this->assertEquals($expected, $actual);
-
- $this->assertNull($this->findAndSaveNode($actual, '//style'));
- }
-
- public function testSpecificity()
- {
- $html = '
';
- $css = <<
';
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testMergeOriginalStyles()
- {
- $html = 'text
';
- $css = <<text
';
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testXHTMLOutput()
- {
- $html = '
';
- $css = 'a { display: block; }';
-
- $this->cssToInlineStyles->setHTML($html);
- $this->cssToInlineStyles->setCSS($css);
- $actual = $this->cssToInlineStyles->convert(true);
-
- $this->assertContains('
', $actual);
- }
-
- public function testCleanup()
- {
- $html = ' id="foo" class="bar"
';
- $css = ' #id { display: inline; } .className { margin-right: 10px; }';
- $expected = ' id="foo" class="bar"
';
- $this->cssToInlineStyles->setCleanup();
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testEqualSpecificity()
- {
- $html = '
';
- $css = ' .one { display: inline; } a > strong {} a {} a {} a {} a {} a {} a {}a {} img { display: block; }';
- $expected = '
';
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testInvalidSelector()
- {
- $html = "";
- $css = ' p&@*$%& { display: inline; }';
- $expected = $html;
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testEncoding()
- {
- $html = "" . html_entity_decode('’', 0, 'UTF-8') . "
";
- $css = '';
- $expected = '' . chr(0xc3) . chr(0xa2) . chr(0xc2) . chr(0x80) . chr(0xc2) . chr(0x99) . '
';
-
- $this->cssToInlineStyles->setEncoding('ISO-8859-1');
- $this->runHTMLToCSS($html, $css, $expected);
- }
-
- public function testXMLHeaderIsRemoved()
- {
- $html = 'Foo
';
- $expected = <<
-
-
- Foo
-
-
-
-EOF;
- $this->cssToInlineStyles->setHTML($html);
- $this->cssToInlineStyles->setCSS('');
- $actual = $this->cssToInlineStyles->convert(true);
-
- $this->assertEquals($expected, $actual);
- }
-
- private function runHTMLToCSS($html, $css, $expected, $asXHTML = false)
- {
- $cssToInlineStyles = $this->cssToInlineStyles;
- $cssToInlineStyles->setHTML($html);
- $cssToInlineStyles->setCSS($css);
- $output = $cssToInlineStyles->convert($asXHTML);
- $actual = $this->stripBody($output, $asXHTML);
- $this->assertEquals($expected, $actual);
- }
-
- private function stripBody($html, $asXHTML = false)
- {
- $dom = new \DOMDocument();
- /*if ($asXHTML) {
- $dom->loadXML($html);
- } else {*/
- $dom->loadHTML($html);
- /*}*/
- $xpath = new \DOMXPath($dom);
- $nodelist = $xpath->query('//body/*');
- $result = '';
- for ($i = 0; $i < $nodelist->length; $i++) {
- $node = $nodelist->item($i);
- if ($asXHTML) {
- $result .= $dom->saveXML($node);
- } else {
- $result .= $dom->saveHTML($node);
- }
- }
-
- return $result;
- }
-
- private function findAndSaveNode($html, $query)
- {
- $dom = new \DOMDocument();
- $dom->loadHTML($html);
- $xpath = new \DOMXPath($dom);
- $nodelist = $xpath->query($query);
- if ($nodelist->length > 0) {
- $node = $nodelist->item(0);
-
- return $dom->saveHTML($node);
- } else {
- return null;
- }
- }
-}
diff --git a/tests/CssToOnlineStylesTest.php b/tests/CssToOnlineStylesTest.php
new file mode 100644
index 0000000..aa557d0
--- /dev/null
+++ b/tests/CssToOnlineStylesTest.php
@@ -0,0 +1,110 @@
+cssToInlineStyles = new CssToInlineStyles();
+ }
+
+ public function tearDown()
+ {
+ $this->cssToInlineStyles = null;
+ }
+
+ public function testNoXMLHeaderPresent()
+ {
+ $this->assertNotContains(
+ 'cssToInlineStyles->convert(
+ 'foo
',
+ ''
+ )
+ );
+ }
+
+ public function testApplyNoStylesOnElement()
+ {
+ $document = new \DOMDocument();
+ $element = $document->createElement('a', 'foo');
+ $inlineElement = $this->cssToInlineStyles->inlineCssOnElement(
+ $element,
+ array()
+ );
+
+ $document->appendChild($inlineElement);
+ $this->assertEquals('foo', trim($document->saveHTML()));
+ }
+
+ public function testApplyBasicStylesOnElement()
+ {
+ $document = new \DOMDocument();
+ $element = $document->createElement('a', 'foo');
+ $inlineElement = $this->cssToInlineStyles->inlineCssOnElement(
+ $element,
+ array(
+ new Property('padding', '5px'),
+ )
+ );
+
+ $document->appendChild($inlineElement);
+
+ $this->assertEquals('foo', trim($document->saveHTML()));
+ }
+
+ public function testApplyBasicStylesOnElementWithInlineStyles()
+ {
+ $document = new \DOMDocument();
+ $element = $document->createElement('a', 'foo');
+ $element->setAttribute('style', 'color: green;');
+ $inlineElement = $this->cssToInlineStyles->inlineCssOnElement(
+ $element,
+ array(
+ new Property('padding', '5px'),
+ )
+ );
+
+ $document->appendChild($inlineElement);
+
+ $this->assertEquals(
+ 'foo',
+ trim($document->saveHTML())
+ );
+ }
+
+ public function testBasicRealHTMLExample()
+ {
+ $html = 'foo
';
+ $css = 'p { color: red; }';
+
+ $this->assertEquals(
+ 'foo
',
+ $this->stripAllStuff(
+ $this->cssToInlineStyles->convert($html, $css)
+ )
+ );
+ }
+
+ private function stripAllStuff($content)
+ {
+ $content = str_replace(
+ array("\n", "\t"),
+ '',
+ $content
+ );
+ $content = preg_replace('|(\s)+<|', '<', $content);
+ $content = preg_replace('|>(\s)+|', '>', $content);
+
+ return $content;
+ }
+}
diff --git a/tests/SpecificityTest.php b/tests/SpecificityTest.php
deleted file mode 100644
index 44d0644..0000000
--- a/tests/SpecificityTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-assertEquals(array(1,2,3), $specificity->getValues());
- }
-
- public function testIncreaseValue()
- {
- $specificity = new Specificity(1, 2, 3);
- $specificity->increase(1,2,3);
- $this->assertEquals(array(2,4,6), $specificity->getValues());
- }
-
-
- /** @dataProvider getCompareTestData */
- public function testCompare(Specificity $a, Specificity $b, $result)
- {
- $this->assertEquals($result, $a->compareTo($b));
- }
-
- public function getCompareTestData()
- {
- return array(
- array(new Specificity(0, 0, 0), new Specificity(0, 0, 0), 0),
- array(new Specificity(0, 0, 1), new Specificity(0, 0, 1), 0),
- array(new Specificity(0, 0, 2), new Specificity(0, 0, 1), 1),
- array(new Specificity(0, 0, 2), new Specificity(0, 0, 3), -1),
- array(new Specificity(0, 4, 0), new Specificity(0, 4, 0), 0),
- array(new Specificity(0, 6, 0), new Specificity(0, 5, 11), 1),
- array(new Specificity(0, 7, 0), new Specificity(0, 8, 0), -1),
- array(new Specificity(9, 0, 0), new Specificity(9, 0, 0), 0),
- array(new Specificity(11, 0, 0), new Specificity(10, 11, 0), 1),
- array(new Specificity(12, 11, 0), new Specificity(13, 0, 0), -1),
- );
- }
-
- /** @dataProvider getSelectorData */
- public function testFromSelector($selector, $result)
- {
- $specificity = Specificity::fromSelector($selector);
- $this->assertEquals($result, $specificity->getValues());
- }
-
- public function getSelectorData()
- {
- return array(
- array("*", array(0,0,0)),
- array("li", array(0,0,1)),
- array("ul li", array(0,0,2)),
- array("ul ol+li", array(0,0,3)),
- array("h1 + *[rel=up]", array(0,1,1)),
- array("ul ol li.red", array(0,1,3)),
- array("li.red.level", array(0,2,1)),
- array("#x34y", array(1,0,0)),
- );
- }
-
- /** @dataProvider getSkippedSelectorData */
- public function testSkippedFromSelector($selector, $result)
- {
- $this->markTestSkipped(
- 'Skipping edge cases in CSS'
- );
-
- $specificity = Specificity::fromSelector($selector);
- $this->assertEquals($result, $specificity->getValues());
- }
-
- public function getSkippedSelectorData()
- {
- return array(
- array("#s12:not(FOO)", array(1,0,1)),
- );
- }
-}
From 658805342ec0fbdd050b676b095c62f9f7f781d8 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 17:34:14 +0100
Subject: [PATCH 05/39] Trying to fix coverage
---
.travis.yml | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 2ee4b33..356ad3b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,4 +17,8 @@ before_script:
- composer install --prefer-source --no-interaction --no-scripts
script:
- - bin/phpunit --verbose --coverage-text
+ - vendor/bin/phpunit --verbose --coverage-clover=coverage.clover
+
+after_success:
+ - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]] && [[ "$TRAVIS_PHP_VERSION" != "nightly" ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi
+ - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]] && [[ "$TRAVIS_PHP_VERSION" != "nightly" ]]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi
From 4c91066f74b28fbb9497b29aec71d2f7e2b622ec Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 17:47:25 +0100
Subject: [PATCH 06/39] Use PHPUnit 4.8 instead of 5.0 as it requires php 5.6
---
composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/composer.json b/composer.json
index 6f6d1b2..0a27922 100644
--- a/composer.json
+++ b/composer.json
@@ -15,7 +15,7 @@
"symfony/css-selector": "^2.7"
},
"require-dev": {
- "phpunit/phpunit": "~5.0"
+ "phpunit/phpunit": "~4.8"
},
"autoload": {
"psr-4": {
From 8ab951983aef7ce4f4de78e1f20981fb63e69457 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 17:51:36 +0100
Subject: [PATCH 07/39] Added some badges
---
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
mode change 100755 => 100644 README.md
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
index 00d226f..31b0a70
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# CssToInlineStyles class
-[](https://travis-ci.org/tijsverkoyen/CssToInlineStyles)
+[](https://travis-ci.org/tijsverkoyen/CssToInlineStyles) [](https://scrutinizer-ci.com/g/tijsverkoyen/CssToInlineStyles/?branch=master) [](https://scrutinizer-ci.com/g/tijsverkoyen/CssToInlineStyles/?branch=master) [](https://insight.sensiolabs.com/projects/5c0ce94f-de6d-403e-9e0a-431268deb75c)
+
+## Installation
> CssToInlineStyles is a class that enables you to convert HTML-pages/files into
> HTML-pages/files with inline styles. This is very usefull when you're sending
From acff8170e4d9df11a724345dc71281cffca24119 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 18:09:23 +0100
Subject: [PATCH 08/39] Fixed some issues detected with SensioLabsInsight
---
LICENSE.md | 25 +++++++++++++------------
composer.json | 2 +-
src/Css/Property/Processor.php | 3 ++-
src/Css/Rule/Processor.php | 2 --
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/LICENSE.md b/LICENSE.md
index 81adb07..5f40959 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -7,16 +7,17 @@ modification, are permitted provided that the following conditions are met:
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
-3. The name of the author may not be used to endorse or promote products
- derived from this software without specific prior written permission.
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
-This software is provided by the author "as is" and any express or implied
-warranties, including, but not limited to, the implied warranties of
-merchantability and fitness for a particular purpose are disclaimed. In no event
-shall the author be liable for any direct, indirect, incidental, special,
-exemplary, or consequential damages (including, but not limited to, procurement
-of substitute goods or services; loss of use, data, or profits; or business
-interruption) however caused and on any theory of liability, whether in
-contract, strict liability, or tort (including negligence or otherwise) arising
-in any way out of the use of this software, even if advised of the possibility
-of such damage.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/composer.json b/composer.json
index 0a27922..c3de758 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,7 @@
"type": "library",
"description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.",
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
- "license": "BSD",
+ "license": "BSD-3-Clause",
"authors": [
{
"name": "Tijs Verkoyen",
diff --git a/src/Css/Property/Processor.php b/src/Css/Property/Processor.php
index dcbd733..546d9ef 100644
--- a/src/Css/Property/Processor.php
+++ b/src/Css/Property/Processor.php
@@ -16,8 +16,9 @@ public function splitIntoSeparateProperties($propertiesString)
$properties = (array) explode(';', $propertiesString);
$keysToRemove = array();
+ $numberOfProperties = count($properties);
- for ($i = 0; $i < count($properties); $i++) {
+ for ($i = 0; $i < $numberOfProperties; $i++) {
$properties[$i] = trim($properties[$i]);
// if the new property begins with base64 it is part of the current property
diff --git a/src/Css/Rule/Processor.php b/src/Css/Rule/Processor.php
index a7309ef..f347667 100644
--- a/src/Css/Rule/Processor.php
+++ b/src/Css/Rule/Processor.php
@@ -61,8 +61,6 @@ public function convertToObjects($rule, $orginalOrder)
foreach ($selectors as $selector) {
$selector = trim($selector);
-// $ruleSet['specificity'] = Specificity::fromSelector($selector);
-
$rules[] = new Rule(
$selector,
$propertiesProcessor->convertArrayToObjects($properties),
From 7fd71521aa9ee590550014d398cdd6e84207aa93 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 18:36:32 +0100
Subject: [PATCH 09/39] Added a test to check if the order is maintained based
on #83
---
tests/Css/Rule/ProcessorTest.php | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/tests/Css/Rule/ProcessorTest.php b/tests/Css/Rule/ProcessorTest.php
index 76abee8..b687440 100644
--- a/tests/Css/Rule/ProcessorTest.php
+++ b/tests/Css/Rule/ProcessorTest.php
@@ -42,4 +42,25 @@ public function testMostBasicRule()
$this->assertEquals('block', $rules[0]->getProperties()[1]->getValue());
$this->assertEquals(1, $rules[0]->getOrder());
}
+
+ public function testMaintainOrderOfProperties()
+ {
+ $css = <<processor->convertToObjects($css, 1);
+
+ $this->assertCount(1, $rules);
+ $this->assertInstanceOf('TijsVerkoyen\CssToInlineStyles\Css\Rule\Rule', $rules[0]);
+ $this->assertEquals('div', $rules[0]->getSelector());
+ $this->assertCount(2, $rules[0]->getProperties());
+ $this->assertEquals('width', $rules[0]->getProperties()[0]->getName());
+ $this->assertEquals('200px', $rules[0]->getProperties()[0]->getValue());
+ $this->assertEquals('_width', $rules[0]->getProperties()[1]->getName());
+ $this->assertEquals('211px', $rules[0]->getProperties()[1]->getValue());
+ $this->assertEquals(1, $rules[0]->getOrder());
+ }
}
From 4e7c36c20f96fcfd62d69f46ce6e567b4b65681d Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Fri, 20 Nov 2015 18:43:53 +0100
Subject: [PATCH 10/39] Added some more test to check if all media-queries are
stripped.
---
tests/Css/ProcessorTest.php | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/tests/Css/ProcessorTest.php b/tests/Css/ProcessorTest.php
index 50a0669..b0c2fd2 100644
--- a/tests/Css/ProcessorTest.php
+++ b/tests/Css/ProcessorTest.php
@@ -68,4 +68,22 @@ public function testCssWithMediaQueries()
$this->assertEquals('red', $rules[0]->getProperties()[0]->getValue());
$this->assertEquals(1, $rules[0]->getOrder());
}
+
+ public function testMakeSureMediaQueriesAreRemoved()
+ {
+ $css = '@media tv and (min-width: 700px) and (orientation: landscape) {.foo {display: none;}}';
+ $this->assertEmpty($this->processor->getRules($css));
+
+ $css = '@media (min-width: 700px), handheld and (orientation: landscape) {.foo {display: none;}}';
+ $this->assertEmpty($this->processor->getRules($css));
+
+ $css = '@media not screen and (color), print and (color)';
+ $this->assertEmpty($this->processor->getRules($css));
+
+ $css = '@media screen and (min-aspect-ratio: 1/1) {.foo {display: none;}}';
+ $this->assertEmpty($this->processor->getRules($css));
+
+ $css = '@media screen and (device-aspect-ratio: 16/9), screen and (device-aspect-ratio: 16/10) {.foo {display: none;}}';
+ $this->assertEmpty($this->processor->getRules($css));
+ }
}
From 8e2010633f8ddbe7f46b954e85964370cc5f9731 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Mon, 23 Nov 2015 10:58:05 +0100
Subject: [PATCH 11/39] Use the container based infrastructure
---
.travis.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.travis.yml b/.travis.yml
index 356ad3b..5020e92 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,7 @@
language: php
+sudo: false
+
matrix:
include:
- php: 5.4
From c6359dc626fa3a3f4c752755273ebca711220865 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Mon, 23 Nov 2015 11:03:47 +0100
Subject: [PATCH 12/39] More readable code for converting a string into a
property
---
src/Css/Property/Processor.php | 13 ++++++++++---
tests/Css/Property/ProcessorTest.php | 6 ++++++
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/src/Css/Property/Processor.php b/src/Css/Property/Processor.php
index 546d9ef..ae64f13 100644
--- a/src/Css/Property/Processor.php
+++ b/src/Css/Property/Processor.php
@@ -63,13 +63,20 @@ protected function cleanup($string)
*/
public function convertToObject($property)
{
- $chunks = (array) explode(':', $property, 2);
+ if (stripos($property, ':') === false) {
+ return null;
+ }
+
+ list($name, $value) = explode(':', $property, 2);
+
+ $name = trim($name);
+ $value = trim($value);
- if (!isset($chunks[1]) || $chunks[1] == '') {
+ if ($value === '') {
return null;
}
- return new Property(trim($chunks[0]), trim($chunks[1]));
+ return new Property($name, $value);
}
/**
diff --git a/tests/Css/Property/ProcessorTest.php b/tests/Css/Property/ProcessorTest.php
index 052e5f9..980cf20 100644
--- a/tests/Css/Property/ProcessorTest.php
+++ b/tests/Css/Property/ProcessorTest.php
@@ -71,4 +71,10 @@ public function testBuildingPropertiesString()
$this->processor->buildPropertiesString($properties)
);
}
+
+ public function testFaultyProperties()
+ {
+ $this->assertNull($this->processor->convertToObject('foo'));
+ $this->assertNull($this->processor->convertToObject('foo:'));
+ }
}
From ea182c6b03ebf6af74ba458c369384bbf2490416 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Mon, 23 Nov 2015 11:13:08 +0100
Subject: [PATCH 13/39] Fixed issue with comparing Specificity
---
src/Css/Rule/Processor.php | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/Css/Rule/Processor.php b/src/Css/Rule/Processor.php
index f347667..e45473a 100644
--- a/src/Css/Rule/Processor.php
+++ b/src/Css/Rule/Processor.php
@@ -97,17 +97,17 @@ public function convertArrayToObjects(array $rules)
* Sort an array on the specificity element
*
* @return int
- * @param array $e1 The first element.
- * @param array $e2 The second element.
+ * @param Rule $e1 The first element.
+ * @param Rule $e2 The second element.
*/
- private static function sortOnSpecificity($e1, $e2)
+ private static function sortOnSpecificity(Rule $e1, Rule $e2)
{
- // Compare the specificity
- $value = $e1['specificity']->compareTo($e2['specificity']);
+ $e1Specificity = ($e1->getSpecificity());
+ $value = $e1Specificity->compareTo($e2->getSpecificity());
// if the specificity is the same, use the order in which the element appeared
if ($value === 0) {
- $value = $e1['order'] - $e2['order'];
+ $value = $e1->getOrder() - $e2->getOrder();
}
return $value;
From e3b73228610e4e5782f432361efae8508fcf935f Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Mon, 23 Nov 2015 11:20:53 +0100
Subject: [PATCH 14/39] Fixed some PHPDoc
---
src/Css/Processor.php | 6 +++---
src/Css/Property/Processor.php | 4 ++--
src/Css/Property/Property.php | 2 +-
src/Css/Rule/Processor.php | 4 ++--
src/Css/Rule/Rule.php | 18 ++++++++++++------
5 files changed, 20 insertions(+), 14 deletions(-)
diff --git a/src/Css/Processor.php b/src/Css/Processor.php
index 9bb8e43..d6f949a 100644
--- a/src/Css/Processor.php
+++ b/src/Css/Processor.php
@@ -22,10 +22,10 @@ public function getRules($css)
}
/**
- * @param $css
- * @return mixed|string
+ * @param string $css
+ * @return string
*/
- protected function doCleanup($css)
+ private function doCleanup($css)
{
// remove media queries
$css = preg_replace('/@media [^{]*{([^{}]|{[^{}]*})*}/', '', $css);
diff --git a/src/Css/Property/Processor.php b/src/Css/Property/Processor.php
index ae64f13..4a8e0d4 100644
--- a/src/Css/Property/Processor.php
+++ b/src/Css/Property/Processor.php
@@ -41,7 +41,7 @@ public function splitIntoSeparateProperties($propertiesString)
* @param $string
* @return mixed|string
*/
- protected function cleanup($string)
+ private function cleanup($string)
{
$string = str_replace(array("\r", "\n"), '', $string);
$string = str_replace(array("\t"), ' ', $string);
@@ -83,7 +83,7 @@ public function convertToObject($property)
* Convert an array of property-strings into objects
*
* @param array $properties
- * @return array
+ * @return Property[]
*/
public function convertArrayToObjects(array $properties)
{
diff --git a/src/Css/Property/Property.php b/src/Css/Property/Property.php
index 1e83f35..dbc94a6 100644
--- a/src/Css/Property/Property.php
+++ b/src/Css/Property/Property.php
@@ -2,7 +2,7 @@
namespace TijsVerkoyen\CssToInlineStyles\Css\Property;
-class Property
+final class Property
{
/**
* @var string
diff --git a/src/Css/Rule/Processor.php b/src/Css/Rule/Processor.php
index e45473a..33f5335 100644
--- a/src/Css/Rule/Processor.php
+++ b/src/Css/Rule/Processor.php
@@ -24,7 +24,7 @@ public function splitIntoSeparateRules($rulesString)
* @param $string
* @return mixed|string
*/
- protected function cleanup($string)
+ private function cleanup($string)
{
$string = str_replace(array("\r", "\n"), '', $string);
$string = str_replace(array("\t"), ' ', $string);
@@ -74,7 +74,7 @@ public function convertToObjects($rule, $orginalOrder)
/**
* @param array $rules
- * @return array
+ * @return Rule[]
*/
public function convertArrayToObjects(array $rules)
{
diff --git a/src/Css/Rule/Rule.php b/src/Css/Rule/Rule.php
index 2ddcccd..4130991 100644
--- a/src/Css/Rule/Rule.php
+++ b/src/Css/Rule/Rule.php
@@ -2,6 +2,8 @@
namespace TijsVerkoyen\CssToInlineStyles\Css\Rule;
+use TijsVerkoyen\CssToInlineStyles\Css\Specificity\Specificity;
+
final class Rule
{
/**
@@ -14,6 +16,9 @@ final class Rule
*/
private $properties;
+ /**
+ * @var Specificity
+ */
private $specificity;
/**
@@ -23,12 +28,13 @@ final class Rule
/**
* Rule constructor.
- * @param string $selector
- * @param array $properties
- * @param $specificity
- * @param int $order
+ *
+ * @param string $selector
+ * @param array $properties
+ * @param Specificity $specificity
+ * @param int $order
*/
- public function __construct($selector, array $properties, $specificity, $order)
+ public function __construct($selector, array $properties, Specificity $specificity, $order)
{
$this->selector = $selector;
$this->properties = $properties;
@@ -59,7 +65,7 @@ public function getProperties()
/**
* Get specificity
*
- * @return mixed
+ * @return Specificity
*/
public function getSpecificity()
{
From d1452b451fcc1258ca0ed1b3dad72337787db646 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Mon, 23 Nov 2015 11:25:01 +0100
Subject: [PATCH 15/39] Skip empty properties
---
src/Css/Property/Processor.php | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/Css/Property/Processor.php b/src/Css/Property/Processor.php
index 4a8e0d4..27cd3ad 100644
--- a/src/Css/Property/Processor.php
+++ b/src/Css/Property/Processor.php
@@ -90,6 +90,11 @@ public function convertArrayToObjects(array $properties)
$objects = array();
foreach ($properties as $property) {
+ $object = $this->convertToObject($property);
+ if ($object === null) {
+ continue;
+ }
+
$objects[] = $this->convertToObject($property);
}
From d48d4f399f4416510ef70446244c2fe53eb62632 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Mon, 23 Nov 2015 11:26:36 +0100
Subject: [PATCH 16/39] More performant code
---
src/Css/Property/Property.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Css/Property/Property.php b/src/Css/Property/Property.php
index dbc94a6..320c3dc 100644
--- a/src/Css/Property/Property.php
+++ b/src/Css/Property/Property.php
@@ -53,7 +53,7 @@ public function getValue()
*/
public function isImportant()
{
- return (stristr($this->value, '!important') !== false);
+ return (stripos($this->value, '!important') !== false);
}
/**
From b3834407de6be247fe23f22a8f40a188d547afaa Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Mon, 23 Nov 2015 11:26:44 +0100
Subject: [PATCH 17/39] Better method-name
---
tests/CssToOnlineStylesTest.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/CssToOnlineStylesTest.php b/tests/CssToOnlineStylesTest.php
index aa557d0..8cdd8f9 100644
--- a/tests/CssToOnlineStylesTest.php
+++ b/tests/CssToOnlineStylesTest.php
@@ -89,13 +89,13 @@ public function testBasicRealHTMLExample()
$this->assertEquals(
'foo
',
- $this->stripAllStuff(
+ $this->stripAllWhitespaces(
$this->cssToInlineStyles->convert($html, $css)
)
);
}
- private function stripAllStuff($content)
+ private function stripAllWhitespaces($content)
{
$content = str_replace(
array("\n", "\t"),
From fa040ce760b7c981b51209af6e24f44817d39899 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Mon, 23 Nov 2015 11:42:05 +0100
Subject: [PATCH 18/39] Fixed a stupid mistake where I called a method twice
---
src/Css/Property/Processor.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Css/Property/Processor.php b/src/Css/Property/Processor.php
index 27cd3ad..eee0eba 100644
--- a/src/Css/Property/Processor.php
+++ b/src/Css/Property/Processor.php
@@ -95,7 +95,7 @@ public function convertArrayToObjects(array $properties)
continue;
}
- $objects[] = $this->convertToObject($property);
+ $objects[] = $object;
}
return $objects;
From 06dd977cc5812f1f320b42d87911bb02f740d1bb Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Tue, 24 Nov 2015 14:22:00 +0100
Subject: [PATCH 19/39] : doesn't have a cased variant
---
src/Css/Property/Processor.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Css/Property/Processor.php b/src/Css/Property/Processor.php
index eee0eba..5f97de8 100644
--- a/src/Css/Property/Processor.php
+++ b/src/Css/Property/Processor.php
@@ -63,7 +63,7 @@ private function cleanup($string)
*/
public function convertToObject($property)
{
- if (stripos($property, ':') === false) {
+ if (strpos($property, ':') === false) {
return null;
}
From 10ec2eb930c474aa4127ec2f503dfe0114098222 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Tue, 24 Nov 2015 14:40:05 +0100
Subject: [PATCH 20/39] Better PHPdoc
---
src/Css/Processor.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Css/Processor.php b/src/Css/Processor.php
index d6f949a..8abd870 100644
--- a/src/Css/Processor.php
+++ b/src/Css/Processor.php
@@ -10,7 +10,7 @@ class Processor
* Get the rules from a given CSS-string
*
* @param string $css
- * @return array
+ * @return Rule[]
*/
public function getRules($css)
{
From e1d0d5ed826e42c1eaf8dad451ae1cc4eae35536 Mon Sep 17 00:00:00 2001
From: Tijs Verkoyen
Date: Tue, 24 Nov 2015 14:54:45 +0100
Subject: [PATCH 21/39] Use inline styles if they are present in $html
---
src/Css/Processor.php | 21 +++++++++++++++++++++
src/CssToInlineStyles.php | 28 ++++++++++++++++++++++++++--
2 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/src/Css/Processor.php b/src/Css/Processor.php
index 8abd870..2a52d07 100644
--- a/src/Css/Processor.php
+++ b/src/Css/Processor.php
@@ -21,6 +21,27 @@ public function getRules($css)
return $rulesProcessor->convertArrayToObjects($rules);
}
+ /**
+ * Get the CSS from the style-tags in the given HTML-string
+ *
+ * @param string $html
+ * @return string
+ */
+ public function getCssFromStyleTags($html)
+ {
+ $css = '';
+ $matches = array();
+ preg_match_all('||isU', $html, $matches);
+
+ if (!empty($matches[2])) {
+ foreach ($matches[2] as $match) {
+ $css .= trim($match) . "\n";
+ }
+ }
+
+ return $css;
+ }
+
/**
* @param string $css
* @return string
diff --git a/src/CssToInlineStyles.php b/src/CssToInlineStyles.php
index 927a305..dbe6497 100644
--- a/src/CssToInlineStyles.php
+++ b/src/CssToInlineStyles.php
@@ -9,10 +9,34 @@
class CssToInlineStyles
{
- public function convert($html, $css)
+ /**
+ * Will inline the $css into the given $html
+ *
+ * Remark: if the html contains
+
+
+ foo
+
+