Passed
Push — master ( 6cde99...6a1e51 )
by Lawrence
01:23
created

Format::is_closed_tag()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 14
c 1
b 0
f 0
nc 8
nop 0
dl 0
loc 22
ccs 12
cts 12
cp 1
crap 5
rs 8.6737
1
<?php
2
namespace Roesch;
3
4
/**
5
 * Format HTML class
6
 */
7
class Format
8
{
9
    /*
10
     * @var
11
     */
12
    private $input = null;
13
14
    /*
15
     * @var
16
     */
17
    private $output = null;
18
19
    /*
20
     * @var
21
     */
22
    private $in_tag = false;
23
24
    /*
25
     * @var
26
     */
27
    private $in_comment = false;
28
29
    /*
30
     * @var
31
     */
32
    private $in_content = false;
33
34
    /*
35
     * @var
36
     */
37
    private $inline_tag = false;
38
39
    /*
40
     * @var
41
     */
42
    private $i = 0;
43
44
    /*
45
     * @var
46
     */
47
    private $indent_depth = 0;
48
49
    /*
50
     * @var
51
     */
52
    private $indent_type = "\t";
53
    
54
    /**
55
     * Static interface
56
     * - Allows you to call the method witout initialising the class first
57
     *
58
     * <code>
59
     *  // use spaces at 4 length
60
     *  echo \Roesch\Format::HTML('Unformatted HTML string');
61
     *
62
     *  // use spaces at 2 length
63
     *  echo \Roesch\Format::HTML('Unformatted HTML string', true, 2);
64
     *
65
     *  // use tabs
66
     *  echo \Roesch\Format::HTML('Unformatted HTML string', false);
67
     * </code>
68
     *
69
     * @param  string $input          HTML which is to be processed
70
     * @param  bool   $use_spaces     Use spaces instead of tabs
71
     * @param  int    $indent_length  Length of indent spacing
72
     * @return string
73
     */
74 10
    public static function HTML($input, $use_spaces = true, $indent_length = 4)
75
    {
76 10
        return (new self)->process($input, $use_spaces, $indent_length);
77
    }
78
    
79
    /**
80
     * Process/Format HTML
81
     *
82
     * <code>
83
     *  $format = new \Roesch\Format();
84
     *
85
     *  // use spaces at 4 length
86
     *  echo $format->html('Unformatted HTML string');
87
     *
88
     *  // use spaces at 2 length
89
     *  echo $format->html('Unformatted HTML string', true, 2);
90
     *
91
     *  // use tabs
92
     *  echo $format->html('Unformatted HTML string', false);
93
     * </code>
94
     *
95
     * @param  string $input          HTML which is to be processed
96
     * @param  bool   $use_spaces     Use spaces instead of tabs
97
     * @param  int    $indent_length  Length of indent spacing
98
     * @return string
99
     */
100 10
    private function process($input, $use_spaces = true, $indent_length = 4)
101
    {
102 10
        if (!is_string($input)) {
103 1
            throw new \InvalidArgumentException('1st argument must be a string');
104
        }
105
106 9
        if (!is_int($indent_length)) {
107 1
            throw new \InvalidArgumentException('3rd argument must be an integer');
108
        }
109
110 8
        if ($indent_length < 0) {
111 1
            throw new \InvalidArgumentException('3rd argument must be greater or equals 0');
112
        }
113
114 7
        if ($use_spaces) {
115 7
            $this->indent_type = str_repeat(' ', $indent_length);
116
        }
117
118 7
        $this->input = $input;
119 7
        $this->output = null;
120
121 7
        $i = 0;
122
123 7
        if (preg_match('/<\!doctype/i', $this->input)) {
124 2
            $i = strpos($this->input, '>') + 1;
125 2
            $this->output .= substr($this->input, 0, $i);
126
        }
127
128 7
        for ($this->i = $i; $this->i < strlen($this->input); $this->i++) {
129 7
            if ($this->in_comment) {
130 1
                $this->parse_comment();
131 7
            } elseif ($this->in_tag) {
132 6
                $this->parse_inner_tag();
133 7
            } elseif ($this->inline_tag) {
134 3
                $this->parse_inner_inline_tag();
135
            } else {
136 7
                if (preg_match('/[\r\n\t]/', $this->input[$this->i])) {
137 3
                    continue;
138 7
                } elseif ($this->input[$this->i] == '<') {
139 7
                    if (!$this->is_inline_tag()) {
140 5
                        $this->in_content = false;
141
                    }
142 7
                    $this->parse_tag();
143 2
                } elseif (!$this->in_content) {
144 2
                    if (!$this->inline_tag) {
145 2
                        $this->output .= "\n" . str_repeat($this->indent_type, $this->indent_depth);
146
                    }
147 2
                    $this->in_content = true;
148
                }
149 7
                $this->output .= $this->input[$this->i];
150
            }
151
        }
152
153 7
        return trim(preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $this->output));
154
    }
155
156
    /**
157
     * @return void
158
     */
159 1
    private function parse_comment()
160
    {
161 1
        if ($this->is_end_comment()) {
162 1
            $this->in_comment = false;
163 1
            $this->output .= '-->';
164 1
            $this->i += 3;
165
        } else {
166 1
            $this->output .= $this->input[$this->i];
167
        }
168 1
    }
169
170
    /**
171
     * @return void
172
     */
173 6
    private function parse_inner_tag()
174
    {
175 6
        if ($this->input[$this->i] == '>') {
176 6
            $this->in_tag = false;
177 6
            $this->output .= '>';
178
        } else {
179 6
            $this->output .= $this->input[$this->i];
180
        }
181 6
    }
182
183
    /**
184
     * @return void
185
     */
186 3
    private function parse_inner_inline_tag()
187
    {
188 3
        if ($this->input[$this->i] == '>') {
189 3
            $this->inline_tag = false;
190 3
            $this->decrement_tabs();
191 3
            $this->output .= '>';
192
        } else {
193 3
            $this->output .= $this->input[$this->i];
194
        }
195 3
    }
196
197
    /**
198
     * @return void
199
     */
200 7
    private function parse_tag()
201
    {
202 7
        if ($this->is_comment()) {
203 1
            $this->output .= "\n" . str_repeat($this->indent_type, $this->indent_depth);
204 1
            $this->in_comment = true;
205 6
        } elseif ($this->is_end_tag()) {
206 4
            $this->in_tag = true;
207 4
            $this->inline_tag = false;
208 4
            $this->decrement_tabs();
209 4
            if (!$this->is_inline_tag() && !$this->is_tag_empty()) {
210 4
                $this->output .= "\n" . str_repeat($this->indent_type, $this->indent_depth);
211
            }
212
        } else {
213 6
            $this->in_tag = true;
214 6
            if (!$this->in_content && !$this->inline_tag) {
215 6
                $this->output .= "\n" . str_repeat($this->indent_type, $this->indent_depth);
216
            }
217 6
            if (!$this->is_closed_tag()) {
218 6
                $this->indent_depth++;
219
            }
220 6
            if ($this->is_inline_tag()) {
221 3
                $this->inline_tag = true;
222
            }
223
        }
224 7
    }
225
226
    /**
227
     * @return bool
228
     */
229 6
    private function is_end_tag()
230
    {
231 6
        for ($i = $this->i; $i < strlen($this->input); $i++) {
232 6
            if ($this->input[$i] == '<' && $this->input[$i + 1] == '/') {
233 3
                return true;
234 6
            } elseif ($this->input[$i] == '<' && $this->input[$i + 1] == '!') {
235 1
                return true;
236 6
            } elseif ($this->input[$i] == '>') {
237 6
                return false;
238
            }
239
        }
240
        // @codeCoverageIgnoreStart
241
        return false;
242
        // @codeCoverageIgnoreEnd
243
    }
244
245
    /**
246
     * @return void
247
     */
248 6
    private function decrement_tabs()
249
    {
250 6
        $this->indent_depth--;
251 6
        if ($this->indent_depth < 0) {
252
            // @codeCoverageIgnoreStart
253
            $this->indent_depth = 0;
254
            // @codeCoverageIgnoreEnd
255
        }
256 6
    }
257
258
    /**
259
     * @return bool
260
     */
261 7
    private function is_comment()
262
    {
263
        if (
264 7
            $this->input[$this->i] == '<' &&
265 7
            $this->input[$this->i + 1] == '!' &&
266 7
            $this->input[$this->i + 2] == '-' &&
267 7
            $this->input[$this->i + 3] == '-'
268
        ) {
269 1
            return true;
270
        } else {
271 6
            return false;
272
        }
273
    }
274
275
    /**
276
     * @return bool
277
     */
278 1
    private function is_end_comment()
279
    {
280
        if (
281 1
            $this->input[$this->i] == '-' &&
282 1
            $this->input[$this->i + 1] == '-' &&
283 1
            $this->input[$this->i + 2] == '>'
284
        ) {
285 1
            return true;
286
        } else {
287 1
            return false;
288
        }
289
    }
290
291
    /**
292
     * @return bool
293
     */
294 4
    private function is_tag_empty()
295
    {
296 4
        $tag = $this->get_current_tag($this->i + 2);
297 4
        $in_tag = false;
298
299 4
        for ($i = $this->i - 1; $i >= 0; $i--) {
300 4
            if (!$in_tag) {
301 4
                if ($this->input[$i] == '>') {
302 4
                    $in_tag = true;
303 2
                } elseif (!preg_match('/\s/', $this->input[$i])) {
304 4
                    return false;
305
                }
306
            } else {
307 4
                if ($this->input[$i] == '<') {
308 4
                    if ($tag == $this->get_current_tag($i + 1)) {
309 3
                        return true;
310
                    } else {
311 3
                        return false;
312
                    }
313
                }
314
            }
315
        }
316
        // @codeCoverageIgnoreStart
317
        return true;
318
        // @codeCoverageIgnoreEnd
319
    }
320
321
    /**
322
     * @param  int    $i String index of input
323
     * @return string
324
     */
325 4
    private function get_current_tag($i)
326
    {
327 4
        $tag = '';
328
329 4
        for ($i; $i < strlen($this->input); $i++) {
330 4
            if ($this->input[$i] == '<') {
331
                // @codeCoverageIgnoreStart
332
                continue;
333
                // @codeCoverageIgnoreEnd
334 4
            } elseif ($this->input[$i] == '>' or preg_match('/\s/', $this->input[$i])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
335 4
                return $tag;
336
            } else {
337 4
                $tag .= $this->input[$i];
338
            }
339
        }
340
        
341
        // @codeCoverageIgnoreStart
342
        return $tag;
343
        // @codeCoverageIgnoreEnd
344
    }
345
346
    /**
347
     * @return bool
348
     */
349 6
    private function is_closed_tag()
350
    {
351
        $tags = array(
352 6
            'meta', 'link', 'img', 'hr', 'br', 'input',
353
        );
354
355 6
        $tag = '';
356
357 6
        for ($i = $this->i; $i < strlen($this->input); $i++) {
358 6
            if ($this->input[$i] == '<') {
359 6
                continue;
360 6
            } elseif (preg_match('/\s/', $this->input[$i])) {
361 5
                break;
362
            } else {
363 6
                $tag .= $this->input[$i];
364
            }
365
        }
366
367 6
        if (in_array($tag, $tags)) {
368 2
            return true;
369
        } else {
370 6
            return false;
371
        }
372
    }
373
374
    /**
375
     * @return bool
376
     */
377 7
    private function is_inline_tag()
378
    {
379
        $tags = array(
380 7
            'title',
381
            'span',
382
            'abbr',
383
            'acronym',
384
            'b',
385
            'basefont',
386
            'bdo',
387
            'big',
388
            'cite',
389
            'code',
390
            'dfn',
391
            'em',
392
            'font',
393
            'i',
394
            'kbd',
395
            'q',
396
            's',
397
            'samp',
398
            'small',
399
            'strike',
400
            //'strong',
401
            'sub',
402
            'sup',
403
            'textarea',
404
            'tt',
405
            'u',
406
            'var',
407
            'del',
408
            'pre',
409
        );
410
411 7
        $tag = '';
412
413 7
        for ($i = $this->i; $i < strlen($this->input); $i++) {
414 7
            if ($this->input[$i] == '<' or $this->input[$i] == '/') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
415 7
                continue;
416 7
            } elseif (preg_match('/\s/', $this->input[$i]) or $this->input[$i] == '>') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
417 7
                break;
418
            } else {
419 7
                $tag .= $this->input[$i];
420
            }
421
        }
422
423 7
        if (in_array($tag, $tags)) {
424 3
            return true;
425
        } else {
426 5
            return false;
427
        }
428
    }
429
}
430