InlineJavascriptMinifier::process()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 19
rs 9.9666
1
<?php
2
3
namespace gulch\Minify\Processor;
4
5
use gulch\Minify\Contract\ProcessorInterface;
6
7
class InlineJavascriptMinifier implements ProcessorInterface
8
{
9
    public function process(string $buffer): string
10
    {
11
        if (\strlen($buffer) === 0) {
12
            return '';
13
        }
14
15
        $javascript_minified = [];
16
        \preg_match_all('{<script.+</script>}msU', $buffer, $script_blocks);
17
18
        // Minify the javascript in <script> tags.
19
        foreach ($script_blocks[0] as $block) {
20
            $javascript_minified[] = $this->minifyJavascript($block);
21
        }
22
23
        if (\sizeof($javascript_minified)) {
24
            $buffer = \str_replace($script_blocks[0], $javascript_minified, $buffer);
25
        }
26
27
        return $buffer;
28
    }
29
30
    private function minifyJavascript(string $buffer): string
31
    {
32
        $tags = ['close' => \strrchr($buffer, '<')];
33
        $open_length = \strpos($buffer, '>') + 1;
34
        $tags['open'] = \substr($buffer, 0, $open_length);
35
        $buffer = \substr($buffer, $open_length, -\strlen($tags['close']));
36
37
        // Strip spaces from the tags
38
        $tags = \preg_replace('/\s{2,}/', ' ', $tags);
39
40
        // Catch all string literals and comment blocks
41
        if (\preg_match_all(
42
            '#((?:((?<!\\\)\'|")|(/\*)|(//)).*(?(2)(?<!\\\)\2|(?(3)\*/|\n)))#msuUS',
43
            $buffer,
44
            $match,
45
            \PREG_OFFSET_CAPTURE
46
        )) {
47
            $js_literals = $js_code = [];
48
            for ($match = $match[0], $c = \count($match), $i = $pos = $offset = 0; $i < $c; $i++) {
49
                $js_code[$pos++] = \trim(\substr($buffer, $offset, $match[$i][1] - $offset));
50
                $offset = $match[$i][1] + \strlen($match[$i][0]);
51
52
                // Save only if we haven't matched a comment block
53
                if ($match[$i][0][0] !== '/') {
54
                    $js_literals[$pos++] = \array_shift($match[$i]);
55
                }
56
            }
57
            $js_code[$pos] = \substr($buffer, $offset);
58
59
            // $match might be quite large, so free it up together with other vars that we no longer need
60
            unset($match, $offset, $pos);
61
        } else {
62
            $js_code = [$buffer];
63
            $js_literals = [];
64
        }
65
66
        // Standartize new lines
67
        $js_code = \str_replace(["\r\n", "\r"], "\n", $js_code);
68
69
        $patterns = [
70
            // Remove spaces following and preceeding JS-wise non-special & non-word characters
71
            '#\s*([!\#%&()*+,\-./:;<=>?@\[\]^`{|}~])\s*#' => '$1',
72
            // Reduce the remaining multiple whitespace characters to a single space
73
            '#\s{2,}#' => ' ',
74
        ];
75
76
        $js_code = \preg_replace(
77
            \array_keys($patterns),
78
            \array_values($patterns),
79
            $js_code
80
        );
81
82
        // Glue back JS quoted strings
83
        $js_code += $js_literals;
84
        \ksort($js_code);
85
        $buffer = \implode($js_code);
86
87
        return $tags['open'] . $buffer . $tags['close'];
88
    }
89
}
90