Completed
Pull Request — master (#19)
by Todd
03:16
created

Compiler::compileOpenTag()   C

Complexity

Conditions 13
Paths 42

Size

Total Lines 42
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 13

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 31
cts 31
cp 1
rs 5.1234
c 0
b 0
f 0
cc 13
eloc 29
nc 42
nop 5
crap 13

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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