/prestashop/_/tools/CssSplitter.php

https://gitlab.com/A.Julien/sendstockbymail-module-prestashop · PHP · 254 lines · 141 code · 30 blank · 83 comment · 20 complexity · 68bb6d57b37f5db3f1c1a9341d0ec2e7 MD5 · raw file

  1. <?php
  2. /**
  3. * PHP CSS Splitter
  4. *
  5. * Based on https://github.com/zweilove/css_splitter but written for PHP
  6. *
  7. * @homepage http://www.github.com/dlundgren/php-css-splitter
  8. * @author David Lundgren
  9. * @copyright 2014, David Lundgren. All Rights Reserved.
  10. * @license MIT
  11. */
  12. /**
  13. * Splitter of the CSS
  14. *
  15. * This class is responsible for counting and splitting a CSS file into given chunks.
  16. *
  17. * This is most useful for IE9 and lower which have a selector limit of 4096 per stylesheet
  18. *
  19. * @package CssSplitter
  20. */
  21. class CssSplitter
  22. {
  23. /**
  24. * @var integer The default number of selectors to split on
  25. */
  26. const MAX_SELECTORS_DEFAULT = 4095;
  27. /**
  28. * Counts the selectors in the given css
  29. *
  30. * @param string $css
  31. * @return int The count of selectors in the CSS
  32. */
  33. public function countSelectors($css)
  34. {
  35. $count = 0;
  36. foreach ($this->splitIntoBlocks($css) as $rules) {
  37. $count += $rules['count'];
  38. }
  39. return $count;
  40. }
  41. /**
  42. * Returns the requested part of the split css
  43. *
  44. * @param string $css
  45. * @param int $part
  46. * @param int $maxSelectors
  47. * @return null|string
  48. */
  49. public function split($css, $part = 1, $maxSelectors = self::MAX_SELECTORS_DEFAULT)
  50. {
  51. if (empty($css)) {
  52. return null;
  53. }
  54. $charset = $this->extractCharset($css);
  55. isset($charset) && $css = str_replace($charset, '', $css);
  56. if (empty($css)) {
  57. return null;
  58. }
  59. $blocks = $this->splitIntoBlocks($css);
  60. if (empty($blocks)) {
  61. return null;
  62. }
  63. $output = $charset ? : '';
  64. $count = 0;
  65. $partCount = 1;
  66. foreach ($blocks as $block) {
  67. $appliedMedia = false;
  68. foreach ($block['rules'] as $rule) {
  69. $tmpCount = $rule['count'];
  70. // we have a new part so let's reset and increase
  71. if (($count + $tmpCount) > $maxSelectors) {
  72. $partCount++;
  73. $count = 0;
  74. }
  75. $count += $tmpCount;
  76. if ($partCount < $part) {
  77. continue;
  78. }
  79. if ($partCount > $part) {
  80. break;
  81. }
  82. if (!$appliedMedia && isset($block['at-rule'])) {
  83. $output .= $block['at-rule'] . ' {';
  84. $appliedMedia = true;
  85. }
  86. $output .= $rule['rule'];
  87. }
  88. $appliedMedia && $output .= '}';
  89. }
  90. return $output;
  91. }
  92. /**
  93. * Summarizes the block of CSS
  94. *
  95. * This splits the block into it's css rules and then counts the selectors in the rule
  96. *
  97. * @param $block
  98. * @return array Array(rules,count)
  99. */
  100. private function summarizeBlock($block)
  101. {
  102. $block = array(
  103. 'rules' => is_array($block) ? $block : $this->splitIntoRules(trim($block)),
  104. 'count' => 0
  105. );
  106. foreach ($block['rules'] as $key => $rule) {
  107. $block['rules'][$key] = array(
  108. 'rule' => $rule,
  109. 'count' => $this->countSelectorsInRule($rule),
  110. );
  111. $block['count'] += $block['rules'][$key]['count'];
  112. }
  113. return $block;
  114. }
  115. /**
  116. * Splits the css into blocks maintaining the order of the rules and media queries
  117. *
  118. * This makes it easier to split the CSS into files when a media query might be split
  119. *
  120. * @param $css
  121. * @return array
  122. */
  123. private function splitIntoBlocks($css)
  124. {
  125. if (is_array($css)) {
  126. return $css;
  127. }
  128. $blocks = array();
  129. $css = $this->stripComments($css);
  130. $offset = 0;
  131. // catch all @feature {...} blocks
  132. if (preg_match_all('/(@[^{]+){([^{}]*{[^}]*})*\s*}/ism', $css, $matches, PREG_OFFSET_CAPTURE) > 0) {
  133. foreach ($matches[0] as $key => $match) {
  134. $atRule = trim($matches[1][$key][0]);
  135. list($rules, $start) = $match;
  136. if ($start > $offset) {
  137. // make previous selectors into their own block
  138. $block = trim(substr($css, $offset, $start - $offset));
  139. if (!empty($block)) {
  140. $blocks[] = $this->summarizeBlock($block);
  141. }
  142. }
  143. $offset = $start + strlen($rules);
  144. // currently only @media rules need to be parsed and counted for IE9 selectors
  145. if (strpos($atRule, '@media') === 0) {
  146. $block = $this->summarizeBlock(substr($rules, strpos($rules, '{') + 1, -1));
  147. }
  148. else {
  149. $block = array(
  150. 'count' => 1,
  151. 'rules' => array(
  152. array(
  153. 'rule' => substr($rules, strpos($rules, '{') + 1, -1),
  154. 'count' => 1,
  155. )
  156. ),
  157. );
  158. }
  159. $block['at-rule'] = $atRule;
  160. $blocks[] = $block;
  161. }
  162. // catch any remaining as it's own block
  163. $block = trim(substr($css, $offset));
  164. if (!empty($block)) {
  165. $blocks[] = $this->summarizeBlock($block);
  166. }
  167. }
  168. else {
  169. $blocks[] = $this->summarizeBlock($css);
  170. }
  171. return $blocks;
  172. }
  173. /**
  174. * Splits the css into it's rules
  175. *
  176. * @param $css
  177. * @return array
  178. */
  179. private function splitIntoRules($css)
  180. {
  181. $rules = preg_split('/}/', trim($this->stripComments($css)));
  182. // complete any rules by append } to them
  183. array_walk($rules,
  184. function (&$s) {
  185. !empty($s) && $s = trim("$s}");
  186. });
  187. // clears out any empty rules
  188. return array_filter($rules);
  189. }
  190. /**
  191. * Counts the selectors in the rule
  192. *
  193. * @param $rule
  194. * @return int
  195. */
  196. private function countSelectorsInRule($rule)
  197. {
  198. $lines = explode('{', $this->stripComments($rule));
  199. return substr_count($lines[0], ',') + 1;
  200. }
  201. /**
  202. * Extracts the charset from the rule
  203. *
  204. * @param $css
  205. * @return null|string
  206. */
  207. private function extractCharset($css)
  208. {
  209. if (preg_match('/^(\@charset[^;]+;)/is', $css, $match)) {
  210. return $match[1];
  211. }
  212. return null;
  213. }
  214. /**
  215. * Strips the comment
  216. *
  217. * @param $string
  218. * @return mixed
  219. */
  220. private function stripComments($string)
  221. {
  222. return preg_replace('~/\*.*?\*/~si', '', $string);
  223. }
  224. }