Completed
Pull Request — master (#2252)
by ྅༻ Ǭɀħ
04:13
created

Bookmarkletgen   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 177
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 177
rs 10
c 0
b 0
f 0
wmc 18
lcom 1
cbo 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A crunch() 0 13 1
A encodeURIComponent() 0 7 1
A kill_comments() 0 6 1
A compress_white_space() 0 19 2
A combine_strings() 0 6 1
C replace_strings() 0 52 10
A restore_strings() 0 7 2
1
<?php
2
3
/**
4
 * BookmarkletGen : converts readable Javascript code into a bookmarklet link
5
 *
6
 * Features :
7
 * - removes comments
8
 * - compresses code, not literal strings
9
 *   Example:
10
 *   function someName( param ) { alert( "this is a string" ) }
11
 *   will return:
12
 *   function%20someName(param){alert("this is a string")}
13
 * - wraps code into a self invoking function
14
 *
15
 * This is basically a slightly enhanced PHP port of the excellent Bookmarklet Crunchinator
16
 * http://ted.mielczarek.org/code/mozilla/bookmarklet.html
17
 *
18
 */
19
namespace Ozh\Bookmarkletgen;
20
21
class Bookmarkletgen {
22
23
    private $literal_strings = array();
24
    
25
    /**
26
     * Main function, calls all others
27
     *
28
     * @param  string $code  Javascript code to bookmarkletify
29
     * @return string        Bookmarklet link
30
     */
31
    public function crunch( $code ) {
32
        $out = "(function() {\n" . $code . "\n})();";
33
34
        $out = $this->replace_strings( $out );
35
        $out = $this->kill_comments( $out );
36
        $out = $this->compress_white_space( $out );
37
        $out = $this->combine_strings( $out );
38
        $out = $this->restore_strings( $out );
39
        $out = $this->encodeURIComponent( $out );
40
        $out = 'javascript:' . $out;
41
42
        return $out;
43
    }
44
    
45
    /**
46
     * PHP port of Javascript function encodeURIComponent
47
     *
48
     * From http://stackoverflow.com/a/1734255/36850
49
     *
50
     * @since
51
     * @param  string $str  String to encode
52
     * @return string       Encoded string
53
     */
54
    // 
55
    private function encodeURIComponent( $str ) {
56
        $revert = array(
57
            '%21'=>'!', '%2A'=>'*', '%28'=>'(', '%29'=>')',
58
        );
59
    
60
        return strtr( rawurlencode( $str ), $revert );
61
    }
62
63
    /**
64
     * Kill comment lines and blocks
65
     *
66
     * @param  string $code  Commented Javascript code
67
     * @return string        Commentless code
68
     */
69
    private function kill_comments( $code ) {
70
        $code = preg_replace( '!\s*//.+$!m', '', $code );
71
        $code = preg_replace( '!/\*.+?\*/!sm', '', $code ); // s modifier: dot matches new lines
72
        
73
        return $code;
74
    }
75
    
76
    /**
77
     * Compress white space
78
     *
79
     * Remove some extraneous spaces and make the whole script a one liner
80
     *
81
     * @param  string $code  Javascript code
82
     * @return string        Compressed code
83
     */
84
    private function compress_white_space( $code ) {
85
        // Tabs to space, no more than 1 consecutive space
86
        $code = preg_replace( '!\t!m', ' ', $code );
87
        $code = preg_replace( '![ ]{2,}!m', ' ', $code );
88
        
89
        // Remove uneccessary white space around operators, braces and brackets.
90
        // \xHH sequence is: !%&()*+,-/:;<=>?[]\{|}~
91
        $code = preg_replace( '/\s([\x21\x25\x26\x28\x29\x2a\x2b\x2c\x2d\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5d\x5c\x7b\x7c\x7d\x7e])/m', "$1", $code );
92
        $code = preg_replace( '/([\x21\x25\x26\x28\x29\x2a\x2b\x2c\x2d\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5d\x5c\x7b\x7c\x7d\x7e])\s/m', "$1", $code );
93
        
94
        // Split on each line, trim leading/trailing white space, kill empty lines, combine everything in one line
95
        $code = preg_split( '/\r\n|\r|\n/', $code );
96
        foreach( $code as $i => $line ) {
97
            $code[ $i ] = trim( $line );
98
        }
99
        $code = implode( '', $code );
100
101
        return $code;
102
    }
103
    
104
    /**
105
     * Combine any consecutive strings
106
     *
107
     * In the case we have two consecutive quoted strings (eg: "hello" + "world"), save a couple more
108
     * length and combine them
109
     *
110
     * @param  string $code  Javascript code
111
     * @return string        Javascript code
112
     */
113
    private function combine_strings( $code ) {
114
        $code = preg_replace('/"\+"/m', "", $code);
115
        $code = preg_replace("/'\+'/m", "", $code);
116
117
        return $code;
118
    }
119
120
    
121
    /**
122
     * Replace all literal strings (eg: "hello world") with a placeholder and collect them in an array
123
     *
124
     * The idea is that strings cannot be trimmed or white-space optimized: take them out first before uglifying
125
     * the code, then we'll reinject them back in later
126
     *
127
     * @param  string $code  Javascript code
128
     * @return string        Javascript code with placeholders (eg "__1__") instead of literal strings
129
     */
130
    private function replace_strings( $code ) {
131
        $return    = "";
132
        $literal   = "";
133
        $quoteChar = "";
134
        $escaped   = false;
135
136
        // Split script into individual lines.
137
        $lines = explode("\n", $code);
138
        $count = count( $lines );
139
        for( $i = 0; $i < $count; $i++ ) {
140
141
            $j = 0;
142
            $inQuote = false;
143
            while ($j < strlen( $lines[$i] ) ) {
144
                $c = $lines[ $i ][ $j ];
145
146
                // If not already in a string, look for the start of one.
147
                if (!$inQuote) {
148
                    if ($c == '"' || $c == "'") {
149
                        $inQuote = true;
150
                        $escaped = false;
151
                        $quoteChar = $c;
152
                        $literal = $c;
153
                    }
154
                    else {
155
                        $return .= $c;
156
                    }
157
                }
158
159
                // Already in a string, look for end and copy characters.
160
                else {
161
                    if ($c == $quoteChar && !$escaped) {
162
                        $inQuote = false;
163
                        $literal .= $quoteChar;
164
                        $return .= "__" . count( $this->literal_strings ) . "__";
165
                        $this->literal_strings[ count( $this->literal_strings ) ] = $literal;
166
                    }
167
                    else if ($c == "\\" && !$escaped) {
168
                        $escaped = true;
169
                    }
170
                    else {
171
                        $escaped = false;
172
                    }
173
                    $literal .= $c;
174
                }
175
                $j++;
176
            }
177
            $return .= "\n";
178
        }
179
180
        return $return;
181
    }
182
    
183
    /**
184
     * Restore literal strings by replacing their placeholders with actual strings
185
     *
186
     * @param  string $code  Javascript code with placeholders
187
     * @return string        Javascript code with actual strings
188
     */
189
    private function restore_strings( $code ) {
190
        foreach( $this->literal_strings as $i => $string ) {
191
            $code = preg_replace( '/__' . $i . '__/', $string, $code, 1 );
192
        }
193
        
194
        return $code;
195
    }
196
197
}
198