See Release Notes
Long Term Support Release
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body