5 * @copyright 2012-2019 Leaf Corcoran
7 * @license http://opensource.org/licenses/MIT MIT
9 * @link http://scssphp.github.io/scssphp
12 namespace ScssPhp\ScssPhp
;
14 use ScssPhp\ScssPhp\Formatter\OutputBlock
;
15 use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
;
20 * @author Leaf Corcoran <leafot@gmail.com>
22 abstract class Formatter
57 public $assignSeparator;
62 public $keepSemicolons;
65 * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
67 protected $currentBlock;
72 protected $currentLine;
77 protected $currentColumn;
80 * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
82 protected $sourceMapGenerator;
87 protected $strippedSemicolon;
90 * Initialize formatter
94 abstract public function __construct();
97 * Return indentation (whitespace)
101 protected function indentStr()
107 * Return property assignment
111 * @param string $name
112 * @param mixed $value
116 public function property($name, $value)
118 return rtrim($name) . $this->assignSeparator
. $value . ';';
122 * Output lines inside a block
124 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
126 protected function blockLines(OutputBlock
$block)
128 $inner = $this->indentStr();
130 $glue = $this->break . $inner;
132 $this->write($inner . implode($glue, $block->lines
));
134 if (! empty($block->children
)) {
135 $this->write($this->break);
140 * Output block selectors
142 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
144 protected function blockSelectors(OutputBlock
$block)
146 $inner = $this->indentStr();
149 . implode($this->tagSeparator
, $block->selectors
)
150 . $this->open
. $this->break);
154 * Output block children
156 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
158 protected function blockChildren(OutputBlock
$block)
160 foreach ($block->children
as $child) {
161 $this->block($child);
166 * Output non-empty block
168 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
170 protected function block(OutputBlock
$block)
172 if (empty($block->lines
) && empty($block->children
)) {
176 $this->currentBlock
= $block;
178 $pre = $this->indentStr();
180 if (! empty($block->selectors
)) {
181 $this->blockSelectors($block);
183 $this->indentLevel++
;
186 if (! empty($block->lines
)) {
187 $this->blockLines($block);
190 if (! empty($block->children
)) {
191 $this->blockChildren($block);
194 if (! empty($block->selectors
)) {
195 $this->indentLevel
--;
197 if (! $this->keepSemicolons
) {
198 $this->strippedSemicolon
= '';
201 if (empty($block->children
)) {
202 $this->write($this->break);
205 $this->write($pre . $this->close
. $this->break);
210 * Test and clean safely empty children
212 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
216 protected function testEmptyChildren($block)
218 $isEmpty = empty($block->lines
);
220 if ($block->children
) {
221 foreach ($block->children
as $k => &$child) {
222 if (! $this->testEmptyChildren($child)) {
227 if ($child->type
=== Type
::T_MEDIA ||
$child->type
=== Type
::T_DIRECTIVE
) {
228 $child->children
= [];
229 $child->selectors
= null;
238 * Entry point to formatting a block
242 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree
243 * @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
247 public function format(OutputBlock
$block, SourceMapGenerator
$sourceMapGenerator = null)
249 $this->sourceMapGenerator
= null;
251 if ($sourceMapGenerator) {
252 $this->currentLine
= 1;
253 $this->currentColumn
= 0;
254 $this->sourceMapGenerator
= $sourceMapGenerator;
257 $this->testEmptyChildren($block);
261 $this->block($block);
263 $out = ob_get_clean();
273 protected function write($str)
275 if (! empty($this->strippedSemicolon
)) {
276 echo $this->strippedSemicolon
;
278 $this->strippedSemicolon
= '';
282 * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
283 * will be striped for real before a closing, otherwise displayed unchanged starting the next write
285 if (! $this->keepSemicolons
&&
287 (strpos($str, ';') !== false) &&
288 (substr($str, -1) === ';')
290 $str = substr($str, 0, -1);
292 $this->strippedSemicolon
= ';';
295 if ($this->sourceMapGenerator
) {
296 $this->sourceMapGenerator
->addMapping(
298 $this->currentColumn
,
299 $this->currentBlock
->sourceLine
,
300 //columns from parser are off by one
301 $this->currentBlock
->sourceColumn
> 0 ?
$this->currentBlock
->sourceColumn
- 1 : 0,
302 $this->currentBlock
->sourceName
305 $lines = explode("\n", $str);
306 $lineCount = count($lines);
307 $this->currentLine +
= $lineCount-1;
309 $lastLine = array_pop($lines);
311 $this->currentColumn
= ($lineCount === 1 ?
$this->currentColumn
: 0) +
strlen($lastLine);