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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body