PageRenderTime 25ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/scssphp/Formatter.php

http://github.com/moodle/moodle
PHP | 316 lines | 140 code | 56 blank | 120 comment | 17 complexity | f424bba841b2fe10024b99bf7d49f1ea MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * SCSSPHP
  4. *
  5. * @copyright 2012-2019 Leaf Corcoran
  6. *
  7. * @license http://opensource.org/licenses/MIT MIT
  8. *
  9. * @link http://scssphp.github.io/scssphp
  10. */
  11. namespace ScssPhp\ScssPhp;
  12. use ScssPhp\ScssPhp\Formatter\OutputBlock;
  13. use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
  14. /**
  15. * Base formatter
  16. *
  17. * @author Leaf Corcoran <leafot@gmail.com>
  18. */
  19. abstract class Formatter
  20. {
  21. /**
  22. * @var integer
  23. */
  24. public $indentLevel;
  25. /**
  26. * @var string
  27. */
  28. public $indentChar;
  29. /**
  30. * @var string
  31. */
  32. public $break;
  33. /**
  34. * @var string
  35. */
  36. public $open;
  37. /**
  38. * @var string
  39. */
  40. public $close;
  41. /**
  42. * @var string
  43. */
  44. public $tagSeparator;
  45. /**
  46. * @var string
  47. */
  48. public $assignSeparator;
  49. /**
  50. * @var boolean
  51. */
  52. public $keepSemicolons;
  53. /**
  54. * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
  55. */
  56. protected $currentBlock;
  57. /**
  58. * @var integer
  59. */
  60. protected $currentLine;
  61. /**
  62. * @var integer
  63. */
  64. protected $currentColumn;
  65. /**
  66. * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
  67. */
  68. protected $sourceMapGenerator;
  69. /**
  70. * @var string
  71. */
  72. protected $strippedSemicolon;
  73. /**
  74. * Initialize formatter
  75. *
  76. * @api
  77. */
  78. abstract public function __construct();
  79. /**
  80. * Return indentation (whitespace)
  81. *
  82. * @return string
  83. */
  84. protected function indentStr()
  85. {
  86. return '';
  87. }
  88. /**
  89. * Return property assignment
  90. *
  91. * @api
  92. *
  93. * @param string $name
  94. * @param mixed $value
  95. *
  96. * @return string
  97. */
  98. public function property($name, $value)
  99. {
  100. return rtrim($name) . $this->assignSeparator . $value . ';';
  101. }
  102. /**
  103. * Output lines inside a block
  104. *
  105. * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
  106. */
  107. protected function blockLines(OutputBlock $block)
  108. {
  109. $inner = $this->indentStr();
  110. $glue = $this->break . $inner;
  111. $this->write($inner . implode($glue, $block->lines));
  112. if (! empty($block->children)) {
  113. $this->write($this->break);
  114. }
  115. }
  116. /**
  117. * Output block selectors
  118. *
  119. * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
  120. */
  121. protected function blockSelectors(OutputBlock $block)
  122. {
  123. $inner = $this->indentStr();
  124. $this->write($inner
  125. . implode($this->tagSeparator, $block->selectors)
  126. . $this->open . $this->break);
  127. }
  128. /**
  129. * Output block children
  130. *
  131. * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
  132. */
  133. protected function blockChildren(OutputBlock $block)
  134. {
  135. foreach ($block->children as $child) {
  136. $this->block($child);
  137. }
  138. }
  139. /**
  140. * Output non-empty block
  141. *
  142. * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
  143. */
  144. protected function block(OutputBlock $block)
  145. {
  146. if (empty($block->lines) && empty($block->children)) {
  147. return;
  148. }
  149. $this->currentBlock = $block;
  150. $pre = $this->indentStr();
  151. if (! empty($block->selectors)) {
  152. $this->blockSelectors($block);
  153. $this->indentLevel++;
  154. }
  155. if (! empty($block->lines)) {
  156. $this->blockLines($block);
  157. }
  158. if (! empty($block->children)) {
  159. $this->blockChildren($block);
  160. }
  161. if (! empty($block->selectors)) {
  162. $this->indentLevel--;
  163. if (! $this->keepSemicolons) {
  164. $this->strippedSemicolon = '';
  165. }
  166. if (empty($block->children)) {
  167. $this->write($this->break);
  168. }
  169. $this->write($pre . $this->close . $this->break);
  170. }
  171. }
  172. /**
  173. * Test and clean safely empty children
  174. *
  175. * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
  176. *
  177. * @return boolean
  178. */
  179. protected function testEmptyChildren($block)
  180. {
  181. $isEmpty = empty($block->lines);
  182. if ($block->children) {
  183. foreach ($block->children as $k => &$child) {
  184. if (! $this->testEmptyChildren($child)) {
  185. $isEmpty = false;
  186. continue;
  187. }
  188. if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) {
  189. $child->children = [];
  190. $child->selectors = null;
  191. }
  192. }
  193. }
  194. return $isEmpty;
  195. }
  196. /**
  197. * Entry point to formatting a block
  198. *
  199. * @api
  200. *
  201. * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree
  202. * @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
  203. *
  204. * @return string
  205. */
  206. public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
  207. {
  208. $this->sourceMapGenerator = null;
  209. if ($sourceMapGenerator) {
  210. $this->currentLine = 1;
  211. $this->currentColumn = 0;
  212. $this->sourceMapGenerator = $sourceMapGenerator;
  213. }
  214. $this->testEmptyChildren($block);
  215. ob_start();
  216. $this->block($block);
  217. $out = ob_get_clean();
  218. return $out;
  219. }
  220. /**
  221. * Output content
  222. *
  223. * @param string $str
  224. */
  225. protected function write($str)
  226. {
  227. if (! empty($this->strippedSemicolon)) {
  228. echo $this->strippedSemicolon;
  229. $this->strippedSemicolon = '';
  230. }
  231. /*
  232. * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
  233. * will be striped for real before a closing, otherwise displayed unchanged starting the next write
  234. */
  235. if (! $this->keepSemicolons &&
  236. $str &&
  237. (strpos($str, ';') !== false) &&
  238. (substr($str, -1) === ';')
  239. ) {
  240. $str = substr($str, 0, -1);
  241. $this->strippedSemicolon = ';';
  242. }
  243. if ($this->sourceMapGenerator) {
  244. $this->sourceMapGenerator->addMapping(
  245. $this->currentLine,
  246. $this->currentColumn,
  247. $this->currentBlock->sourceLine,
  248. //columns from parser are off by one
  249. $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
  250. $this->currentBlock->sourceName
  251. );
  252. $lines = explode("\n", $str);
  253. $lineCount = count($lines);
  254. $this->currentLine += $lineCount-1;
  255. $lastLine = array_pop($lines);
  256. $this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + strlen($lastLine);
  257. }
  258. echo $str;
  259. }
  260. }