MDL-67379 libraries: Upgrade scssphp to 1.0.6
[moodle.git] / lib / scssphp / Formatter.php
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  */
12 namespace ScssPhp\ScssPhp;
14 use ScssPhp\ScssPhp\Formatter\OutputBlock;
15 use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
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;
29     /**
30      * @var string
31      */
32     public $indentChar;
34     /**
35      * @var string
36      */
37     public $break;
39     /**
40      * @var string
41      */
42     public $open;
44     /**
45      * @var string
46      */
47     public $close;
49     /**
50      * @var string
51      */
52     public $tagSeparator;
54     /**
55      * @var string
56      */
57     public $assignSeparator;
59     /**
60      * @var boolean
61      */
62     public $keepSemicolons;
64     /**
65      * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
66      */
67     protected $currentBlock;
69     /**
70      * @var integer
71      */
72     protected $currentLine;
74     /**
75      * @var integer
76      */
77     protected $currentColumn;
79     /**
80      * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
81      */
82     protected $sourceMapGenerator;
84     /**
85      * @var string
86      */
87     protected $strippedSemicolon;
89     /**
90      * Initialize formatter
91      *
92      * @api
93      */
94     abstract public function __construct();
96     /**
97      * Return indentation (whitespace)
98      *
99      * @return string
100      */
101     protected function indentStr()
102     {
103         return '';
104     }
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     }
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();
130         $glue = $this->break . $inner;
132         $this->write($inner . implode($glue, $block->lines));
134         if (! empty($block->children)) {
135             $this->write($this->break);
136         }
137     }
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();
148         $this->write($inner
149             . implode($this->tagSeparator, $block->selectors)
150             . $this->open . $this->break);
151     }
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     }
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         }
176         $this->currentBlock = $block;
178         $pre = $this->indentStr();
180         if (! empty($block->selectors)) {
181             $this->blockSelectors($block);
183             $this->indentLevel++;
184         }
186         if (! empty($block->lines)) {
187             $this->blockLines($block);
188         }
190         if (! empty($block->children)) {
191             $this->blockChildren($block);
192         }
194         if (! empty($block->selectors)) {
195             $this->indentLevel--;
197             if (! $this->keepSemicolons) {
198                 $this->strippedSemicolon = '';
199             }
201             if (empty($block->children)) {
202                 $this->write($this->break);
203             }
205             $this->write($pre . $this->close . $this->break);
206         }
207     }
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);
220         if ($block->children) {
221             foreach ($block->children as $k => &$child) {
222                 if (! $this->testEmptyChildren($child)) {
223                     $isEmpty = false;
224                     continue;
225                 }
227                 if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) {
228                     $child->children = [];
229                     $child->selectors = null;
230                 }
231             }
232         }
234         return $isEmpty;
235     }
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;
251         if ($sourceMapGenerator) {
252             $this->currentLine        = 1;
253             $this->currentColumn      = 0;
254             $this->sourceMapGenerator = $sourceMapGenerator;
255         }
257         $this->testEmptyChildren($block);
259         ob_start();
261         $this->block($block);
263         $out = ob_get_clean();
265         return $out;
266     }
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;
278             $this->strippedSemicolon = '';
279         }
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);
292             $this->strippedSemicolon = ';';
293         }
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             );
305             $lines = explode("\n", $str);
306             $lineCount = count($lines);
307             $this->currentLine += $lineCount-1;
309             $lastLine = array_pop($lines);
311             $this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + strlen($lastLine);
312         }
314         echo $str;
315     }