Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   1  <?php
   2  
   3  /**
   4   * SCSSPHP
   5   *
   6   * @copyright 2012-2020 Leaf Corcoran
   7   *
   8   * @license http://opensource.org/licenses/MIT MIT
   9   *
  10   * @link http://scssphp.github.io/scssphp
  11   */
  12  
  13  namespace ScssPhp\ScssPhp;
  14  
  15  use ScssPhp\ScssPhp\Formatter\OutputBlock;
  16  use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
  17  
  18  /**
  19   * Base formatter
  20   *
  21   * @author Leaf Corcoran <leafot@gmail.com>
  22   */
  23  abstract class Formatter
  24  {
  25      /**
  26       * @var integer
  27       */
  28      public $indentLevel;
  29  
  30      /**
  31       * @var string
  32       */
  33      public $indentChar;
  34  
  35      /**
  36       * @var string
  37       */
  38      public $break;
  39  
  40      /**
  41       * @var string
  42       */
  43      public $open;
  44  
  45      /**
  46       * @var string
  47       */
  48      public $close;
  49  
  50      /**
  51       * @var string
  52       */
  53      public $tagSeparator;
  54  
  55      /**
  56       * @var string
  57       */
  58      public $assignSeparator;
  59  
  60      /**
  61       * @var boolean
  62       */
  63      public $keepSemicolons;
  64  
  65      /**
  66       * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
  67       */
  68      protected $currentBlock;
  69  
  70      /**
  71       * @var integer
  72       */
  73      protected $currentLine;
  74  
  75      /**
  76       * @var integer
  77       */
  78      protected $currentColumn;
  79  
  80      /**
  81       * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
  82       */
  83      protected $sourceMapGenerator;
  84  
  85      /**
  86       * @var string
  87       */
  88      protected $strippedSemicolon;
  89  
  90      /**
  91       * Initialize formatter
  92       *
  93       * @api
  94       */
  95      abstract public function __construct();
  96  
  97      /**
  98       * Return indentation (whitespace)
  99       *
 100       * @return string
 101       */
 102      protected function indentStr()
 103      {
 104          return '';
 105      }
 106  
 107      /**
 108       * Return property assignment
 109       *
 110       * @api
 111       *
 112       * @param string $name
 113       * @param mixed  $value
 114       *
 115       * @return string
 116       */
 117      public function property($name, $value)
 118      {
 119          return rtrim($name) . $this->assignSeparator . $value . ';';
 120      }
 121  
 122      /**
 123       * Return custom property assignment
 124       * differs in that you have to keep spaces in the value as is
 125       *
 126       * @api
 127       *
 128       * @param string $name
 129       * @param mixed  $value
 130       *
 131       * @return string
 132       */
 133      public function customProperty($name, $value)
 134      {
 135          return rtrim($name) . trim($this->assignSeparator) . $value . ';';
 136      }
 137  
 138      /**
 139       * Output lines inside a block
 140       *
 141       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 142       */
 143      protected function blockLines(OutputBlock $block)
 144      {
 145          $inner = $this->indentStr();
 146          $glue  = $this->break . $inner;
 147  
 148          $this->write($inner . implode($glue, $block->lines));
 149  
 150          if (! empty($block->children)) {
 151              $this->write($this->break);
 152          }
 153      }
 154  
 155      /**
 156       * Output block selectors
 157       *
 158       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 159       */
 160      protected function blockSelectors(OutputBlock $block)
 161      {
 162          $inner = $this->indentStr();
 163  
 164          $this->write($inner
 165              . implode($this->tagSeparator, $block->selectors)
 166              . $this->open . $this->break);
 167      }
 168  
 169      /**
 170       * Output block children
 171       *
 172       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 173       */
 174      protected function blockChildren(OutputBlock $block)
 175      {
 176          foreach ($block->children as $child) {
 177              $this->block($child);
 178          }
 179      }
 180  
 181      /**
 182       * Output non-empty block
 183       *
 184       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 185       */
 186      protected function block(OutputBlock $block)
 187      {
 188          if (empty($block->lines) && empty($block->children)) {
 189              return;
 190          }
 191  
 192          $this->currentBlock = $block;
 193  
 194          $pre = $this->indentStr();
 195  
 196          if (! empty($block->selectors)) {
 197              $this->blockSelectors($block);
 198  
 199              $this->indentLevel++;
 200          }
 201  
 202          if (! empty($block->lines)) {
 203              $this->blockLines($block);
 204          }
 205  
 206          if (! empty($block->children)) {
 207              $this->blockChildren($block);
 208          }
 209  
 210          if (! empty($block->selectors)) {
 211              $this->indentLevel--;
 212  
 213              if (! $this->keepSemicolons) {
 214                  $this->strippedSemicolon = '';
 215              }
 216  
 217              if (empty($block->children)) {
 218                  $this->write($this->break);
 219              }
 220  
 221              $this->write($pre . $this->close . $this->break);
 222          }
 223      }
 224  
 225      /**
 226       * Test and clean safely empty children
 227       *
 228       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 229       *
 230       * @return boolean
 231       */
 232      protected function testEmptyChildren($block)
 233      {
 234          $isEmpty = empty($block->lines);
 235  
 236          if ($block->children) {
 237              foreach ($block->children as $k => &$child) {
 238                  if (! $this->testEmptyChildren($child)) {
 239                      $isEmpty = false;
 240                      continue;
 241                  }
 242  
 243                  if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) {
 244                      $child->children = [];
 245                      $child->selectors = null;
 246                  }
 247              }
 248          }
 249  
 250          return $isEmpty;
 251      }
 252  
 253      /**
 254       * Entry point to formatting a block
 255       *
 256       * @api
 257       *
 258       * @param \ScssPhp\ScssPhp\Formatter\OutputBlock             $block              An abstract syntax tree
 259       * @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
 260       *
 261       * @return string
 262       */
 263      public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
 264      {
 265          $this->sourceMapGenerator = null;
 266  
 267          if ($sourceMapGenerator) {
 268              $this->currentLine        = 1;
 269              $this->currentColumn      = 0;
 270              $this->sourceMapGenerator = $sourceMapGenerator;
 271          }
 272  
 273          $this->testEmptyChildren($block);
 274  
 275          ob_start();
 276  
 277          $this->block($block);
 278  
 279          $out = ob_get_clean();
 280  
 281          return $out;
 282      }
 283  
 284      /**
 285       * Output content
 286       *
 287       * @param string $str
 288       */
 289      protected function write($str)
 290      {
 291          if (! empty($this->strippedSemicolon)) {
 292              echo $this->strippedSemicolon;
 293  
 294              $this->strippedSemicolon = '';
 295          }
 296  
 297          /*
 298           * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
 299           * will be striped for real before a closing, otherwise displayed unchanged starting the next write
 300           */
 301          if (
 302              ! $this->keepSemicolons &&
 303              $str &&
 304              (strpos($str, ';') !== false) &&
 305              (substr($str, -1) === ';')
 306          ) {
 307              $str = substr($str, 0, -1);
 308  
 309              $this->strippedSemicolon = ';';
 310          }
 311  
 312          if ($this->sourceMapGenerator) {
 313              $lines = explode("\n", $str);
 314              $lastLine = array_pop($lines);
 315  
 316              foreach ($lines as $line) {
 317                  // If the written line starts is empty, adding a mapping would add it for
 318                  // a non-existent column as we are at the end of the line
 319                  if ($line !== '') {
 320                      $this->sourceMapGenerator->addMapping(
 321                          $this->currentLine,
 322                          $this->currentColumn,
 323                          $this->currentBlock->sourceLine,
 324                          //columns from parser are off by one
 325                          $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
 326                          $this->currentBlock->sourceName
 327                      );
 328                  }
 329  
 330                  $this->currentLine++;
 331                  $this->currentColumn = 0;
 332              }
 333  
 334              if ($lastLine !== '') {
 335                  $this->sourceMapGenerator->addMapping(
 336                      $this->currentLine,
 337                      $this->currentColumn,
 338                      $this->currentBlock->sourceLine,
 339                      //columns from parser are off by one
 340                      $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
 341                      $this->currentBlock->sourceName
 342                  );
 343              }
 344  
 345              $this->currentColumn += \strlen($lastLine);
 346          }
 347  
 348          echo $str;
 349      }
 350  }