478aa6a56fd2511fb95ff7f7aa45f2f854aed11e
[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      * Initialize formatter
86      *
87      * @api
88      */
89     abstract public function __construct();
91     /**
92      * Return indentation (whitespace)
93      *
94      * @return string
95      */
96     protected function indentStr()
97     {
98         return '';
99     }
101     /**
102      * Return property assignment
103      *
104      * @api
105      *
106      * @param string $name
107      * @param mixed  $value
108      *
109      * @return string
110      */
111     public function property($name, $value)
112     {
113         return rtrim($name) . $this->assignSeparator . $value . ';';
114     }
116     /**
117      * Strip semi-colon appended by property(); it's a separator, not a terminator
118      *
119      * @api
120      *
121      * @param array $lines
122      */
123     public function stripSemicolon(&$lines)
124     {
125         if ($this->keepSemicolons) {
126             return;
127         }
129         if (($count = count($lines)) && substr($lines[$count - 1], -1) === ';') {
130             $lines[$count - 1] = substr($lines[$count - 1], 0, -1);
131         }
132     }
134     /**
135      * Output lines inside a block
136      *
137      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
138      */
139     protected function blockLines(OutputBlock $block)
140     {
141         $inner = $this->indentStr();
143         $glue = $this->break . $inner;
145         $this->write($inner . implode($glue, $block->lines));
147         if (! empty($block->children)) {
148             $this->write($this->break);
149         }
150     }
152     /**
153      * Output block selectors
154      *
155      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
156      */
157     protected function blockSelectors(OutputBlock $block)
158     {
159         $inner = $this->indentStr();
161         $this->write($inner
162             . implode($this->tagSeparator, $block->selectors)
163             . $this->open . $this->break);
164     }
166     /**
167      * Output block children
168      *
169      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
170      */
171     protected function blockChildren(OutputBlock $block)
172     {
173         foreach ($block->children as $child) {
174             $this->block($child);
175         }
176     }
178     /**
179      * Output non-empty block
180      *
181      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
182      */
183     protected function block(OutputBlock $block)
184     {
185         if (empty($block->lines) && empty($block->children)) {
186             return;
187         }
189         $this->currentBlock = $block;
191         $pre = $this->indentStr();
193         if (! empty($block->selectors)) {
194             $this->blockSelectors($block);
196             $this->indentLevel++;
197         }
199         if (! empty($block->lines)) {
200             $this->blockLines($block);
201         }
203         if (! empty($block->children)) {
204             $this->blockChildren($block);
205         }
207         if (! empty($block->selectors)) {
208             $this->indentLevel--;
210             if (empty($block->children)) {
211                 $this->write($this->break);
212             }
214             $this->write($pre . $this->close . $this->break);
215         }
216     }
218     /**
219      * Test and clean safely empty children
220      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
221      * @return bool
222      */
223     protected function testEmptyChildren($block)
224     {
225         $isEmpty = empty($block->lines);
227         if ($block->children) {
228             foreach ($block->children as $k => &$child) {
229                 if (! $this->testEmptyChildren($child)) {
230                     $isEmpty = false;
231                 } else {
232                     if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) {
233                         $child->children = [];
234                         $child->selectors = null;
235                     }
236                 }
237             }
238         }
239         return $isEmpty;
240     }
242     /**
243      * Entry point to formatting a block
244      *
245      * @api
246      *
247      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock             $block              An abstract syntax tree
248      * @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
249      *
250      * @return string
251      */
252     public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
253     {
254         $this->sourceMapGenerator = null;
256         if ($sourceMapGenerator) {
257             $this->currentLine = 1;
258             $this->currentColumn = 0;
259             $this->sourceMapGenerator = $sourceMapGenerator;
260         }
262         $this->testEmptyChildren($block);
264         ob_start();
266         $this->block($block);
268         $out = ob_get_clean();
270         return $out;
271     }
273     /**
274      * @param string $str
275      */
276     protected function write($str)
277     {
278         if ($this->sourceMapGenerator) {
279             $this->sourceMapGenerator->addMapping(
280                 $this->currentLine,
281                 $this->currentColumn,
282                 $this->currentBlock->sourceLine,
283                 //columns from parser are off by one
284                 $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
285                 $this->currentBlock->sourceName
286             );
288             $lines = explode("\n", $str);
289             $lineCount = count($lines);
290             $this->currentLine += $lineCount-1;
292             $lastLine = array_pop($lines);
294             $this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + strlen($lastLine);
295         }
297         echo $str;
298     }