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