Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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  
  12  namespace ScssPhp\ScssPhp;
  13  
  14  use ScssPhp\ScssPhp\Formatter\OutputBlock;
  15  use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
  16  
  17  /**
  18   * Base formatter
  19   *
  20   * @author Leaf Corcoran <leafot@gmail.com>
  21   */
  22  abstract class Formatter
  23  {
  24      /**
  25       * @var integer
  26       */
  27      public $indentLevel;
  28  
  29      /**
  30       * @var string
  31       */
  32      public $indentChar;
  33  
  34      /**
  35       * @var string
  36       */
  37      public $break;
  38  
  39      /**
  40       * @var string
  41       */
  42      public $open;
  43  
  44      /**
  45       * @var string
  46       */
  47      public $close;
  48  
  49      /**
  50       * @var string
  51       */
  52      public $tagSeparator;
  53  
  54      /**
  55       * @var string
  56       */
  57      public $assignSeparator;
  58  
  59      /**
  60       * @var boolean
  61       */
  62      public $keepSemicolons;
  63  
  64      /**
  65       * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
  66       */
  67      protected $currentBlock;
  68  
  69      /**
  70       * @var integer
  71       */
  72      protected $currentLine;
  73  
  74      /**
  75       * @var integer
  76       */
  77      protected $currentColumn;
  78  
  79      /**
  80       * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
  81       */
  82      protected $sourceMapGenerator;
  83  
  84      /**
  85       * @var string
  86       */
  87      protected $strippedSemicolon;
  88  
  89      /**
  90       * Initialize formatter
  91       *
  92       * @api
  93       */
  94      abstract public function __construct();
  95  
  96      /**
  97       * Return indentation (whitespace)
  98       *
  99       * @return string
 100       */
 101      protected function indentStr()
 102      {
 103          return '';
 104      }
 105  
 106      /**
 107       * Return property assignment
 108       *
 109       * @api
 110       *
 111       * @param string $name
 112       * @param mixed  $value
 113       *
 114       * @return string
 115       */
 116      public function property($name, $value)
 117      {
 118          return rtrim($name) . $this->assignSeparator . $value . ';';
 119      }
 120  
 121      /**
 122       * Output lines inside a block
 123       *
 124       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 125       */
 126      protected function blockLines(OutputBlock $block)
 127      {
 128          $inner = $this->indentStr();
 129  
 130          $glue = $this->break . $inner;
 131  
 132          $this->write($inner . implode($glue, $block->lines));
 133  
 134          if (! empty($block->children)) {
 135              $this->write($this->break);
 136          }
 137      }
 138  
 139      /**
 140       * Output block selectors
 141       *
 142       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 143       */
 144      protected function blockSelectors(OutputBlock $block)
 145      {
 146          $inner = $this->indentStr();
 147  
 148          $this->write($inner
 149              . implode($this->tagSeparator, $block->selectors)
 150              . $this->open . $this->break);
 151      }
 152  
 153      /**
 154       * Output block children
 155       *
 156       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 157       */
 158      protected function blockChildren(OutputBlock $block)
 159      {
 160          foreach ($block->children as $child) {
 161              $this->block($child);
 162          }
 163      }
 164  
 165      /**
 166       * Output non-empty block
 167       *
 168       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 169       */
 170      protected function block(OutputBlock $block)
 171      {
 172          if (empty($block->lines) && empty($block->children)) {
 173              return;
 174          }
 175  
 176          $this->currentBlock = $block;
 177  
 178          $pre = $this->indentStr();
 179  
 180          if (! empty($block->selectors)) {
 181              $this->blockSelectors($block);
 182  
 183              $this->indentLevel++;
 184          }
 185  
 186          if (! empty($block->lines)) {
 187              $this->blockLines($block);
 188          }
 189  
 190          if (! empty($block->children)) {
 191              $this->blockChildren($block);
 192          }
 193  
 194          if (! empty($block->selectors)) {
 195              $this->indentLevel--;
 196  
 197              if (! $this->keepSemicolons) {
 198                  $this->strippedSemicolon = '';
 199              }
 200  
 201              if (empty($block->children)) {
 202                  $this->write($this->break);
 203              }
 204  
 205              $this->write($pre . $this->close . $this->break);
 206          }
 207      }
 208  
 209      /**
 210       * Test and clean safely empty children
 211       *
 212       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 213       *
 214       * @return boolean
 215       */
 216      protected function testEmptyChildren($block)
 217      {
 218          $isEmpty = empty($block->lines);
 219  
 220          if ($block->children) {
 221              foreach ($block->children as $k => &$child) {
 222                  if (! $this->testEmptyChildren($child)) {
 223                      $isEmpty = false;
 224                      continue;
 225                  }
 226  
 227                  if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) {
 228                      $child->children = [];
 229                      $child->selectors = null;
 230                  }
 231              }
 232          }
 233  
 234          return $isEmpty;
 235      }
 236  
 237      /**
 238       * Entry point to formatting a block
 239       *
 240       * @api
 241       *
 242       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock             $block              An abstract syntax tree
 243       * @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
 244       *
 245       * @return string
 246       */
 247      public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
 248      {
 249          $this->sourceMapGenerator = null;
 250  
 251          if ($sourceMapGenerator) {
 252              $this->currentLine        = 1;
 253              $this->currentColumn      = 0;
 254              $this->sourceMapGenerator = $sourceMapGenerator;
 255          }
 256  
 257          $this->testEmptyChildren($block);
 258  
 259          ob_start();
 260  
 261          $this->block($block);
 262  
 263          $out = ob_get_clean();
 264  
 265          return $out;
 266      }
 267  
 268      /**
 269       * Output content
 270       *
 271       * @param string $str
 272       */
 273      protected function write($str)
 274      {
 275          if (! empty($this->strippedSemicolon)) {
 276              echo $this->strippedSemicolon;
 277  
 278              $this->strippedSemicolon = '';
 279          }
 280  
 281          /*
 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
 284           */
 285          if (! $this->keepSemicolons &&
 286              $str &&
 287              (strpos($str, ';') !== false) &&
 288              (substr($str, -1) === ';')
 289          ) {
 290              $str = substr($str, 0, -1);
 291  
 292              $this->strippedSemicolon = ';';
 293          }
 294  
 295          if ($this->sourceMapGenerator) {
 296              $this->sourceMapGenerator->addMapping(
 297                  $this->currentLine,
 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
 303              );
 304  
 305              $lines = explode("\n", $str);
 306              $lineCount = count($lines);
 307              $this->currentLine += $lineCount-1;
 308  
 309              $lastLine = array_pop($lines);
 310  
 311              $this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + strlen($lastLine);
 312          }
 313  
 314          echo $str;
 315      }
 316  }