Completed
Pull Request — master (#11)
by Todd
09:12
created

Compiler::compileOpenTag()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 16
cts 16
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 18
nc 9
nop 4
crap 7
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2017 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Ebi;
9
10
use DOMAttr;
11
use DOMElement;
12
use DOMNode;
13
14
class Compiler {
15
    const T_IF = 'x-if';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
16
    const T_EACH = 'x-each';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
17
    const T_WITH = 'x-with';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
18
    const T_LITERAL = 'x-literal';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
19
    const T_AS = 'x-as';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
20
    const T_COMPONENT = 'x-component';
21
    const T_CHILDREN = 'x-children';
22
    const T_BLOCK = 'x-block';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
23
    const T_ELSE = 'x-else';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
24
    const T_EMPTY = 'x-empty';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
25
    const T_X = 'x';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
26
    const T_INCLUDE = 'x-include';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
27
28
    protected static $special = [
29
        self::T_COMPONENT => 1,
30
        self::T_IF => 2,
31
        self::T_ELSE => 3,
32
        self::T_EACH => 4,
33
        self::T_EMPTY => 5,
34
        self::T_CHILDREN => 6,
35
        self::T_INCLUDE => 7,
36
        self::T_WITH => 8,
37
        self::T_BLOCK => 9,
38
        self::T_LITERAL => 10,
39
        self::T_AS => 11,
40
    ];
41
42
    protected static $htmlTags = [
43
        'a' => 'i',
44
        'abbr' => 'i',
45
        'acronym' => 'i', // deprecated
46
        'address' => 'b',
47
//        'applet' => 'i', // deprecated
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
48
        'area' => 'i',
49
        'article' => 'b',
50
        'aside' => 'b',
51
        'audio' => 'i',
52
        'b' => 'i',
53
        'base' => 'i',
54
//        'basefont' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
55
        'bdi' => 'i',
56
        'bdo' => 'i',
57
//        'bgsound' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
58
//        'big' => 'i',
59
        'x' => 'i',
60
//        'blink' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
61
        'blockquote' => 'b',
62
        'body' => 'b',
63
        'br' => 'i',
64
        'button' => 'i',
65
        'canvas' => 'b',
66
        'caption' => 'i',
67
//        'center' => 'b',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
68
        'cite' => 'i',
69
        'code' => 'i',
70
        'col' => 'i',
71
        'colgroup' => 'i',
72
//        'command' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
73
        'content' => 'i',
74
        'data' => 'i',
75
        'datalist' => 'i',
76
        'dd' => 'b',
77
        'del' => 'i',
78
        'details' => 'i',
79
        'dfn' => 'i',
80
        'dialog' => 'i',
81
//        'dir' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
82
        'div' => 'i',
83
        'dl' => 'b',
84
        'dt' => 'b',
85
//        'element' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
86
        'em' => 'i',
87
        'embed' => 'i',
88
        'fieldset' => 'b',
89
        'figcaption' => 'b',
90
        'figure' => 'b',
91
//        'font' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
92
        'footer' => 'b',
93
        'form' => 'b',
94
        'frame' => 'i',
95
        'frameset' => 'i',
96
        'h1' => 'b',
97
        'h2' => 'b',
98
        'h3' => 'b',
99
        'h4' => 'b',
100
        'h5' => 'b',
101
        'h6' => 'b',
102
        'head' => 'b',
103
        'header' => 'b',
104
        'hgroup' => 'b',
105
        'hr' => 'b',
106
        'html' => 'b',
107
        'i' => 'i',
108
        'iframe' => 'i',
109
        'image' => 'i',
110
        'img' => 'i',
111
        'input' => 'i',
112
        'ins' => 'i',
113
        'isindex' => 'i',
114
        'kbd' => 'i',
115
        'keygen' => 'i',
116
        'label' => 'i',
117
        'legend' => 'i',
118
        'li' => 'i',
119
        'link' => 'i',
120
//        'listing' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
121
        'main' => 'b',
122
        'map' => 'i',
123
        'mark' => 'i',
124
//        'marquee' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
125
        'menu' => 'i',
126
        'menuitem' => 'i',
127
        'meta' => 'i',
128
        'meter' => 'i',
129
        'multicol' => 'i',
130
        'nav' => 'b',
131
        'nobr' => 'i',
132
        'noembed' => 'i',
133
        'noframes' => 'i',
134
        'noscript' => 'b',
135
        'object' => 'i',
136
        'ol' => 'b',
137
        'optgroup' => 'i',
138
        'option' => 'b',
139
        'output' => 'i',
140
        'p' => 'b',
141
        'param' => 'i',
142
        'picture' => 'i',
143
//        'plaintext' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
144
        'pre' => 'b',
145
        'progress' => 'i',
146
        'q' => 'i',
147
        'rp' => 'i',
148
        'rt' => 'i',
149
        'rtc' => 'i',
150
        'ruby' => 'i',
151
        's' => 'i',
152
        'samp' => 'i',
153
        'script' => 'i',
154
        'section' => 'b',
155
        'select' => 'i',
156
//        'shadow' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
157
        'slot' => 'i',
158
        'small' => 'i',
159
        'source' => 'i',
160
//        'spacer' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
161
        'span' => 'i',
162
//        'strike' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
163
        'strong' => 'i',
164
        'style' => 'i',
165
        'sub' => 'i',
166
        'summary' => 'i',
167
        'sup' => 'i',
168
        'table' => 'b',
169
        'tbody' => 'i',
170
        'td' => 'i',
171
        'template' => 'i',
172
        'textarea' => 'i',
173
        'tfoot' => 'b',
174
        'th' => 'i',
175
        'thead' => 'i',
176
        'time' => 'i',
177
        'title' => 'i',
178
        'tr' => 'i',
179
        'track' => 'i',
180
//        'tt' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
181
        'u' => 'i',
182
        'ul' => 'b',
183
        'var' => 'i',
184
        'video' => 'b',
185
        'wbr' => 'i',
186
187
        /// SVG ///
188
        'animate' => 's',
189
        'animateColor' => 's',
190
        'animateMotion' => 's',
191
        'animateTransform' => 's',
192
//        'canvas' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
193
        'circle' => 's',
194
        'desc' => 's',
195
        'defs' => 's',
196
        'discard' => 's',
197
        'ellipse' => 's',
198
        'g' => 's',
199
//        'image' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
200
        'line' => 's',
201
        'marker' => 's',
202
        'mask' => 's',
203
        'missing-glyph' => 's',
204
        'mpath' => 's',
205
        'metadata' => 's',
206
        'path' => 's',
207
        'pattern' => 's',
208
        'polygon' => 's',
209
        'polyline' => 's',
210
        'rect' => 's',
211
        'set' => 's',
212
        'svg' => 's',
213
        'switch' => 's',
214
        'symbol' => 's',
215
        'text' => 's',
216
//        'unknown' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
217
        'use' => 's',
218
    ];
219
220
    /**
221
     * @var ExpressionLanguage
222
     */
223
    protected $expressions;
224
225 44
    public function __construct() {
226 44
        $this->expressions = new ExpressionLanguage();
227 44
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
228 44
    }
229
230
    /**
231
     * Register a runtime function.
232
     *
233
     * @param string $name The name of the function.
234
     * @param callable $function The function callback.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $function not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
235
     */
236 42
    public function defineFunction($name, $function = null) {
237 42
        if ($function === null) {
238 1
            $function = $name;
239
        }
240
241 42
        $this->expressions->register(
242 42
            $name,
243 42
            $this->getFunctionCompiler($name, $function),
244 42
            $this->getFunctionEvaluator($function)
245
        );
246 42
    }
247
248 42
    private function getFunctionEvaluator($function) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
249 42
        if ($function === 'empty') {
250 42
            return function ($expr) {
251
                return empty($expr);
252 42
            };
253 41
        } elseif ($function === 'isset') {
254
            return function ($expr) {
255
                return isset($expr);
256
            };
257
        }
258
259 41
        return $function;
260
    }
261
262 42
    private function getFunctionCompiler($name, $function) {
263 42
        $var = var_export(strtolower($name), true);
264 42
        $fn = function ($expr) use ($var) {
265
            return "\$this->call($var, $expr)";
266 42
        };
267
268 42
        if (is_string($function)) {
269 42
            $fn = function ($expr) use ($function) {
270 7
                return "$function($expr)";
271 42
            };
272 41
        } elseif (is_array($function)) {
273 41
            if (is_string($function[0])) {
274
                $fn = function ($expr) use ($function) {
275
                    return "$function[0]::$function[1]($expr)";
276
                };
277 41
            } elseif ($function[0] instanceof Ebi) {
278 41
                $fn = function ($expr) use ($function) {
279 5
                    return "\$this->$function[1]($expr)";
280 41
                };
281
            }
282
        }
283
284 42
        return $fn;
285
    }
286
287 38
    public function compile($src, array $options = []) {
288 38
        $options += ['basename' => '', 'runtime' => true];
289
290 38
        $src = trim($src);
291
292 38
        $dom = new \DOMDocument();
293 38
        libxml_use_internal_errors(true);
294
295 38
        $fragment = false;
296 38
        if (strpos($src, '<html') === false) {
297 37
            $src = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><html><body>$src</body></html>";
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
298 37
            $fragment = true;
299
        }
300
301 38
        $dom->loadHTML($src, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOCDATA | LIBXML_NOXMLDECL);
302
//        $arr = $this->domToArray($dom);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
303
304 38
        $out = new CompilerBuffer();
305
306 38
        $out->setBasename($options['basename']);
307
308 38
        if ($options['runtime']) {
309 37
            $name = var_export($options['basename'], true);
310 37
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
311
        } else {
312 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
313
        }
314
315 38
        $out->pushScope(['this' => 'props']);
316 38
        $out->indent(+1);
317
318 38
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
319
320 38
        foreach ($parent->childNodes as $node) {
321 38
            $this->compileNode($node, $out);
322
        }
323
324 38
        $out->indent(-1);
325 38
        $out->popScope();
326
327 38
        if ($options['runtime']) {
328 37
            $out->appendCode("});");
329
        } else {
330 1
            $out->appendCode("};");
331
        }
332
333 38
        $r = $out->flush();
334 38
        return $r;
335
    }
336
337 33
    protected function isComponent($tag) {
338 33
        return !isset(static::$htmlTags[$tag]);
339
    }
340
341 38
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
342 38
        if ($out->getNodeProp($node, 'skip')) {
343 4
            return;
344
        }
345
346 38
        switch ($node->nodeType) {
347 38
            case XML_TEXT_NODE:
348 36
                $this->compileTextNode($node, $out);
349 36
                break;
350 35
            case XML_ELEMENT_NODE:
351
                /* @var \DOMElement $node */
352 35
                $this->compileElementNode($node, $out);
353 35
                break;
354 2
            case XML_COMMENT_NODE:
355
                /* @var \DOMComment $node */
356 1
                $this->compileCommentNode($node, $out);
357 1
                break;
358 1
            case XML_DOCUMENT_TYPE_NODE:
359 1
                $out->echoLiteral("<!DOCTYPE {$node->name}>\n");
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in DOMNode.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
360 1
                break;
361
            default:
362
                $r = "// Unknown node\n".
0 ignored issues
show
Unused Code introduced by
$r is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
363
                    '// '.str_replace("\n", "\n// ", $node->ownerDocument->saveHTML($node));
364
        }
365 38
    }
366
367
    protected function domToArray(DOMNode $root) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
368
        $result = [];
369
370
        if ($root->hasAttributes()) {
371
            $attrs = $root->attributes;
372
            foreach ($attrs as $attr) {
373
                $result['@attributes'][$attr->name] = $attr->value;
374
            }
375
        }
376
377
        if ($root->hasChildNodes()) {
378
            $children = $root->childNodes;
379
            if ($children->length == 1) {
380
                $child = $children->item(0);
381
                if ($child->nodeType == XML_TEXT_NODE) {
382
                    $result['_value'] = $child->nodeValue;
383
                    return count($result) == 1
384
                        ? $result['_value']
385
                        : $result;
386
                }
387
            }
388
            $groups = [];
389
            foreach ($children as $child) {
390
                if (!isset($result[$child->nodeName])) {
391
                    $result[$child->nodeName] = $this->domToArray($child);
392
                } else {
393
                    if (!isset($groups[$child->nodeName])) {
394
                        $result[$child->nodeName] = [$result[$child->nodeName]];
395
                        $groups[$child->nodeName] = 1;
396
                    }
397
                    $result[$child->nodeName][] = $this->domToArray($child);
398
                }
399
            }
400
        }
401
402
        return $result;
403
    }
404
405 1
    protected function newline(DOMNode $node, CompilerBuffer $out) {
406 1
        if ($node->previousSibling && $node->previousSibling->nodeType !== XML_COMMENT_NODE) {
407
            $out->appendCode("\n");
408
        }
409 1
    }
410
411 1
    protected function compileCommentNode(\DOMComment $node, CompilerBuffer $out) {
412 1
        $comments = explode("\n", trim($node->nodeValue));
413
414 1
        $this->newline($node, $out);
415 1
        foreach ($comments as $comment) {
416 1
            $out->appendCode("// $comment\n");
417
        }
418 1
    }
419
420 36
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
421 36
        $text = $this->ltrim($this->rtrim($node->nodeValue, $node, $out), $node, $out);
422
423 36
        $items = $this->splitExpressions($text);
424
425 36
        foreach ($items as $i => list($text, $offset)) {
426 36
            if (preg_match('`^{\S`', $text)) {
427 22
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
428 1
                    $out->echoCode($this->expr($m[1], $out));
429
                } else {
430 22
                    $out->echoCode('htmlspecialchars('.$this->expr(substr($text, 1, -1), $out).')');
431
                }
432
            } else {
433
//                if ($i === 0) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
434
//                    $text = $this->ltrim($text, $node, $out);
435
//                }
436
//                if ($i === count($items) - 1) {
437
//                    $text = $this->rtrim($text, $node, $out);
438
//                }
439
440 36
                $out->echoLiteral($text);
441
            }
442
        }
443 36
    }
444
445 35
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
446 35
        list($attributes, $special) = $this->splitAttributes($node);
447
448 35
        if (!empty($special) || $this->isComponent($node->tagName)) {
449 30
            $this->compileSpecialNode($node, $attributes, $special, $out);
450
        } else {
451 17
            $this->compileOpenTag($node, $node->attributes, $out);
452
453 17
            foreach ($node->childNodes as $childNode) {
454 17
                $this->compileNode($childNode, $out);
455
            }
456
457 17
            $this->compileCloseTag($node, $out);
458
        }
459 35
    }
460
461 36
    protected function splitExpressions($value) {
462 36
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
463 36
        return $values;
464
    }
465
466 34
    protected function expr($expr, CompilerBuffer $output, DOMAttr $attr = null) {
467 34
        $names = $output->getScopeVariables();
468
469 34
        $compiled = $this->expressions->compile($expr, function ($name) use ($names) {
0 ignored issues
show
Documentation introduced by
function ($name) use($na...e, true) . ']'; } } is of type object<Closure>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
470 34
            if (isset($names[$name])) {
471 16
                return $names[$name];
472 29
            } elseif ($name[0] === '@') {
473 1
                return 'this->meta['.var_export(substr($name, 1), true).']';
474
            } else {
475 28
                return $names['this'].'['.var_export($name, true).']';
476
            }
477 34
        });
478
479 34
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
480 4
            $compiled = call_user_func($fn, $compiled);
481
        }
482
483 34
        return $compiled;
484
    }
485
486
    /**
487
     * Get the compiler function to wrap an attribute.
488
     *
489
     * Attribute functions are regular expression functions, but with a special naming convention. The following naming
490
     * conventions are supported:
491
     *
492
     * - **@tag:attribute**: Applies to an attribute only on a specific tag.
493
     * - **@attribute**: Applies to all attributes with a given name.
494
     *
495
     * @param DOMAttr $attr The attribute to look at.
496
     * @return callable|null A function or **null** if the attribute doesn't have a function.
497
     */
498 10
    private function getAttributeFunction(DOMAttr $attr) {
499 10
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
500
501 10
        foreach ($keys as $key) {
502 10
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
503 10
                return $fn;
504
            }
505
        }
506 6
    }
507
508
    /**
509
     * @param DOMElement $node
510
     */
511 35
    protected function splitAttributes(DOMElement $node) {
512 35
        $attributes = [];
513 35
        $special = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
514
515 35
        foreach ($node->attributes as $name => $attribute) {
516 34
            if (isset(static::$special[$name])) {
517 29
                $special[$name] = $attribute;
518
            } else {
519 34
                $attributes[$name] = $attribute;
520
            }
521
        }
522
523 35
        uksort($special, function ($a, $b) {
524 7
            return strnatcmp(static::$special[$a], static::$special[$b]);
525 35
        });
526
527 35
        return [$attributes, $special];
528
    }
529
530 30
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
531 30
        $specialName = key($special);
532
533
        switch ($specialName) {
534 30
            case self::T_COMPONENT:
535 8
                $this->compileComponentRegister($node, $attributes, $special, $out);
536 8
                break;
537 30
            case self::T_IF:
538 7
                $this->compileIf($node, $attributes, $special, $out);
539 7
                break;
540 30
            case self::T_EACH:
541 12
                $this->compileEach($node, $attributes, $special, $out);
542 12
                break;
543 21
            case self::T_BLOCK:
544 1
                $this->compileBlock($node, $attributes, $special, $out);
545 1
                break;
546 21
            case self::T_CHILDREN:
547 2
                $this->compileChildBlock($node, $attributes, $special, $out);
548 2
                break;
549 21
            case self::T_INCLUDE:
550 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
551 1
                break;
552 21
            case self::T_WITH:
553 3
                if ($this->isComponent($node->tagName)) {
554
                    // With has a special meaning in components.
555 2
                    $this->compileComponentInclude($node, $attributes, $special, $out);
556
                } else {
557 1
                    $this->compileWith($node, $attributes, $special, $out);
558
                }
559 3
                break;
560 21
            case self::T_LITERAL:
561 2
                $this->compileLiteral($node, $attributes, $special, $out);
562 2
                break;
563 19
            case '':
564 19
                if ($this->isComponent($node->tagName)) {
565 6
                    $this->compileComponentInclude($node, $attributes, $special, $out);
566
                } else {
567 18
                    $this->compileElement($node, $attributes, $out);
568
                }
569 19
                break;
570
        }
571 30
    }
572
573
    /**
574
     * Compile component registering.
575
     *
576
     * @param DOMElement $node
577
     * @param $attributes
578
     * @param $special
579
     * @param CompilerBuffer $out
580
     */
581 8
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
582 8
        $name = strtolower($special[self::T_COMPONENT]->value);
583 8
        unset($special[self::T_COMPONENT]);
584
585 8
        $prev = $out->select($name);
586
587 8
        $varName = var_export($name, true);
588 8
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
589 8
        $out->pushScope(['this' => 'props']);
590 8
        $out->indent(+1);
591
592
        try {
593 8
            $this->compileSpecialNode($node, $attributes, $special, $out);
594 8
        } finally {
595 8
            $out->popScope();
596 8
            $out->indent(-1);
597 8
            $out->appendCode("});");
598 8
            $out->select($prev);
599
        }
600 8
    }
601
602 1
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
603 1
        $name = strtolower($special[self::T_BLOCK]->value);
604 1
        unset($special[self::T_BLOCK]);
605
606 1
        $prev = $out->select($name);
607
608 1
        $use = '$'.implode(', $', $out->getScopeVariables()).', $children';
609
610 1
        $out->appendCode("function () use ($use) {\n");
611 1
        $out->pushScope(['this' => 'props']);
612 1
        $out->indent(+1);
613
614
        try {
615 1
            $this->compileSpecialNode($node, $attributes, $special, $out);
616 1
        } finally {
617 1
            $out->indent(-1);
618 1
            $out->popScope();
619 1
            $out->appendCode("}");
620 1
            $out->select($prev);
621
        }
622
623 1
        return $out;
624
    }
625
626
    /**
627
     * Compile component inclusion and rendering.
628
     *
629
     * @param DOMElement $node
630
     * @param DOMAttr[] $attributes
631
     * @param DOMAttr[] $special
632
     * @param CompilerBuffer $out
633
     */
634 9
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
635
        // Generate the attributes into a property array.
636 9
        $props = [];
637 9
        foreach ($attributes as $name => $attribute) {
638
            /* @var DOMAttr $attr */
639 5
            if ($this->isExpression($attribute->value)) {
640 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
641
            } else {
642 1
                $expr = var_export($attribute->value, true);
643
            }
644
645 5
            $props[] = var_export($name, true).' => '.$expr;
646
        }
647 9
        $propsStr = '['.implode(', ', $props).']';
648
649 9
        if (isset($special[self::T_WITH])) {
650 2
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
651 2
            unset($special[self::T_WITH]);
652
653 2
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
654
        } elseif (empty($props)) {
655
            // By default the current context is passed to components.
656 3
            $propsStr = $this->expr('this', $out);
657
        }
658
659
        // Compile the children blocks.
660 9
        $blocks = $this->compileComponentBlocks($node, $out);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
661 9
        $blocksStr = $blocks->flush();
662
663 9
        if (isset($special[self::T_INCLUDE])) {
664 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
665
        } else {
666 8
            $name = var_export($node->tagName, true);
667
        }
668
669 9
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
670 9
    }
671
672
    /**
673
     * @param DOMElement $parent
674
     * @return CompilerBuffer
675
     */
676 9
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
677 9
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
678 9
            'baseIndent' => $out->getIndent(),
679 9
            'indent' => $out->getIndent() + 1,
680 9
            'depth' => $out->getDepth(),
681 9
            'scopes' => $out->getAllScopes()
682
        ]);
683
684 9
        if ($this->isEmptyNode($parent)) {
685 7
            return $blocksOut;
686
        }
687
688 2
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
689
690 2
        $blocksOut->appendCode("function () use ($use) {\n");
691 2
        $blocksOut->indent(+1);
692
693
        try {
694 2
            foreach ($parent->childNodes as $node) {
695 2
                $this->compileNode($node, $blocksOut);
696
            }
697 2
        } finally {
698 2
            $blocksOut->indent(-1);
699 2
            $blocksOut->appendCode("}");
700
        }
701
702 2
        return $blocksOut;
703
    }
704
705 22
    protected function compileTagComment(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
706
        // Don't double up comments.
707 22
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
708
            return;
709
        }
710
711 22
        $str = '<'.$node->tagName;
712 22
        foreach ($special as $attr) {
713
            /* @var DOMAttr $attr */
714 22
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.htmlspecialchars($attr->value).'"');
715
        }
716 22
        $str .= '>';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
717 22
        $comments = explode("\n", $str);
718 22
        foreach ($comments as $comment) {
719 22
            $out->appendCode("// $comment\n");
720
        }
721 22
    }
722
723 35
    protected function compileOpenTag(DOMElement $node, $attributes, CompilerBuffer $out, $force = false) {
724 35
        if ($node->tagName === self::T_X) {
725 3
            return;
726
        }
727
728 34
        $out->echoLiteral('<'.$node->tagName);
729
730 34
        foreach ($attributes as $name => $attribute) {
731
            /* @var DOMAttr $attribute */
732 8
            $out->echoLiteral(' '.$name.'="');
733
734
            // Check for an attribute expression.
735 8
            if ($this->isExpression($attribute->value)) {
736 6
                $out->echoCode('htmlspecialchars('.$this->expr(substr($attribute->value, 1, -1), $out, $attribute).')');
737
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
738 2
                $value  = call_user_func($fn, var_export($attribute->value, true));
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
739
740
                $out->echoCode("htmlspecialchars($value)");
741 8
742
            } else {
743
                $out->echoLiteral(htmlspecialchars($attribute->value));
744 34
            }
745 34
746
            $out->echoLiteral('"');
747 1
        }
748
749 34
        if ($node->hasChildNodes() || $force) {
750
            $out->echoLiteral('>');
751 11
        } else {
752 11
            $out->echoLiteral(" />");
753
        }
754
    }
755 35
756 35
    private function isExpression($value) {
757 34
        return preg_match('`^{\S.*}$`', $value);
758
    }
759 35
760
    protected function compileCloseTag(DOMElement $node, CompilerBuffer $out, $force = false) {
761 3
        if (($force || $node->hasChildNodes()) && $node->tagName !== self::T_X) {
762 3
            $out->echoLiteral("</{$node->tagName}>");
763
        }
764
    }
765 9
766 9
    protected function isEmptyText(DOMNode $node) {
767 7
        return $node instanceof \DOMText && empty(trim($node->data));
768
    }
769
770 2
    protected function isEmptyNode(DOMNode $node) {
771 2
        if (!$node->hasChildNodes()) {
772 1
            return true;
773
        }
774 1
775 1
        foreach ($node->childNodes as $childNode) {
776
            if ($childNode instanceof DOMElement) {
777
                return false;
778
            }
779
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
780
                return false;
781
            }
782 7
        }
783 7
784 7
        return true;
785 7
    }
786
787 7
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
788 7
        $this->compileTagComment($node, $attributes, $special, $out);
789
        $expr = $this->expr($special[self::T_IF]->value, $out);
790 7
        unset($special[self::T_IF]);
791 7
792
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
793 7
        $out->setNodeProp($elseNode, 'skip', true);
794
795 7
        $out->appendCode('if ('.$expr.") {\n");
796
        $out->indent(+1);
797 7
798 2
        $this->compileSpecialNode($node, $attributes, $special, $out);
799 2
800
        $out->indent(-1);
801 2
802
        if ($elseNode) {
803 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
804 2
            unset($special[self::T_ELSE]);
805 2
806
            $out->appendCode("} else {\n");
807
808 7
            $out->indent(+1);
809 7
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
810
            $out->indent(-1);
811 12
        }
812 12
813 12
        $out->appendCode("}\n");
814
    }
815 12
816 12
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
817
        $this->compileTagComment($node, $attributes, $special, $out);
818 12
        $this->compileOpenTag($node, $attributes, $out);
819 10
820
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
821 2
        $out->setNodeProp($emptyNode, 'skip', true);
822
823 2
        if ($emptyNode === null) {
824 2
            $this->compileEachLoop($node, $attributes, $special, $out);
825
        } else {
826 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
827
828 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
829 2
            unset($emptySpecial[self::T_EMPTY]);
830 2
831
            $out->appendCode('if ('.$expr.") {\n");
832 2
833
            $out->indent(+1);
834 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
835 2
            $out->indent(-1);
836 2
837
            $out->appendCode("} else {\n");
838 2
839
            $out->indent(+1);
840
            $this->compileEachLoop($node, $attributes, $special, $out);
841 12
            $out->indent(-1);
842 12
843
            $out->appendCode("}\n");
844 1
        }
845 1
846 1
        $this->compileCloseTag($node, $out);
847
    }
848 1
849 1
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
850 1
        $this->compileTagComment($node, $attributes, $special, $out);
851
        $with = $this->expr($special[self::T_WITH]->value, $out);
852 1
853
        $out->depth(+1);
854 1
        $scope = ['this' => $out->depthName('props')];
855
        if (!empty($special[self::T_AS]) && preg_match('`^([a-z0-9]+)$`', $special[self::T_AS]->value, $m)) {
856 1
            // The template specified an x-as attribute to alias the with expression.
857 1
            $scope = [$m[1] => $out->depthName('props')];
858
        }
859 1
        unset($special[self::T_WITH], $special[self::T_AS]);
860
861 1
        $out->pushScope($scope);
862 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
863 1
864
        $this->compileSpecialNode($node, $attributes, $special, $out);
865 2
866 2
        $out->depth(-1);
867 2
        $out->popScope();
868
    }
869 2
870
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
871 2
        $this->compileTagComment($node, $attributes, $special, $out);
872 2
        unset($special[self::T_LITERAL]);
873 2
874
        $this->compileOpenTag($node, $attributes, $out);
875
876 2
        foreach ($node->childNodes as $childNode) {
877 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
878
            $out->echoLiteral($html);
879 18
        }
880 18
881
        $this->compileCloseTag($node, $out);
882 18
    }
883 18
884
    protected function compileElement(DOMElement $node, array $attributes, CompilerBuffer $out) {
885
        $this->compileOpenTag($node, $attributes, $out);
886 18
887 18
        foreach ($node->childNodes as $childNode) {
888
            $this->compileNode($childNode, $out);
889
        }
890
891
        $this->compileCloseTag($node, $out);
892
    }
893
894
    /**
895
     * Find a special node in relation to another node.
896
     *
897
     * This method is used to find things such as x-empty and x-else elements.
898
     *
899 19
     * @param DOMElement $node The node to search in relation to.
900
     * @param string $attribute The name of the attribute to search for.
901 19
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
902 2
     * @return DOMElement|null Returns the found element node or **null** if not found.
903 2
     */
904
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
905
        // First look for a sibling after the node.
906
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
907 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
908
                return $sibNode;
909
            }
910
911
            // Stop searching if we encounter another node.
912
            if (!$this->isEmptyText($sibNode)) {
913 17
                break;
914 17
            }
915 17
        }
916 2
917
        // Next look inside the node.
918
        $parentFound = false;
919 17
        foreach ($node->childNodes as $childNode) {
920 12
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
921 7
                return $childNode;
922 17
            }
923
924
            if ($childNode instanceof DOMElement) {
925
                $parentFound = $childNode->hasAttribute($parentAttribute);
926 15
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
927
                $parentFound = false;
928
            }
929
        }
930
931
        return null;
932
    }
933
934
    /**
935 12
     * @param DOMElement $node
936 12
     * @param array $attributes
937 12
     * @param array $special
938
     * @param CompilerBuffer $out
939 12
     */
940 12
    private function compileEachLoop(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
941 12
        $each = $this->expr($special[self::T_EACH]->value, $out);
942 6
        unset($special[self::T_EACH]);
943 6
944 6
        $as = ['', $out->depthName('props', 1)];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
945 3
        $scope = ['this' => $as[1]];
946
        if (!empty($special[self::T_AS])) {
947
            if (preg_match('`(?:([a-z0-9]+)\s+)?([a-z0-9]+)`', $special[self::T_AS]->value, $m)) {
948
                $scope = [$m[2] => $as[1]];
949 12
                if (!empty($m[1])) {
950 12
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
951 9
                }
952
            }
953 3
        }
954
        unset($special[self::T_AS]);
955 12
        if (empty($as[0])) {
956 12
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
957 12
        } else {
958
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
959 12
        }
960 12
        $out->depth(+1);
961
        $out->indent(+1);
962
        $out->pushScope($scope);
963 12
964 12
        foreach ($node->childNodes as $childNode) {
965 12
            $this->compileNode($childNode, $out);
966 12
        }
967 12
968
        $out->indent(-1);
969 36
        $out->depth(-1);
970 36
        $out->popScope();
971
        $out->appendCode("}\n");
972
    }
973
974 36
    protected function ltrim($text, \DOMNode $node, CompilerBuffer $out) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
975 36
        if ($this->inPre($node)) {
976 6
            return $text;
977
        }
978
979 35
        $sib = $node->previousSibling ?: $node->parentNode;
980 35
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
981 35
            return ltrim($text);
982
        }
983
984
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
985
        if ($count === 0) {
986
            $text = preg_replace('`^\s+`', ' ', $text);
987 35
        }
988
989
//        if ($sib !== null && ($sib->nodeType === XML_COMMENT_NODE || in_array($sib->tagName, static::$blocks))) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
990 36
//            return ltrim($text);
991 36
//        }
992
        return $text;
993
    }
994
995 36
    protected function rtrim($text, \DOMNode $node, CompilerBuffer $out) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
996
        if ($this->inPre($node)) {
997 36
            return $text;
998 5
        }
999
1000
        $sib = $node->nextSibling ?: $node->parentNode;
1001 35
1002 35
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1003 35
            return rtrim($text);
1004
        }
1005
1006
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1007
        if ($count === 0) {
1008
            $text = preg_replace('`\s+$`', ' ', $text);
1009 35
        }
1010
1011
//        if ($sib !== null && ($sib->nodeType === XML_COMMENT_NODE || in_array($sib->tagName, static::$blocks))) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1012 36
//            return rtrim($text);
1013 36
//        }
1014 36
        return $text;
1015
    }
1016
1017
    protected function inPre(\DOMNode $node) {
1018 36
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1019
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1020
                return true;
1021 2
            }
1022
        }
1023 2
        return false;
1024 2
    }
1025
1026 2
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1027 2
        /* @var DOMAttr $child */
1028
        $child = $special[self::T_CHILDREN];
1029 2
        unset($special[self::T_CHILDREN]);
1030
1031 2
        $key = $child->value === '' ? 0 : $child->value;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1032 2
        $keyStr = var_export($key, true);
1033 2
1034 2
        $this->compileOpenTag($node, $attributes, $out, true);
1035 2
1036
        $out->appendCode("if (isset(\$children[{$keyStr}])) {\n");
1037 2
        $out->indent(+1);
1038 2
        $out->appendCode("\$children[{$keyStr}]();\n");
1039
        $out->indent(-1);
1040
        $out->appendCode("}\n");
1041
1042
        $this->compileCloseTag($node, $out, true);
1043
    }
1044
}
1045