<?php
>
/**
* SCSSPHP
*
< * @copyright 2012-2019 Leaf Corcoran
> * @copyright 2012-2020 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp;
use ScssPhp\ScssPhp\Formatter\OutputBlock;
use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
/**
* Base formatter
*
* @author Leaf Corcoran <leafot@gmail.com>
> *
*/
> * @internal
abstract class Formatter
{
/**
< * @var integer
> * @var int
*/
public $indentLevel;
/**
* @var string
*/
public $indentChar;
/**
* @var string
*/
public $break;
/**
* @var string
*/
public $open;
/**
* @var string
*/
public $close;
/**
* @var string
*/
public $tagSeparator;
/**
* @var string
*/
public $assignSeparator;
/**
< * @var boolean
> * @var bool
*/
public $keepSemicolons;
/**
* @var \ScssPhp\ScssPhp\Formatter\OutputBlock
*/
protected $currentBlock;
/**
< * @var integer
> * @var int
*/
protected $currentLine;
/**
< * @var integer
> * @var int
*/
protected $currentColumn;
/**
< * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
> * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null
*/
protected $sourceMapGenerator;
/**
* @var string
*/
protected $strippedSemicolon;
/**
* Initialize formatter
*
* @api
*/
abstract public function __construct();
/**
* Return indentation (whitespace)
*
* @return string
*/
protected function indentStr()
{
return '';
}
/**
* Return property assignment
*
* @api
*
* @param string $name
* @param mixed $value
*
* @return string
*/
public function property($name, $value)
{
return rtrim($name) . $this->assignSeparator . $value . ';';
}
/**
> * Return custom property assignment
* Output lines inside a block
> * differs in that you have to keep spaces in the value as is
*
> *
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
> * @api
*/
> *
protected function blockLines(OutputBlock $block)
> * @param string $name
{
> * @param mixed $value
$inner = $this->indentStr();
> *
> * @return string
$glue = $this->break . $inner;
> */
> public function customProperty($name, $value)
$this->write($inner . implode($glue, $block->lines));
> {
> return rtrim($name) . trim($this->assignSeparator) . $value . ';';
if (! empty($block->children)) {
> }
$this->write($this->break);
>
}
> /**
}
> *
> * @return void
<
* Output block selectors
*
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
> *
*/
> * @return void
protected function blockSelectors(OutputBlock $block)
{
> assert(! empty($block->selectors));
$inner = $this->indentStr();
>
$this->write($inner
. implode($this->tagSeparator, $block->selectors)
. $this->open . $this->break);
}
/**
* Output block children
*
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
> *
*/
> * @return void
protected function blockChildren(OutputBlock $block)
{
foreach ($block->children as $child) {
$this->block($child);
}
}
/**
* Output non-empty block
*
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
> *
*/
> * @return void
protected function block(OutputBlock $block)
{
if (empty($block->lines) && empty($block->children)) {
return;
}
$this->currentBlock = $block;
$pre = $this->indentStr();
if (! empty($block->selectors)) {
$this->blockSelectors($block);
$this->indentLevel++;
}
if (! empty($block->lines)) {
$this->blockLines($block);
}
if (! empty($block->children)) {
$this->blockChildren($block);
}
if (! empty($block->selectors)) {
$this->indentLevel--;
if (! $this->keepSemicolons) {
$this->strippedSemicolon = '';
}
if (empty($block->children)) {
$this->write($this->break);
}
$this->write($pre . $this->close . $this->break);
}
}
/**
* Test and clean safely empty children
*
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
*
< * @return boolean
> * @return bool
*/
protected function testEmptyChildren($block)
{
$isEmpty = empty($block->lines);
if ($block->children) {
foreach ($block->children as $k => &$child) {
if (! $this->testEmptyChildren($child)) {
$isEmpty = false;
continue;
}
if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) {
$child->children = [];
$child->selectors = null;
}
}
}
return $isEmpty;
}
/**
* Entry point to formatting a block
*
* @api
*
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree
* @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
*
* @return string
*/
public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
{
$this->sourceMapGenerator = null;
if ($sourceMapGenerator) {
$this->currentLine = 1;
$this->currentColumn = 0;
$this->sourceMapGenerator = $sourceMapGenerator;
}
$this->testEmptyChildren($block);
ob_start();
> try {
$this->block($block);
> } catch (\Exception $e) {
> ob_end_clean();
$out = ob_get_clean();
> throw $e;
> } catch (\Throwable $e) {
return $out;
> ob_end_clean();
}
> throw $e;
> }
/**
> assert($out !== false);
* Output content
*
* @param string $str
> *
*/
> * @return void
protected function write($str)
{
if (! empty($this->strippedSemicolon)) {
echo $this->strippedSemicolon;
$this->strippedSemicolon = '';
}
/*
* Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
* will be striped for real before a closing, otherwise displayed unchanged starting the next write
*/
< if (! $this->keepSemicolons &&
> if (
> ! $this->keepSemicolons &&
$str &&
(strpos($str, ';') !== false) &&
(substr($str, -1) === ';')
) {
$str = substr($str, 0, -1);
$this->strippedSemicolon = ';';
}
if ($this->sourceMapGenerator) {
> $lines = explode("\n", $str);
$this->sourceMapGenerator->addMapping(
> $lastLine = array_pop($lines);
$this->currentLine,
>
$this->currentColumn,
> foreach ($lines as $line) {
$this->currentBlock->sourceLine,
> // If the written line starts is empty, adding a mapping would add it for
//columns from parser are off by one
> // a non-existent column as we are at the end of the line
$this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
> if ($line !== '') {
$this->currentBlock->sourceName
> assert($this->currentBlock->sourceLine !== null);
);
> assert($this->currentBlock->sourceName !== null);
> }
< $lines = explode("\n", $str);
< $lineCount = count($lines);
< $this->currentLine += $lineCount-1;
> $this->currentLine++;
> $this->currentColumn = 0;
> }
< $lastLine = array_pop($lines);
> if ($lastLine !== '') {
> assert($this->currentBlock->sourceLine !== null);
> assert($this->currentBlock->sourceName !== null);
> $this->sourceMapGenerator->addMapping(
> $this->currentLine,
> $this->currentColumn,
> $this->currentBlock->sourceLine,
> //columns from parser are off by one
> $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
> $this->currentBlock->sourceName
> );
> }
< $this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + strlen($lastLine);
> $this->currentColumn += \strlen($lastLine);
}
echo $str;
}
}