Completed
Pull Request — master (#15)
by Todd
02:46
created

Compiler::compile()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 49
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 49
ccs 28
cts 28
cp 1
rs 8.5906
c 0
b 0
f 0
cc 6
eloc 30
nc 32
nop 2
crap 6
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
    const T_EXPR = 'x-expr';
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...
28
    const T_UNESCAPE = 'x-unescape';
29
30
    const IDENT_REGEX = '`^([a-z0-9-]+)$`i';
31
32
    protected static $special = [
33
        self::T_COMPONENT => 1,
34
        self::T_IF => 2,
35
        self::T_ELSE => 3,
36
        self::T_EACH => 4,
37
        self::T_EMPTY => 5,
38
        self::T_CHILDREN => 6,
39
        self::T_INCLUDE => 7,
40
        self::T_WITH => 8,
41
        self::T_BLOCK => 9,
42
        self::T_LITERAL => 10,
43
        self::T_AS => 11,
44
        self::T_UNESCAPE => 12
45
    ];
46
47
    protected static $htmlTags = [
48
        'a' => 'i',
49
        'abbr' => 'i',
50
        'acronym' => 'i', // deprecated
51
        'address' => 'b',
52
//        '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...
53
        'area' => 'i',
54
        'article' => 'b',
55
        'aside' => 'b',
56
        'audio' => 'i',
57
        'b' => 'i',
58
        'base' => 'i',
59
//        '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...
60
        'bdi' => 'i',
61
        'bdo' => 'i',
62
//        '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...
63
//        'big' => 'i',
64
        'x' => 'i',
65
//        '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...
66
        'blockquote' => 'b',
67
        'body' => 'b',
68
        'br' => 'i',
69
        'button' => 'i',
70
        'canvas' => 'b',
71
        'caption' => 'i',
72
//        '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...
73
        'cite' => 'i',
74
        'code' => 'i',
75
        'col' => 'i',
76
        'colgroup' => 'i',
77
//        '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...
78
        'content' => 'i',
79
        'data' => 'i',
80
        'datalist' => 'i',
81
        'dd' => 'b',
82
        'del' => 'i',
83
        'details' => 'i',
84
        'dfn' => 'i',
85
        'dialog' => 'i',
86
//        '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...
87
        'div' => 'i',
88
        'dl' => 'b',
89
        'dt' => 'b',
90
//        '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...
91
        'em' => 'i',
92
        'embed' => 'i',
93
        'fieldset' => 'b',
94
        'figcaption' => 'b',
95
        'figure' => 'b',
96
//        '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...
97
        'footer' => 'b',
98
        'form' => 'b',
99
        'frame' => 'i',
100
        'frameset' => 'i',
101
        'h1' => 'b',
102
        'h2' => 'b',
103
        'h3' => 'b',
104
        'h4' => 'b',
105
        'h5' => 'b',
106
        'h6' => 'b',
107
        'head' => 'b',
108
        'header' => 'b',
109
        'hgroup' => 'b',
110
        'hr' => 'b',
111
        'html' => 'b',
112
        'i' => 'i',
113
        'iframe' => 'i',
114
        'image' => 'i',
115
        'img' => 'i',
116
        'input' => 'i',
117
        'ins' => 'i',
118
        'isindex' => 'i',
119
        'kbd' => 'i',
120
        'keygen' => 'i',
121
        'label' => 'i',
122
        'legend' => 'i',
123
        'li' => 'i',
124
        'link' => 'i',
125
//        '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...
126
        'main' => 'b',
127
        'map' => 'i',
128
        'mark' => 'i',
129
//        '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...
130
        'menu' => 'i',
131
        'menuitem' => 'i',
132
        'meta' => 'i',
133
        'meter' => 'i',
134
        'multicol' => 'i',
135
        'nav' => 'b',
136
        'nobr' => 'i',
137
        'noembed' => 'i',
138
        'noframes' => 'i',
139
        'noscript' => 'b',
140
        'object' => 'i',
141
        'ol' => 'b',
142
        'optgroup' => 'i',
143
        'option' => 'b',
144
        'output' => 'i',
145
        'p' => 'b',
146
        'param' => 'i',
147
        'picture' => 'i',
148
//        '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...
149
        'pre' => 'b',
150
        'progress' => 'i',
151
        'q' => 'i',
152
        'rp' => 'i',
153
        'rt' => 'i',
154
        'rtc' => 'i',
155
        'ruby' => 'i',
156
        's' => 'i',
157
        'samp' => 'i',
158
        'script' => 'i',
159
        'section' => 'b',
160
        'select' => 'i',
161
//        '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...
162
        'slot' => 'i',
163
        'small' => 'i',
164
        'source' => 'i',
165
//        '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...
166
        'span' => 'i',
167
//        '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...
168
        'strong' => 'i',
169
        'style' => 'i',
170
        'sub' => 'i',
171
        'summary' => 'i',
172
        'sup' => 'i',
173
        'table' => 'b',
174
        'tbody' => 'i',
175
        'td' => 'i',
176
        'template' => 'i',
177
        'textarea' => 'i',
178
        'tfoot' => 'b',
179
        'th' => 'i',
180
        'thead' => 'i',
181
        'time' => 'i',
182
        'title' => 'i',
183
        'tr' => 'i',
184
        'track' => 'i',
185
//        '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...
186
        'u' => 'i',
187
        'ul' => 'b',
188
        'var' => 'i',
189
        'video' => 'b',
190
        'wbr' => 'i',
191
192
        /// SVG ///
193
        'animate' => 's',
194
        'animateColor' => 's',
195
        'animateMotion' => 's',
196
        'animateTransform' => 's',
197
//        '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...
198
        'circle' => 's',
199
        'desc' => 's',
200
        'defs' => 's',
201
        'discard' => 's',
202
        'ellipse' => 's',
203
        'g' => 's',
204
//        '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...
205
        'line' => 's',
206
        'marker' => 's',
207
        'mask' => 's',
208
        'missing-glyph' => 's',
209
        'mpath' => 's',
210
        'metadata' => 's',
211
        'path' => 's',
212
        'pattern' => 's',
213
        'polygon' => 's',
214
        'polyline' => 's',
215
        'rect' => 's',
216
        'set' => 's',
217
        'svg' => 's',
218
        'switch' => 's',
219
        'symbol' => 's',
220
        'text' => 's',
221
//        '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...
222
        'use' => 's',
223
    ];
224
225
    /**
226
     * @var ExpressionLanguage
227
     */
228
    protected $expressions;
229
230 53
    public function __construct() {
231 53
        $this->expressions = new ExpressionLanguage();
232 53
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
233 53
        $this->expressions->register(
234 53
            'hasChildren',
235 53
            function ($name = null) {
236 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
237 53
            },
238 53
            function ($name = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $name 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...
239
                return false;
240 53
            });
241 53
    }
242
243
    /**
244
     * Register a runtime function.
245
     *
246
     * @param string $name The name of the function.
247
     * @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...
248
     */
249 51
    public function defineFunction($name, $function = null) {
250 51
        if ($function === null) {
251 1
            $function = $name;
252
        }
253
254 51
        $this->expressions->register(
255 51
            $name,
256 51
            $this->getFunctionCompiler($name, $function),
257 51
            $this->getFunctionEvaluator($function)
258
        );
259 51
    }
260
261 51
    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...
262 51
        if ($function === 'empty') {
263 51
            return function ($expr) {
264
                return empty($expr);
265 51
            };
266 50
        } elseif ($function === 'isset') {
267
            return function ($expr) {
268
                return isset($expr);
269
            };
270
        }
271
272 50
        return $function;
273
    }
274
275 51
    private function getFunctionCompiler($name, $function) {
276 51
        $var = var_export(strtolower($name), true);
277 51
        $fn = function ($expr) use ($var) {
278 1
            return "\$this->call($var, $expr)";
279 51
        };
280
281 51
        if (is_string($function)) {
282 51
            $fn = function (...$args) use ($function) {
283 12
                return $function.'('.implode(', ', $args).')';
284 51
            };
285 50
        } elseif (is_array($function)) {
286 50
            if (is_string($function[0])) {
287
                $fn = function (...$args) use ($function) {
288
                    return "$function[0]::$function[1](".implode(', ', $args).')';
289
                };
290 50
            } elseif ($function[0] instanceof Ebi) {
291 50
                $fn = function (...$args) use ($function) {
292 6
                    return "\$this->$function[1](".implode(', ', $args).')';
293 50
                };
294
            }
295
        }
296
297 51
        return $fn;
298
    }
299
300 45
    public function compile($src, array $options = []) {
301 45
        $options += ['basename' => '', 'runtime' => true];
302
303 45
        $src = trim($src);
304
305 45
        $dom = new \DOMDocument();
306 45
        libxml_use_internal_errors(true);
307
308 45
        $fragment = false;
309 45
        if (strpos($src, '<html') === false) {
310 44
            $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...
311 44
            $fragment = true;
312
        }
313
314 45
        $dom->loadHTML($src, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOCDATA | LIBXML_NOXMLDECL);
315
//        $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...
316
317 45
        $out = new CompilerBuffer();
318
319 45
        $out->setBasename($options['basename']);
320
321 45
        if ($options['runtime']) {
322 44
            $name = var_export($options['basename'], true);
323 44
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
324
        } else {
325 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
326
        }
327
328 45
        $out->pushScope(['this' => 'props']);
329 45
        $out->indent(+1);
330
331 45
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
332
333 45
        foreach ($parent->childNodes as $node) {
334 45
            $this->compileNode($node, $out);
335
        }
336
337 45
        $out->indent(-1);
338 45
        $out->popScope();
339
340 45
        if ($options['runtime']) {
341 44
            $out->appendCode("});");
342
        } else {
343 1
            $out->appendCode("};");
344
        }
345
346 45
        $r = $out->flush();
347 45
        return $r;
348
    }
349
350 36
    protected function isComponent($tag) {
351 36
        return !isset(static::$htmlTags[$tag]);
352
    }
353
354 45
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
355 45
        if ($out->getNodeProp($node, 'skip')) {
356 4
            return;
357
        }
358
359 45
        switch ($node->nodeType) {
360 45
            case XML_TEXT_NODE:
361 40
                $this->compileTextNode($node, $out);
362 40
                break;
363 42
            case XML_ELEMENT_NODE:
364
                /* @var \DOMElement $node */
365 42
                $this->compileElementNode($node, $out);
366 42
                break;
367 2
            case XML_COMMENT_NODE:
368
                /* @var \DOMComment $node */
369 1
                $this->compileCommentNode($node, $out);
370 1
                break;
371 1
            case XML_DOCUMENT_TYPE_NODE:
372 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...
373 1
                break;
374
            default:
375
                $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...
376
                    '// '.str_replace("\n", "\n// ", $node->ownerDocument->saveHTML($node));
377
        }
378 45
    }
379
380
    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...
381
        $result = [];
382
383
        if ($root->hasAttributes()) {
384
            $attrs = $root->attributes;
385
            foreach ($attrs as $attr) {
386
                $result['@attributes'][$attr->name] = $attr->value;
387
            }
388
        }
389
390
        if ($root->hasChildNodes()) {
391
            $children = $root->childNodes;
392
            if ($children->length == 1) {
393
                $child = $children->item(0);
394
                if ($child->nodeType == XML_TEXT_NODE) {
395
                    $result['_value'] = $child->nodeValue;
396
                    return count($result) == 1
397
                        ? $result['_value']
398
                        : $result;
399
                }
400
            }
401
            $groups = [];
402
            foreach ($children as $child) {
403
                if (!isset($result[$child->nodeName])) {
404
                    $result[$child->nodeName] = $this->domToArray($child);
405
                } else {
406
                    if (!isset($groups[$child->nodeName])) {
407
                        $result[$child->nodeName] = [$result[$child->nodeName]];
408
                        $groups[$child->nodeName] = 1;
409
                    }
410
                    $result[$child->nodeName][] = $this->domToArray($child);
411
                }
412
            }
413
        }
414
415
        return $result;
416
    }
417
418 1
    protected function newline(DOMNode $node, CompilerBuffer $out) {
419 1
        if ($node->previousSibling && $node->previousSibling->nodeType !== XML_COMMENT_NODE) {
420
            $out->appendCode("\n");
421
        }
422 1
    }
423
424 1
    protected function compileCommentNode(\DOMComment $node, CompilerBuffer $out) {
425 1
        $comments = explode("\n", trim($node->nodeValue));
426
427 1
        $this->newline($node, $out);
428 1
        foreach ($comments as $comment) {
429 1
            $out->appendCode("// $comment\n");
430
        }
431 1
    }
432
433 40
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
434 40
        $text = $this->ltrim($this->rtrim($node->nodeValue, $node, $out), $node, $out);
435
436 40
        $items = $this->splitExpressions($text);
437
438 40
        foreach ($items as $i => list($text, $offset)) {
439 40
            if (preg_match('`^{\S`', $text)) {
440 25
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
441 2
                    $out->echoCode($this->expr($m[1], $out));
442
                } else {
443 25
                    $out->echoCode('htmlspecialchars('.$this->expr(substr($text, 1, -1), $out).')');
444
                }
445
            } else {
446
//                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...
447
//                    $text = $this->ltrim($text, $node, $out);
448
//                }
449
//                if ($i === count($items) - 1) {
450
//                    $text = $this->rtrim($text, $node, $out);
451
//                }
452
453 40
                $out->echoLiteral($text);
454
            }
455
        }
456 40
    }
457
458 42
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
459 42
        list($attributes, $special) = $this->splitAttributes($node);
460
461 42
        if ($node->tagName === self::T_EXPR) {
462 5
            $this->compileExpressionNode($node, $attributes, $special, $out);
463 38
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
464 32
            $this->compileSpecialNode($node, $attributes, $special, $out);
465
        } else {
466 18
            $this->compileOpenTag($node, $node->attributes, $out);
467
468 18
            foreach ($node->childNodes as $childNode) {
469 17
                $this->compileNode($childNode, $out);
470
            }
471
472 18
            $this->compileCloseTag($node, $out);
473
        }
474 42
    }
475
476 40
    protected function splitExpressions($value) {
477 40
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
478 40
        return $values;
479
    }
480
481 40
    protected function expr($expr, CompilerBuffer $output, DOMAttr $attr = null) {
482 40
        $names = $output->getScopeVariables();
483
484 40
        $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...
485 38
            if (isset($names[$name])) {
486 20
                return $names[$name];
487 31
            } elseif ($name[0] === '@') {
488 1
                return 'this->meta['.var_export(substr($name, 1), true).']';
489
            } else {
490 30
                return $names['this'].'['.var_export($name, true).']';
491
            }
492 40
        });
493
494 40
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
495 4
            $compiled = call_user_func($fn, $compiled);
496
        }
497
498 40
        return $compiled;
499
    }
500
501
    /**
502
     * Get the compiler function to wrap an attribute.
503
     *
504
     * Attribute functions are regular expression functions, but with a special naming convention. The following naming
505
     * conventions are supported:
506
     *
507
     * - **@tag:attribute**: Applies to an attribute only on a specific tag.
508
     * - **@attribute**: Applies to all attributes with a given name.
509
     *
510
     * @param DOMAttr $attr The attribute to look at.
511
     * @return callable|null A function or **null** if the attribute doesn't have a function.
512
     */
513 14
    private function getAttributeFunction(DOMAttr $attr) {
514 14
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
515
516 14
        foreach ($keys as $key) {
517 14
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
518 14
                return $fn;
519
            }
520
        }
521 9
    }
522
523
    /**
524
     * @param DOMElement $node
525
     */
526 42
    protected function splitAttributes(DOMElement $node) {
527 42
        $attributes = [];
528 42
        $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...
529
530 42
        foreach ($node->attributes as $name => $attribute) {
531 40
            if (isset(static::$special[$name])) {
532 34
                $special[$name] = $attribute;
533
            } else {
534 40
                $attributes[$name] = $attribute;
535
            }
536
        }
537
538 42
        uksort($special, function ($a, $b) {
539 8
            return strnatcmp(static::$special[$a], static::$special[$b]);
540 42
        });
541
542 42
        return [$attributes, $special];
543
    }
544
545 32
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
546 32
        $specialName = key($special);
547
548
        switch ($specialName) {
549 32
            case self::T_COMPONENT:
550 9
                $this->compileComponentRegister($node, $attributes, $special, $out);
551 9
                break;
552 32
            case self::T_IF:
553 8
                $this->compileIf($node, $attributes, $special, $out);
554 8
                break;
555 32
            case self::T_EACH:
556 12
                $this->compileEach($node, $attributes, $special, $out);
557 12
                break;
558 23
            case self::T_BLOCK:
559 1
                $this->compileBlock($node, $attributes, $special, $out);
560 1
                break;
561 23
            case self::T_CHILDREN:
562 3
                $this->compileChildBlock($node, $attributes, $special, $out);
563 3
                break;
564 23
            case self::T_INCLUDE:
565 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
566 1
                break;
567 23
            case self::T_WITH:
568 4
                if ($this->isComponent($node->tagName)) {
569
                    // With has a special meaning in components.
570 3
                    $this->compileComponentInclude($node, $attributes, $special, $out);
571
                } else {
572 1
                    $this->compileWith($node, $attributes, $special, $out);
573
                }
574 4
                break;
575 22
            case self::T_LITERAL:
576 2
                $this->compileLiteral($node, $attributes, $special, $out);
577 2
                break;
578 20
            case '':
579 20
                if ($this->isComponent($node->tagName)) {
580 7
                    $this->compileComponentInclude($node, $attributes, $special, $out);
581
                } else {
582 19
                    $this->compileElement($node, $attributes, $out);
583
                }
584 20
                break;
585
        }
586 32
    }
587
588
    /**
589
     * Compile component registering.
590
     *
591
     * @param DOMElement $node
592
     * @param $attributes
593
     * @param $special
594
     * @param CompilerBuffer $out
595
     */
596 9
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
597 9
        $name = strtolower($special[self::T_COMPONENT]->value);
598 9
        unset($special[self::T_COMPONENT]);
599
600 9
        $prev = $out->select($name);
601
602 9
        $varName = var_export($name, true);
603 9
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
604 9
        $out->pushScope(['this' => 'props']);
605 9
        $out->indent(+1);
606
607
        try {
608 9
            $this->compileSpecialNode($node, $attributes, $special, $out);
609 9
        } finally {
610 9
            $out->popScope();
611 9
            $out->indent(-1);
612 9
            $out->appendCode("});");
613 9
            $out->select($prev);
614
        }
615 9
    }
616
617 1
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
618 1
        $name = strtolower($special[self::T_BLOCK]->value);
619 1
        unset($special[self::T_BLOCK]);
620
621 1
        $prev = $out->select($name);
622
623 1
        $use = '$'.implode(', $', $out->getScopeVariables()).', $children';
624
625 1
        $out->appendCode("function () use ($use) {\n");
626 1
        $out->pushScope(['this' => 'props']);
627 1
        $out->indent(+1);
628
629
        try {
630 1
            $this->compileSpecialNode($node, $attributes, $special, $out);
631 1
        } finally {
632 1
            $out->indent(-1);
633 1
            $out->popScope();
634 1
            $out->appendCode("}");
635 1
            $out->select($prev);
636
        }
637
638 1
        return $out;
639
    }
640
641
    /**
642
     * Compile component inclusion and rendering.
643
     *
644
     * @param DOMElement $node
645
     * @param DOMAttr[] $attributes
646
     * @param DOMAttr[] $special
647
     * @param CompilerBuffer $out
648
     */
649 11
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
650
        // Generate the attributes into a property array.
651 11
        $props = [];
652 11
        foreach ($attributes as $name => $attribute) {
653
            /* @var DOMAttr $attr */
654 5
            if ($this->isExpression($attribute->value)) {
655 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
656
            } else {
657 1
                $expr = var_export($attribute->value, true);
658
            }
659
660 5
            $props[] = var_export($name, true).' => '.$expr;
661
        }
662 11
        $propsStr = '['.implode(', ', $props).']';
663
664 11
        if (isset($special[self::T_WITH])) {
665 3
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
666 3
            unset($special[self::T_WITH]);
667
668 3
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
669
        } elseif (empty($props)) {
670
            // By default the current context is passed to components.
671 4
            $propsStr = $this->expr('this', $out);
672
        }
673
674
        // Compile the children blocks.
675 11
        $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...
676 11
        $blocksStr = $blocks->flush();
677
678 11
        if (isset($special[self::T_INCLUDE])) {
679 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
680
        } else {
681 10
            $name = var_export($node->tagName, true);
682
        }
683
684 11
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
685 11
    }
686
687
    /**
688
     * @param DOMElement $parent
689
     * @return CompilerBuffer
690
     */
691 11
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
692 11
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
693 11
            'baseIndent' => $out->getIndent(),
694 11
            'indent' => $out->getIndent() + 1,
695 11
            'depth' => $out->getDepth(),
696 11
            'scopes' => $out->getAllScopes()
697
        ]);
698
699 11
        if ($this->isEmptyNode($parent)) {
700 9
            return $blocksOut;
701
        }
702
703 3
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
704
705 3
        $blocksOut->appendCode("function () use ($use) {\n");
706 3
        $blocksOut->indent(+1);
707
708
        try {
709 3
            foreach ($parent->childNodes as $node) {
710 3
                $this->compileNode($node, $blocksOut);
711
            }
712 3
        } finally {
713 3
            $blocksOut->indent(-1);
714 3
            $blocksOut->appendCode("}");
715
        }
716
717 3
        return $blocksOut;
718
    }
719
720 23
    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...
721
        // Don't double up comments.
722 23
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
723
            return;
724
        }
725
726 23
        $str = '<'.$node->tagName;
727 23
        foreach ($special as $attr) {
728
            /* @var DOMAttr $attr */
729 23
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.htmlspecialchars($attr->value).'"');
730
        }
731 23
        $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...
732 23
        $comments = explode("\n", $str);
733 23
        foreach ($comments as $comment) {
734 23
            $out->appendCode("// $comment\n");
735
        }
736 23
    }
737
738 37
    protected function compileOpenTag(DOMElement $node, $attributes, CompilerBuffer $out, $force = false) {
739 37
        if ($node->tagName === self::T_X) {
740 4
            return;
741
        }
742
743 36
        $out->echoLiteral('<'.$node->tagName);
744
745 36
        foreach ($attributes as $name => $attribute) {
746
            /* @var DOMAttr $attribute */
747 9
            $out->echoLiteral(' '.$name.'="');
748
749
            // Check for an attribute expression.
750 9
            if ($this->isExpression($attribute->value)) {
751 6
                $out->echoCode('htmlspecialchars('.$this->expr(substr($attribute->value, 1, -1), $out, $attribute).')');
752 3
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
753 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...
754
755 2
                $out->echoCode("htmlspecialchars($value)");
756
757
            } else {
758 2
                $out->echoLiteral(htmlspecialchars($attribute->value));
759
            }
760
761 9
            $out->echoLiteral('"');
762
        }
763
764 36
        if ($node->hasChildNodes() || $force) {
765 35
            $out->echoLiteral('>');
766
        } else {
767 2
            $out->echoLiteral(" />");
768
        }
769 36
    }
770
771 12
    private function isExpression($value) {
772 12
        return preg_match('`^{\S.*}$`', $value);
773
    }
774
775 37
    protected function compileCloseTag(DOMElement $node, CompilerBuffer $out, $force = false) {
776 37
        if (($force || $node->hasChildNodes()) && $node->tagName !== self::T_X) {
777 35
            $out->echoLiteral("</{$node->tagName}>");
778
        }
779 37
    }
780
781 4
    protected function isEmptyText(DOMNode $node) {
782 4
        return $node instanceof \DOMText && empty(trim($node->data));
783
    }
784
785 11
    protected function isEmptyNode(DOMNode $node) {
786 11
        if (!$node->hasChildNodes()) {
787 9
            return true;
788
        }
789
790 3
        foreach ($node->childNodes as $childNode) {
791 3
            if ($childNode instanceof DOMElement) {
792 1
                return false;
793
            }
794 2
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
795 2
                return false;
796
            }
797
        }
798
799
        return true;
800
    }
801
802 8
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
803 8
        $this->compileTagComment($node, $attributes, $special, $out);
804 8
        $expr = $this->expr($special[self::T_IF]->value, $out);
805 8
        unset($special[self::T_IF]);
806
807 8
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
808 8
        $out->setNodeProp($elseNode, 'skip', true);
809
810 8
        $out->appendCode('if ('.$expr.") {\n");
811 8
        $out->indent(+1);
812
813 8
        $this->compileSpecialNode($node, $attributes, $special, $out);
814
815 8
        $out->indent(-1);
816
817 8
        if ($elseNode) {
818 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
819 2
            unset($special[self::T_ELSE]);
820
821 2
            $out->appendCode("} else {\n");
822
823 2
            $out->indent(+1);
824 2
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
825 2
            $out->indent(-1);
826
        }
827
828 8
        $out->appendCode("}\n");
829 8
    }
830
831 12
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
832 12
        $this->compileTagComment($node, $attributes, $special, $out);
833 12
        $this->compileOpenTag($node, $attributes, $out);
834
835 12
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
836 12
        $out->setNodeProp($emptyNode, 'skip', true);
837
838 12
        if ($emptyNode === null) {
839 10
            $this->compileEachLoop($node, $attributes, $special, $out);
840
        } else {
841 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
842
843 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
844 2
            unset($emptySpecial[self::T_EMPTY]);
845
846 2
            $out->appendCode('if ('.$expr.") {\n");
847
848 2
            $out->indent(+1);
849 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
850 2
            $out->indent(-1);
851
852 2
            $out->appendCode("} else {\n");
853
854 2
            $out->indent(+1);
855 2
            $this->compileEachLoop($node, $attributes, $special, $out);
856 2
            $out->indent(-1);
857
858 2
            $out->appendCode("}\n");
859
        }
860
861 12
        $this->compileCloseTag($node, $out);
862 12
    }
863
864 1
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
865 1
        $this->compileTagComment($node, $attributes, $special, $out);
866 1
        $with = $this->expr($special[self::T_WITH]->value, $out);
867
868 1
        $out->depth(+1);
869 1
        $scope = ['this' => $out->depthName('props')];
870 1
        if (!empty($special[self::T_AS]) && preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
871
            // The template specified an x-as attribute to alias the with expression.
872 1
            $scope = [$m[1] => $out->depthName('props')];
873
        }
874 1
        unset($special[self::T_WITH], $special[self::T_AS]);
875
876 1
        $out->pushScope($scope);
877 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
878
879 1
        $this->compileSpecialNode($node, $attributes, $special, $out);
880
881 1
        $out->depth(-1);
882 1
        $out->popScope();
883 1
    }
884
885 2
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
886 2
        $this->compileTagComment($node, $attributes, $special, $out);
887 2
        unset($special[self::T_LITERAL]);
888
889 2
        $this->compileOpenTag($node, $attributes, $out);
890
891 2
        foreach ($node->childNodes as $childNode) {
892 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
893 2
            $out->echoLiteral($html);
894
        }
895
896 2
        $this->compileCloseTag($node, $out);
897 2
    }
898
899 19
    protected function compileElement(DOMElement $node, array $attributes, CompilerBuffer $out) {
900 19
        $this->compileOpenTag($node, $attributes, $out);
901
902 19
        foreach ($node->childNodes as $childNode) {
903 19
            $this->compileNode($childNode, $out);
904
        }
905
906 19
        $this->compileCloseTag($node, $out);
907 19
    }
908
909
    /**
910
     * Find a special node in relation to another node.
911
     *
912
     * This method is used to find things such as x-empty and x-else elements.
913
     *
914
     * @param DOMElement $node The node to search in relation to.
915
     * @param string $attribute The name of the attribute to search for.
916
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
917
     * @return DOMElement|null Returns the found element node or **null** if not found.
918
     */
919 20
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
920
        // First look for a sibling after the node.
921 20
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
922 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
923 2
                return $sibNode;
924
            }
925
926
            // Stop searching if we encounter another node.
927 2
            if (!$this->isEmptyText($sibNode)) {
928
                break;
929
            }
930
        }
931
932
        // Next look inside the node.
933 18
        $parentFound = false;
934 18
        foreach ($node->childNodes as $childNode) {
935 17
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
936 2
                return $childNode;
937
            }
938
939 17
            if ($childNode instanceof DOMElement) {
940 12
                $parentFound = $childNode->hasAttribute($parentAttribute);
941 7
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
942 17
                $parentFound = false;
943
            }
944
        }
945
946 16
        return null;
947
    }
948
949
    /**
950
     * @param DOMElement $node
951
     * @param array $attributes
952
     * @param array $special
953
     * @param CompilerBuffer $out
954
     */
955 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...
956 12
        $each = $this->expr($special[self::T_EACH]->value, $out);
957 12
        unset($special[self::T_EACH]);
958
959 12
        $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...
960 12
        $scope = ['this' => $as[1]];
961 12
        if (!empty($special[self::T_AS])) {
962 6
            if (preg_match('`(?:([a-z0-9]+)\s+)?([a-z0-9]+)`i', $special[self::T_AS]->value, $m)) {
963 6
                $scope = [$m[2] => $as[1]];
964 6
                if (!empty($m[1])) {
965 3
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
966
                }
967
            }
968
        }
969 12
        unset($special[self::T_AS]);
970 12
        if (empty($as[0])) {
971 9
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
972
        } else {
973 3
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
974
        }
975 12
        $out->depth(+1);
976 12
        $out->indent(+1);
977 12
        $out->pushScope($scope);
978
979 12
        foreach ($node->childNodes as $childNode) {
980 12
            $this->compileNode($childNode, $out);
981
        }
982
983 12
        $out->indent(-1);
984 12
        $out->depth(-1);
985 12
        $out->popScope();
986 12
        $out->appendCode("}\n");
987 12
    }
988
989 40
    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...
990 40
        if ($this->inPre($node)) {
991
            return $text;
992
        }
993
994 40
        $sib = $node->previousSibling ?: $node->parentNode;
995 40
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
996 6
            return ltrim($text);
997
        }
998
999 39
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
1000 39
        if ($count === 0) {
1001 39
            $text = preg_replace('`^\s+`', ' ', $text);
1002
        }
1003
1004
//        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...
1005
//            return ltrim($text);
1006
//        }
1007 39
        return $text;
1008
    }
1009
1010 40
    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...
1011 40
        if ($this->inPre($node)) {
1012
            return $text;
1013
        }
1014
1015 40
        $sib = $node->nextSibling ?: $node->parentNode;
1016
1017 40
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1018 5
            return rtrim($text);
1019
        }
1020
1021 39
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1022 39
        if ($count === 0) {
1023 39
            $text = preg_replace('`\s+$`', ' ', $text);
1024
        }
1025
1026
//        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...
1027
//            return rtrim($text);
1028
//        }
1029 39
        return $text;
1030
    }
1031
1032 40
    protected function inPre(\DOMNode $node) {
1033 40
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1034 40
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1035
                return true;
1036
            }
1037
        }
1038 40
        return false;
1039
    }
1040
1041 3
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1042
        /* @var DOMAttr $child */
1043 3
        $child = $special[self::T_CHILDREN];
1044 3
        unset($special[self::T_CHILDREN]);
1045
1046 3
        $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...
1047 3
        $keyStr = var_export($key, true);
1048
1049 3
        $this->compileOpenTag($node, $attributes, $out, true);
1050
1051 3
        $out->appendCode("if (isset(\$children[{$keyStr}])) {\n");
1052 3
        $out->indent(+1);
1053 3
        $out->appendCode("\$children[{$keyStr}]();\n");
1054 3
        $out->indent(-1);
1055 3
        $out->appendCode("}\n");
1056
1057 3
        $this->compileCloseTag($node, $out, true);
1058 3
    }
1059
1060
    /**
1061
     * Compile an x-expr node.
1062
     *
1063
     * @param DOMElement $node The node to compile.
1064
     * @param array $attributes The node's attributes.
1065
     * @param array $special An array of special attributes.
1066
     * @param CompilerBuffer $out The compiler output.
1067
     */
1068 5
    private function compileExpressionNode(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...
1069 5
        $str = $raw = $node->nodeValue;
0 ignored issues
show
Unused Code introduced by
$raw 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...
1070 5
        $expr = $this->expr($str, $out);
1071
1072 5
        if (!empty($special[self::T_AS]) && preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
1073
            // The template specified an x-as attribute to alias the with expression.
1074 3
            $scope = [$m[1] => $out->depthName('props', 1)];
1075 3
            $out->pushScope($scope);
1076 3
            $out->appendCode('$'.$out->depthName('props', 1)." = $expr;\n");
1077 2
        } elseif (!empty($special[self::T_UNESCAPE])) {
1078 1
            $out->echoCode($expr);
1079
        } else {
1080 1
            $out->echoCode('htmlspecialchars('.$expr.')');
1081
        }
1082 5
    }
1083
}
1084