Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
<?php

/**
 * SCSSPHP
 *
 * @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 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 bool
     */
    public $keepSemicolons;

    /**
     * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
     */
    protected $currentBlock;

    /**
     * @var int
     */
    protected $currentLine;

    /**
     * @var int
     */
    protected $currentColumn;

    /**
     * @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
     * differs in that you have to keep spaces in the value as is
     *
     * @api
     *
     * @param string $name
     * @param mixed  $value
     *
     * @return string
     */
    public function customProperty($name, $value)
    {
        return rtrim($name) . trim($this->assignSeparator) . $value . ';';
    }

    /**
     * Output lines inside a block
     *
     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
     *
     * @return void
     */
    protected function blockLines(OutputBlock $block)
    {
        $inner = $this->indentStr();
        $glue  = $this->break . $inner;

        $this->write($inner . implode($glue, $block->lines));

        if (! empty($block->children)) {
            $this->write($this->break);
        }
    }

    /**
     * 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 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 && $str && (strpos($str, ';') !== false) && (substr($str, -1) === ';') ) { $str = substr($str, 0, -1); $this->strippedSemicolon = ';'; } if ($this->sourceMapGenerator) { $lines = explode("\n", $str); $lastLine = array_pop($lines); foreach ($lines as $line) { // If the written line starts is empty, adding a mapping would add it for // a non-existent column as we are at the end of the line if ($line !== '') {
> assert($this->currentBlock->sourceLine !== null); $this->sourceMapGenerator->addMapping( > assert($this->currentBlock->sourceName !== null);
$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->currentLine++; $this->currentColumn = 0; } if ($lastLine !== '') {
> assert($this->currentBlock->sourceLine !== null); $this->sourceMapGenerator->addMapping( > assert($this->currentBlock->sourceName !== null);
$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 += \strlen($lastLine); } echo $str; } }