| 148 |
lars |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
namespace TijsVerkoyen\CssToInlineStyles\Css\Rule;
|
|
|
4 |
|
|
|
5 |
use Symfony\Component\CssSelector\Node\Specificity;
|
|
|
6 |
use \TijsVerkoyen\CssToInlineStyles\Css\Property\Processor as PropertyProcessor;
|
|
|
7 |
|
|
|
8 |
class Processor
|
|
|
9 |
{
|
|
|
10 |
/**
|
|
|
11 |
* Splits a string into separate rules
|
|
|
12 |
*
|
|
|
13 |
* @param string $rulesString
|
|
|
14 |
*
|
|
|
15 |
* @return string[]
|
|
|
16 |
*/
|
|
|
17 |
public function splitIntoSeparateRules($rulesString)
|
|
|
18 |
{
|
|
|
19 |
$rulesString = $this->cleanup($rulesString);
|
|
|
20 |
|
|
|
21 |
return (array) explode('}', $rulesString);
|
|
|
22 |
}
|
|
|
23 |
|
|
|
24 |
/**
|
|
|
25 |
* @param string $string
|
|
|
26 |
*
|
|
|
27 |
* @return string
|
|
|
28 |
*/
|
|
|
29 |
private function cleanup($string)
|
|
|
30 |
{
|
|
|
31 |
$string = str_replace(array("\r", "\n"), '', $string);
|
|
|
32 |
$string = str_replace(array("\t"), ' ', $string);
|
|
|
33 |
$string = str_replace('"', '\'', $string);
|
|
|
34 |
$string = preg_replace('|/\*.*?\*/|', '', $string);
|
|
|
35 |
$string = preg_replace('/\s\s+/', ' ', $string);
|
|
|
36 |
|
|
|
37 |
$string = trim($string);
|
|
|
38 |
$string = rtrim($string, '}');
|
|
|
39 |
|
|
|
40 |
return $string;
|
|
|
41 |
}
|
|
|
42 |
|
|
|
43 |
/**
|
|
|
44 |
* Converts a rule-string into an object
|
|
|
45 |
*
|
|
|
46 |
* @param string $rule
|
|
|
47 |
* @param int $originalOrder
|
|
|
48 |
*
|
|
|
49 |
* @return Rule[]
|
|
|
50 |
*/
|
|
|
51 |
public function convertToObjects($rule, $originalOrder)
|
|
|
52 |
{
|
|
|
53 |
$rule = $this->cleanup($rule);
|
|
|
54 |
|
|
|
55 |
$chunks = explode('{', $rule);
|
|
|
56 |
if (!isset($chunks[1])) {
|
|
|
57 |
return array();
|
|
|
58 |
}
|
|
|
59 |
$propertiesProcessor = new PropertyProcessor();
|
|
|
60 |
$rules = array();
|
|
|
61 |
$selectors = (array) explode(',', trim($chunks[0]));
|
|
|
62 |
$properties = $propertiesProcessor->splitIntoSeparateProperties($chunks[1]);
|
|
|
63 |
|
|
|
64 |
foreach ($selectors as $selector) {
|
|
|
65 |
$selector = trim($selector);
|
|
|
66 |
$specificity = $this->calculateSpecificityBasedOnASelector($selector);
|
|
|
67 |
|
|
|
68 |
$rules[] = new Rule(
|
|
|
69 |
$selector,
|
|
|
70 |
$propertiesProcessor->convertArrayToObjects($properties, $specificity),
|
|
|
71 |
$specificity,
|
|
|
72 |
$originalOrder
|
|
|
73 |
);
|
|
|
74 |
}
|
|
|
75 |
|
|
|
76 |
return $rules;
|
|
|
77 |
}
|
|
|
78 |
|
|
|
79 |
/**
|
|
|
80 |
* Calculates the specificity based on a CSS Selector string,
|
|
|
81 |
* Based on the patterns from premailer/css_parser by Alex Dunae
|
|
|
82 |
*
|
|
|
83 |
* @see https://github.com/premailer/css_parser/blob/master/lib/css_parser/regexps.rb
|
|
|
84 |
*
|
|
|
85 |
* @param string $selector
|
|
|
86 |
*
|
|
|
87 |
* @return Specificity
|
|
|
88 |
*/
|
|
|
89 |
public function calculateSpecificityBasedOnASelector($selector)
|
|
|
90 |
{
|
|
|
91 |
$idSelectorsPattern = " \#";
|
|
|
92 |
$classAttributesPseudoClassesSelectorsPattern = " (\.[\w]+) # classes
|
|
|
93 |
|
|
|
|
94 |
\[(\w+) # attributes
|
|
|
95 |
|
|
|
|
96 |
(\:( # pseudo classes
|
|
|
97 |
link|visited|active
|
|
|
98 |
|hover|focus
|
|
|
99 |
|lang
|
|
|
100 |
|target
|
|
|
101 |
|enabled|disabled|checked|indeterminate
|
|
|
102 |
|root
|
|
|
103 |
|nth-child|nth-last-child|nth-of-type|nth-last-of-type
|
|
|
104 |
|first-child|last-child|first-of-type|last-of-type
|
|
|
105 |
|only-child|only-of-type
|
|
|
106 |
|empty|contains
|
|
|
107 |
))";
|
|
|
108 |
|
|
|
109 |
$typePseudoElementsSelectorPattern = " ((^|[\s\+\>\~]+)[\w]+ # elements
|
|
|
110 |
|
|
|
|
111 |
\:{1,2}( # pseudo-elements
|
|
|
112 |
after|before
|
|
|
113 |
|first-letter|first-line
|
|
|
114 |
|selection
|
|
|
115 |
)
|
|
|
116 |
)";
|
|
|
117 |
|
|
|
118 |
return new Specificity(
|
|
|
119 |
preg_match_all("/{$idSelectorsPattern}/ix", $selector, $matches),
|
|
|
120 |
preg_match_all("/{$classAttributesPseudoClassesSelectorsPattern}/ix", $selector, $matches),
|
|
|
121 |
preg_match_all("/{$typePseudoElementsSelectorPattern}/ix", $selector, $matches)
|
|
|
122 |
);
|
|
|
123 |
}
|
|
|
124 |
|
|
|
125 |
/**
|
|
|
126 |
* @param string[] $rules
|
|
|
127 |
* @param Rule[] $objects
|
|
|
128 |
*
|
|
|
129 |
* @return Rule[]
|
|
|
130 |
*/
|
|
|
131 |
public function convertArrayToObjects(array $rules, array $objects = array())
|
|
|
132 |
{
|
|
|
133 |
$order = 1;
|
|
|
134 |
foreach ($rules as $rule) {
|
|
|
135 |
$objects = array_merge($objects, $this->convertToObjects($rule, $order));
|
|
|
136 |
$order++;
|
|
|
137 |
}
|
|
|
138 |
|
|
|
139 |
return $objects;
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
/**
|
|
|
143 |
* Sorts an array on the specificity element in an ascending way
|
|
|
144 |
* Lower specificity will be sorted to the beginning of the array
|
|
|
145 |
*
|
|
|
146 |
* @param Rule $e1 The first element.
|
|
|
147 |
* @param Rule $e2 The second element.
|
|
|
148 |
*
|
|
|
149 |
* @return int
|
|
|
150 |
*/
|
|
|
151 |
public static function sortOnSpecificity(Rule $e1, Rule $e2)
|
|
|
152 |
{
|
|
|
153 |
$e1Specificity = $e1->getSpecificity();
|
|
|
154 |
$value = $e1Specificity->compareTo($e2->getSpecificity());
|
|
|
155 |
|
|
|
156 |
// if the specificity is the same, use the order in which the element appeared
|
|
|
157 |
if ($value === 0) {
|
|
|
158 |
$value = $e1->getOrder() - $e2->getOrder();
|
|
|
159 |
}
|
|
|
160 |
|
|
|
161 |
return $value;
|
|
|
162 |
}
|
|
|
163 |
}
|