Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
/*
4
 * This file is part of Mustache.php.
5
 *
6
 * (c) 2010-2017 Justin Hileman
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
/**
13
 * Mustache Compiler class.
14
 *
15
 * This class is responsible for turning a Mustache token parse tree into normal PHP source code.
16
 */
17
class Mustache_Compiler
18
{
19
    private $pragmas;
20
    private $defaultPragmas = array();
21
    private $sections;
22
    private $blocks;
23
    private $source;
24
    private $indentNextLine;
25
    private $customEscape;
26
    private $entityFlags;
27
    private $charset;
28
    private $strictCallables;
29
    private $disableLambdaRendering;
30
 
31
    /**
32
     * Compile a Mustache token parse tree into PHP source code.
33
     *
34
     * @param string $source                 Mustache Template source code
35
     * @param array  $tree                   Parse tree of Mustache tokens
36
     * @param string $name                   Mustache Template class name
37
     * @param bool   $customEscape           (default: false)
38
     * @param string $charset                (default: 'UTF-8')
39
     * @param bool   $strictCallables        (default: false)
40
     * @param int    $entityFlags            (default: ENT_COMPAT)
41
     * @param bool   $disableLambdaRendering (default: false)
42
     *
43
     * @return string Generated PHP source code
44
     */
45
    public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT, $disableLambdaRendering = false)
46
    {
47
        $this->pragmas                = $this->defaultPragmas;
48
        $this->sections               = array();
49
        $this->blocks                 = array();
50
        $this->source                 = $source;
51
        $this->indentNextLine         = true;
52
        $this->customEscape           = $customEscape;
53
        $this->entityFlags            = $entityFlags;
54
        $this->charset                = $charset;
55
        $this->strictCallables        = $strictCallables;
56
        $this->disableLambdaRendering = $disableLambdaRendering;
57
 
58
        return $this->writeCode($tree, $name);
59
    }
60
 
61
    /**
62
     * Enable pragmas across all templates, regardless of the presence of pragma
63
     * tags in the individual templates.
64
     *
65
     * @internal Users should set global pragmas in Mustache_Engine, not here :)
66
     *
67
     * @param string[] $pragmas
68
     */
69
    public function setPragmas(array $pragmas)
70
    {
71
        $this->pragmas = array();
72
        foreach ($pragmas as $pragma) {
73
            $this->pragmas[$pragma] = true;
74
        }
75
        $this->defaultPragmas = $this->pragmas;
76
    }
77
 
78
    /**
79
     * Helper function for walking the Mustache token parse tree.
80
     *
81
     * @throws Mustache_Exception_SyntaxException upon encountering unknown token types
82
     *
83
     * @param array $tree  Parse tree of Mustache tokens
84
     * @param int   $level (default: 0)
85
     *
86
     * @return string Generated PHP source code
87
     */
88
    private function walk(array $tree, $level = 0)
89
    {
90
        $code = '';
91
        $level++;
92
        foreach ($tree as $node) {
93
            switch ($node[Mustache_Tokenizer::TYPE]) {
94
                case Mustache_Tokenizer::T_PRAGMA:
95
                    $this->pragmas[$node[Mustache_Tokenizer::NAME]] = true;
96
                    break;
97
 
98
                case Mustache_Tokenizer::T_SECTION:
99
                    $code .= $this->section(
100
                        $node[Mustache_Tokenizer::NODES],
101
                        $node[Mustache_Tokenizer::NAME],
102
                        isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
103
                        $node[Mustache_Tokenizer::INDEX],
104
                        $node[Mustache_Tokenizer::END],
105
                        $node[Mustache_Tokenizer::OTAG],
106
                        $node[Mustache_Tokenizer::CTAG],
107
                        $level
108
                    );
109
                    break;
110
 
111
                case Mustache_Tokenizer::T_INVERTED:
112
                    $code .= $this->invertedSection(
113
                        $node[Mustache_Tokenizer::NODES],
114
                        $node[Mustache_Tokenizer::NAME],
115
                        isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
116
                        $level
117
                    );
118
                    break;
119
 
120
                case Mustache_Tokenizer::T_PARTIAL:
121
                    $code .= $this->partial(
122
                        $node[Mustache_Tokenizer::NAME],
123
                        isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
124
                        $level
125
                    );
126
                    break;
127
 
128
                case Mustache_Tokenizer::T_PARENT:
129
                    $code .= $this->parent(
130
                        $node[Mustache_Tokenizer::NAME],
131
                        isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
132
                        $node[Mustache_Tokenizer::NODES],
133
                        $level
134
                    );
135
                    break;
136
 
137
                case Mustache_Tokenizer::T_BLOCK_ARG:
138
                    $code .= $this->blockArg(
139
                        $node[Mustache_Tokenizer::NODES],
140
                        $node[Mustache_Tokenizer::NAME],
141
                        $node[Mustache_Tokenizer::INDEX],
142
                        $node[Mustache_Tokenizer::END],
143
                        $node[Mustache_Tokenizer::OTAG],
144
                        $node[Mustache_Tokenizer::CTAG],
145
                        $level
146
                    );
147
                    break;
148
 
149
                case Mustache_Tokenizer::T_BLOCK_VAR:
150
                    $code .= $this->blockVar(
151
                        $node[Mustache_Tokenizer::NODES],
152
                        $node[Mustache_Tokenizer::NAME],
153
                        $node[Mustache_Tokenizer::INDEX],
154
                        $node[Mustache_Tokenizer::END],
155
                        $node[Mustache_Tokenizer::OTAG],
156
                        $node[Mustache_Tokenizer::CTAG],
157
                        $level
158
                    );
159
                    break;
160
 
161
                case Mustache_Tokenizer::T_COMMENT:
162
                    break;
163
 
164
                case Mustache_Tokenizer::T_ESCAPED:
165
                case Mustache_Tokenizer::T_UNESCAPED:
166
                case Mustache_Tokenizer::T_UNESCAPED_2:
167
                    $code .= $this->variable(
168
                        $node[Mustache_Tokenizer::NAME],
169
                        isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
170
                        $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED,
171
                        $level
172
                    );
173
                    break;
174
 
175
                case Mustache_Tokenizer::T_TEXT:
176
                    $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
177
                    break;
178
 
179
                default:
180
                    throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node);
181
            }
182
        }
183
 
184
        return $code;
185
    }
186
 
187
    const KLASS = '<?php
188
 
189
        class %s extends Mustache_Template
190
        {
191
            private $lambdaHelper;%s
192
 
193
            public function renderInternal(Mustache_Context $context, $indent = \'\')
194
            {
195
                $this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
196
                $buffer = \'\';
197
        %s
198
 
199
                return $buffer;
200
            }
201
        %s
202
        %s
203
        }';
204
 
205
    const KLASS_NO_LAMBDAS = '<?php
206
 
207
        class %s extends Mustache_Template
208
        {%s
209
            public function renderInternal(Mustache_Context $context, $indent = \'\')
210
            {
211
                $buffer = \'\';
212
        %s
213
 
214
                return $buffer;
215
            }
216
        }';
217
 
218
    const STRICT_CALLABLE = 'protected $strictCallables = true;';
219
 
220
    /**
221
     * Generate Mustache Template class PHP source.
222
     *
223
     * @param array  $tree Parse tree of Mustache tokens
224
     * @param string $name Mustache Template class name
225
     *
226
     * @return string Generated PHP source code
227
     */
228
    private function writeCode($tree, $name)
229
    {
230
        $code     = $this->walk($tree);
231
        $sections = implode("\n", $this->sections);
232
        $blocks   = implode("\n", $this->blocks);
233
        $klass    = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS;
234
 
235
        $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
236
 
237
        return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections, $blocks);
238
    }
239
 
240
    const BLOCK_VAR = '
241
        $blockFunction = $context->findInBlock(%s);
242
        if (is_callable($blockFunction)) {
243
            $buffer .= call_user_func($blockFunction, $context);
244
        %s}
245
    ';
246
 
247
    const BLOCK_VAR_ELSE = '} else {%s';
248
 
249
    /**
250
     * Generate Mustache Template inheritance block variable PHP source.
251
     *
252
     * @param array  $nodes Array of child tokens
253
     * @param string $id    Section name
254
     * @param int    $start Section start offset
255
     * @param int    $end   Section end offset
256
     * @param string $otag  Current Mustache opening tag
257
     * @param string $ctag  Current Mustache closing tag
258
     * @param int    $level
259
     *
260
     * @return string Generated PHP source code
261
     */
262
    private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level)
263
    {
264
        $id = var_export($id, true);
265
 
266
        $else = $this->walk($nodes, $level);
267
        if ($else !== '') {
268
            $else = sprintf($this->prepare(self::BLOCK_VAR_ELSE, $level + 1, false, true), $else);
269
        }
270
 
271
        return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $else);
272
    }
273
 
274
    const BLOCK_ARG = '%s => array($this, \'block%s\'),';
275
 
276
    /**
277
     * Generate Mustache Template inheritance block argument PHP source.
278
     *
279
     * @param array  $nodes Array of child tokens
280
     * @param string $id    Section name
281
     * @param int    $start Section start offset
282
     * @param int    $end   Section end offset
283
     * @param string $otag  Current Mustache opening tag
284
     * @param string $ctag  Current Mustache closing tag
285
     * @param int    $level
286
     *
287
     * @return string Generated PHP source code
288
     */
289
    private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level)
290
    {
291
        $key = $this->block($nodes);
292
        $id = var_export($id, true);
293
 
294
        return sprintf($this->prepare(self::BLOCK_ARG, $level), $id, $key);
295
    }
296
 
297
    const BLOCK_FUNCTION = '
298
        public function block%s($context)
299
        {
300
            $indent = $buffer = \'\';%s
301
 
302
            return $buffer;
303
        }
304
    ';
305
 
306
    /**
307
     * Generate Mustache Template inheritance block function PHP source.
308
     *
309
     * @param array $nodes Array of child tokens
310
     *
311
     * @return string key of new block function
312
     */
313
    private function block($nodes)
314
    {
315
        $code = $this->walk($nodes, 0);
316
        $key = ucfirst(md5($code));
317
 
318
        if (!isset($this->blocks[$key])) {
319
            $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code);
320
        }
321
 
322
        return $key;
323
    }
324
 
325
    const SECTION_CALL = '
326
        $value = $context->%s(%s);%s
327
        $buffer .= $this->section%s($context, $indent, $value);
328
    ';
329
 
330
    const SECTION = '
331
        private function section%s(Mustache_Context $context, $indent, $value)
332
        {
333
            $buffer = \'\';
334
 
335
            if (%s) {
336
                $source = %s;
337
                $result = (string) call_user_func($value, $source, %s);%s
338
                $buffer .= $result;
339
            } elseif (!empty($value)) {
340
                $values = $this->isIterable($value) ? $value : array($value);
341
                foreach ($values as $value) {
342
                    $context->push($value);
343
                    %s
344
                    $context->pop();
345
                }
346
            }
347
 
348
            return $buffer;
349
        }
350
    ';
351
 
352
    const SECTION_RENDER_LAMBDA = '
353
        if (strpos($result, \'{{\') !== false) {
354
            $result = $this->mustache
355
                ->loadLambda($result%s)
356
                ->renderInternal($context);
357
        }
358
    ';
359
 
360
    /**
361
     * Helper function to compile section with and without lambda rendering.
362
     *
363
     * @param string $key
364
     * @param string $callable
365
     * @param string $source
366
     * @param string $helper
367
     * @param string $delims
368
     * @param string $content
369
     *
370
     * @return string section code
371
     */
372
    private function getSection($key, $callable, $source, $helper, $delims, $content)
373
    {
374
        $render = '';
375
        if (!$this->disableLambdaRendering) {
376
            $render = sprintf($this->prepare(self::SECTION_RENDER_LAMBDA, 2), $delims);
377
        }
378
 
379
        return sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $render, $content);
380
    }
381
 
382
    /**
383
     * Generate Mustache Template section PHP source.
384
     *
385
     * @param array    $nodes   Array of child tokens
386
     * @param string   $id      Section name
387
     * @param string[] $filters Array of filters
388
     * @param int      $start   Section start offset
389
     * @param int      $end     Section end offset
390
     * @param string   $otag    Current Mustache opening tag
391
     * @param string   $ctag    Current Mustache closing tag
392
     * @param int      $level
393
     *
394
     * @return string Generated section PHP source code
395
     */
396
    private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level)
397
    {
398
        $source   = var_export(substr($this->source, $start, $end - $start), true);
399
        $callable = $this->getCallable();
400
 
401
        if ($otag !== '{{' || $ctag !== '}}') {
402
            $delimTag = var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
403
            $helper = sprintf('$this->lambdaHelper->withDelimiters(%s)', $delimTag);
404
            $delims = ', ' . $delimTag;
405
        } else {
406
            $helper = '$this->lambdaHelper';
407
            $delims = '';
408
        }
409
 
410
        $key = ucfirst(md5($delims . "\n" . $source));
411
 
412
        if (!isset($this->sections[$key])) {
413
            $this->sections[$key] = $this->getSection($key, $callable, $source, $helper, $delims, $this->walk($nodes, 2));
414
        }
415
 
416
        $method  = $this->getFindMethod($id);
417
        $id      = var_export($id, true);
418
        $filters = $this->getFilters($filters, $level);
419
 
420
        return sprintf($this->prepare(self::SECTION_CALL, $level), $method, $id, $filters, $key);
421
    }
422
 
423
    const INVERTED_SECTION = '
424
        $value = $context->%s(%s);%s
425
        if (empty($value)) {
426
            %s
427
        }
428
    ';
429
 
430
    /**
431
     * Generate Mustache Template inverted section PHP source.
432
     *
433
     * @param array    $nodes   Array of child tokens
434
     * @param string   $id      Section name
435
     * @param string[] $filters Array of filters
436
     * @param int      $level
437
     *
438
     * @return string Generated inverted section PHP source code
439
     */
440
    private function invertedSection($nodes, $id, $filters, $level)
441
    {
442
        $method  = $this->getFindMethod($id);
443
        $id      = var_export($id, true);
444
        $filters = $this->getFilters($filters, $level);
445
 
446
        return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $filters, $this->walk($nodes, $level));
447
    }
448
 
449
    const PARTIAL_INDENT = ', $indent . %s';
450
    const PARTIAL = '
451
        if ($partial = $this->mustache->loadPartial(%s)) {
452
            $buffer .= $partial->renderInternal($context%s);
453
        }
454
    ';
455
 
456
    /**
457
     * Generate Mustache Template partial call PHP source.
458
     *
459
     * @param string $id     Partial name
460
     * @param string $indent Whitespace indent to apply to partial
461
     * @param int    $level
462
     *
463
     * @return string Generated partial call PHP source code
464
     */
465
    private function partial($id, $indent, $level)
466
    {
467
        if ($indent !== '') {
468
            $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
469
        } else {
470
            $indentParam = '';
471
        }
472
 
473
        return sprintf(
474
            $this->prepare(self::PARTIAL, $level),
475
            var_export($id, true),
476
            $indentParam
477
        );
478
    }
479
 
480
    const PARENT = '
481
        if ($parent = $this->mustache->loadPartial(%s)) {
482
            $context->pushBlockContext(array(%s
483
            ));
484
            $buffer .= $parent->renderInternal($context, $indent);
485
            $context->popBlockContext();
486
        }
487
    ';
488
 
489
    const PARENT_NO_CONTEXT = '
490
        if ($parent = $this->mustache->loadPartial(%s)) {
491
            $buffer .= $parent->renderInternal($context, $indent);
492
        }
493
    ';
494
 
495
    /**
496
     * Generate Mustache Template inheritance parent call PHP source.
497
     *
498
     * @param string $id       Parent tag name
499
     * @param string $indent   Whitespace indent to apply to parent
500
     * @param array  $children Child nodes
501
     * @param int    $level
502
     *
503
     * @return string Generated PHP source code
504
     */
505
    private function parent($id, $indent, array $children, $level)
506
    {
507
        $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs'));
508
 
509
        if (empty($realChildren)) {
510
            return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), var_export($id, true));
511
        }
512
 
513
        return sprintf(
514
            $this->prepare(self::PARENT, $level),
515
            var_export($id, true),
516
            $this->walk($realChildren, $level + 1)
517
        );
518
    }
519
 
520
    /**
521
     * Helper method for filtering out non-block-arg tokens.
522
     *
523
     * @param array $node
524
     *
525
     * @return bool True if $node is a block arg token
526
     */
527
    private static function onlyBlockArgs(array $node)
528
    {
529
        return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG;
530
    }
531
 
532
    const VARIABLE = '
533
        $value = $this->resolveValue($context->%s(%s), $context);%s
534
        $buffer .= %s($value === null ? \'\' : %s);
535
    ';
536
 
537
    /**
538
     * Generate Mustache Template variable interpolation PHP source.
539
     *
540
     * @param string   $id      Variable name
541
     * @param string[] $filters Array of filters
542
     * @param bool     $escape  Escape the variable value for output?
543
     * @param int      $level
544
     *
545
     * @return string Generated variable interpolation PHP source
546
     */
547
    private function variable($id, $filters, $escape, $level)
548
    {
549
        $method  = $this->getFindMethod($id);
550
        $id      = ($method !== 'last') ? var_export($id, true) : '';
551
        $filters = $this->getFilters($filters, $level);
552
        $value   = $escape ? $this->getEscape() : '$value';
553
 
554
        return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
555
    }
556
 
557
    const FILTER = '
558
        $filter = $context->%s(%s);
559
        if (!(%s)) {
560
            throw new Mustache_Exception_UnknownFilterException(%s);
561
        }
562
        $value = call_user_func($filter, $value);%s
563
    ';
564
 
565
    /**
566
     * Generate Mustache Template variable filtering PHP source.
567
     *
568
     * @param string[] $filters Array of filters
569
     * @param int      $level
570
     *
571
     * @return string Generated filter PHP source
572
     */
573
    private function getFilters(array $filters, $level)
574
    {
575
        if (empty($filters)) {
576
            return '';
577
        }
578
 
579
        $name     = array_shift($filters);
580
        $method   = $this->getFindMethod($name);
581
        $filter   = ($method !== 'last') ? var_export($name, true) : '';
582
        $callable = $this->getCallable('$filter');
583
        $msg      = var_export($name, true);
584
 
585
        return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level));
586
    }
587
 
588
    const LINE = '$buffer .= "\n";';
589
    const TEXT = '$buffer .= %s%s;';
590
 
591
    /**
592
     * Generate Mustache Template output Buffer call PHP source.
593
     *
594
     * @param string $text
595
     * @param int    $level
596
     *
597
     * @return string Generated output Buffer call PHP source
598
     */
599
    private function text($text, $level)
600
    {
601
        $indentNextLine = (substr($text, -1) === "\n");
602
        $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
603
        $this->indentNextLine = $indentNextLine;
604
 
605
        return $code;
606
    }
607
 
608
    /**
609
     * Prepare PHP source code snippet for output.
610
     *
611
     * @param string $text
612
     * @param int    $bonus          Additional indent level (default: 0)
613
     * @param bool   $prependNewline Prepend a newline to the snippet? (default: true)
614
     * @param bool   $appendNewline  Append a newline to the snippet? (default: false)
615
     *
616
     * @return string PHP source code snippet
617
     */
618
    private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false)
619
    {
620
        $text = ($prependNewline ? "\n" : '') . trim($text);
621
        if ($prependNewline) {
622
            $bonus++;
623
        }
624
        if ($appendNewline) {
625
            $text .= "\n";
626
        }
627
 
628
        return preg_replace("/\n( {8})?/", "\n" . str_repeat(' ', $bonus * 4), $text);
629
    }
630
 
631
    const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)';
632
    const CUSTOM_ESCAPE  = 'call_user_func($this->mustache->getEscape(), %s)';
633
 
634
    /**
635
     * Get the current escaper.
636
     *
637
     * @param string $value (default: '$value')
638
     *
639
     * @return string Either a custom callback, or an inline call to `htmlspecialchars`
640
     */
641
    private function getEscape($value = '$value')
642
    {
643
        if ($this->customEscape) {
644
            return sprintf(self::CUSTOM_ESCAPE, $value);
645
        }
646
 
647
        return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
648
    }
649
 
650
    /**
651
     * Select the appropriate Context `find` method for a given $id.
652
     *
653
     * The return value will be one of `find`, `findDot`, `findAnchoredDot` or `last`.
654
     *
655
     * @see Mustache_Context::find
656
     * @see Mustache_Context::findDot
657
     * @see Mustache_Context::last
658
     *
659
     * @param string $id Variable name
660
     *
661
     * @return string `find` method name
662
     */
663
    private function getFindMethod($id)
664
    {
665
        if ($id === '.') {
666
            return 'last';
667
        }
668
 
669
        if (isset($this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) {
670
            if (substr($id, 0, 1) === '.') {
671
                return 'findAnchoredDot';
672
            }
673
        }
674
 
675
        if (strpos($id, '.') === false) {
676
            return 'find';
677
        }
678
 
679
        return 'findDot';
680
    }
681
 
682
    const IS_CALLABLE        = '!is_string(%s) && is_callable(%s)';
683
    const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
684
 
685
    /**
686
     * Helper function to compile strict vs lax "is callable" logic.
687
     *
688
     * @param string $variable (default: '$value')
689
     *
690
     * @return string "is callable" logic
691
     */
692
    private function getCallable($variable = '$value')
693
    {
694
        $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;
695
 
696
        return sprintf($tpl, $variable, $variable);
697
    }
698
 
699
    const LINE_INDENT = '$indent . ';
700
 
701
    /**
702
     * Get the current $indent prefix to write to the buffer.
703
     *
704
     * @return string "$indent . " or ""
705
     */
706
    private function flushIndent()
707
    {
708
        if (!$this->indentNextLine) {
709
            return '';
710
        }
711
 
712
        $this->indentNextLine = false;
713
 
714
        return self::LINE_INDENT;
715
    }
716
}