@@ -30,49 +30,59 @@ class JSStrip
30
30
*/
31
31
const OPS = "+-/ " ;
32
32
33
+ protected $ source ;
34
+ protected $ idx = 0 ;
35
+ protected $ line = 0 ;
36
+
33
37
/**
34
38
* Compress the given code
35
- *
39
+ *
36
40
* @param string $source The JavaScript code to compress
37
41
* @return string
42
+ * @throws Exception if parsing fails
38
43
*/
39
44
function compress ($ source )
40
45
{
41
46
$ source = ltrim ($ source ); // strip all initial whitespace
42
47
$ source .= "\n" ;
43
- $ i = 0 ; // char index for input string
48
+ $ idx = 0 ; // char index for input string
49
+
50
+ // track these as member variables
51
+ $ this ->source = $ source ;
52
+ $ this ->line = 1 ;
53
+ $ this ->idx = &$ idx ;
54
+
44
55
$ j = 0 ; // char forward index for input string
45
- $ line = 0 ; // line number of file (close to it anyways)
46
56
$ slen = strlen ($ source ); // size of input string
47
57
$ lch = '' ; // last char added
48
58
$ result = '' ; // we store the final result here
49
59
50
60
51
- while ($ i < $ slen ) {
61
+ while ($ idx < $ slen ) {
52
62
// skip all "boring" characters. This is either
53
63
// reserved word (e.g. "for", "else", "if") or a
54
64
// variable/object/method (e.g. "foo.color")
55
- while ($ i < $ slen && (strpos (self ::CHARS , $ source [$ i ]) === false )) {
56
- $ result .= $ source [$ i ];
57
- $ i = $ i + 1 ;
65
+ while ($ idx < $ slen && (strpos (self ::CHARS , $ source [$ idx ]) === false )) {
66
+ $ result .= $ source [$ idx ];
67
+ $ idx = $ idx + 1 ;
58
68
}
59
69
60
- $ ch = $ source [$ i ];
70
+ $ ch = $ source [$ idx ];
61
71
// multiline comments (keeping IE conditionals)
62
- if ($ ch == '/ ' && $ source [$ i + 1 ] == '* ' && $ source [$ i + 2 ] != '@ ' ) {
63
- $ endC = strpos ($ source , '*/ ' , $ i + 2 );
64
- if ($ endC === false ) trigger_error ('Found invalid /*..*/ comment ' , E_USER_ERROR );
72
+ if ($ ch == '/ ' && $ source [$ idx + 1 ] == '* ' && $ source [$ idx + 2 ] != '@ ' ) {
73
+ $ endC = strpos ($ source , '*/ ' , $ idx + 2 );
74
+ if ($ endC === false ) $ this -> fatal ('Found invalid /*..*/ comment ' );
65
75
66
76
// check if this is a NOCOMPRESS comment
67
- if (substr ($ source , $ i , $ endC + 2 - $ i ) == '/* BEGIN NOCOMPRESS */ ' ) {
77
+ if (substr ($ source , $ idx , $ endC + 2 - $ idx ) == '/* BEGIN NOCOMPRESS */ ' ) {
68
78
// take nested NOCOMPRESS comments into account
69
79
$ depth = 0 ;
70
80
$ nextNC = $ endC ;
71
81
do {
72
82
$ beginNC = strpos ($ source , '/* BEGIN NOCOMPRESS */ ' , $ nextNC + 2 );
73
83
$ endNC = strpos ($ source , '/* END NOCOMPRESS */ ' , $ nextNC + 2 );
74
84
75
- if ($ endNC === false ) trigger_error ('Found invalid NOCOMPRESS comment ' , E_USER_ERROR );
85
+ if ($ endNC === false ) $ this -> fatal ('Found invalid NOCOMPRESS comment ' );
76
86
if ($ beginNC !== false && $ beginNC < $ endNC ) {
77
87
$ depth ++;
78
88
$ nextNC = $ beginNC ;
@@ -83,34 +93,34 @@ function compress($source)
83
93
} while ($ depth >= 0 );
84
94
85
95
// verbatim copy contents, trimming but putting it on its own line
86
- $ result .= "\n" . trim (substr ($ source , $ i + 22 , $ endNC - ($ i + 22 ))) . "\n" ; // BEGIN comment = 22 chars
87
- $ i = $ endNC + 20 ; // END comment = 20 chars
96
+ $ result .= "\n" . trim (substr ($ source , $ idx + 22 , $ endNC - ($ idx + 22 ))) . "\n" ; // BEGIN comment = 22 chars
97
+ $ idx = $ endNC + 20 ; // END comment = 20 chars
88
98
} else {
89
- $ i = $ endC + 2 ;
99
+ $ idx = $ endC + 2 ;
90
100
}
91
101
continue ;
92
102
}
93
103
94
104
// singleline
95
- if ($ ch == '/ ' && $ source [$ i + 1 ] == '/ ' ) {
96
- $ endC = strpos ($ source , "\n" , $ i + 2 );
97
- if ($ endC === false ) trigger_error ('Invalid comment ' , E_USER_ERROR );
98
- $ i = $ endC ;
105
+ if ($ ch == '/ ' && $ source [$ idx + 1 ] == '/ ' ) {
106
+ $ endC = strpos ($ source , "\n" , $ idx + 2 );
107
+ if ($ endC === false ) $ this -> fatal ('Invalid comment ' ); // not sure this can happen
108
+ $ idx = $ endC ;
99
109
continue ;
100
110
}
101
111
102
112
// tricky. might be an RE
103
113
if ($ ch == '/ ' ) {
104
114
// rewind, skip white space
105
115
$ j = 1 ;
106
- while (in_array ($ source [$ i - $ j ], self ::WHITESPACE_CHARS )) {
116
+ while (in_array ($ source [$ idx - $ j ], self ::WHITESPACE_CHARS )) {
107
117
$ j = $ j + 1 ;
108
118
}
109
119
if (current (array_filter (
110
120
self ::REGEX_STARTERS ,
111
- function ($ e ) use ($ source , $ i , $ j ) {
121
+ function ($ e ) use ($ source , $ idx , $ j ) {
112
122
$ len = strlen ($ e );
113
- $ idx = $ i - $ j + 1 - $ len ;
123
+ $ idx = $ idx - $ j + 1 - $ len ;
114
124
return substr ($ source , $ idx , $ len ) === $ e ;
115
125
}
116
126
))) {
@@ -119,94 +129,95 @@ function ($e) use ($source, $i, $j) {
119
129
$ j = 1 ;
120
130
// we set this flag when inside a character class definition, enclosed by brackets [] where '/' does not terminate the re
121
131
$ ccd = false ;
122
- while ($ ccd || $ source [$ i + $ j ] != '/ ' ) {
123
- if ($ source [$ i + $ j ] == '\\' ) $ j = $ j + 2 ;
132
+ while ($ ccd || $ source [$ idx + $ j ] != '/ ' ) {
133
+ if ($ source [$ idx + $ j ] == '\\' ) $ j = $ j + 2 ;
124
134
else {
125
135
$ j ++;
126
136
// check if we entered/exited a character class definition and set flag accordingly
127
- if ($ source [$ i + $ j - 1 ] == '[ ' ) $ ccd = true ;
128
- else if ($ source [$ i + $ j - 1 ] == '] ' ) $ ccd = false ;
137
+ if ($ source [$ idx + $ j - 1 ] == '[ ' ) $ ccd = true ;
138
+ else if ($ source [$ idx + $ j - 1 ] == '] ' ) $ ccd = false ;
129
139
}
130
140
}
131
- $ result .= substr ($ source , $ i , $ j + 1 );
132
- $ i = $ i + $ j + 1 ;
141
+ $ result .= substr ($ source , $ idx , $ j + 1 );
142
+ $ idx = $ idx + $ j + 1 ;
133
143
continue ;
134
144
}
135
145
}
136
146
137
147
// double quote strings
138
148
if ($ ch == '" ' ) {
139
149
$ j = 1 ;
140
- while (($ i + $ j < $ slen ) && $ source [$ i + $ j ] != '" ' ) {
141
- if ($ source [$ i + $ j ] == '\\' && ($ source [$ i + $ j + 1 ] == '" ' || $ source [$ i + $ j + 1 ] == '\\' )) {
150
+ while (($ idx + $ j < $ slen ) && $ source [$ idx + $ j ] != '" ' ) {
151
+ if ($ source [$ idx + $ j ] == '\\' && ($ source [$ idx + $ j + 1 ] == '" ' || $ source [$ idx + $ j + 1 ] == '\\' )) {
142
152
$ j += 2 ;
143
153
} else {
144
154
$ j += 1 ;
145
155
}
146
156
}
147
- $ string = substr ($ source , $ i , $ j + 1 );
157
+ $ string = substr ($ source , $ idx , $ j + 1 );
148
158
// remove multiline markers:
149
159
$ string = str_replace ("\\\n" , '' , $ string );
150
160
$ result .= $ string ;
151
- $ i = $ i + $ j + 1 ;
161
+ $ idx = $ idx + $ j + 1 ;
152
162
continue ;
153
163
}
154
164
155
165
// single quote strings
156
166
if ($ ch == "' " ) {
157
167
$ j = 1 ;
158
- while (($ i + $ j < $ slen ) && $ source [$ i + $ j ] != "' " ) {
159
- if ($ source [$ i + $ j ] == '\\' && ($ source [$ i + $ j + 1 ] == "' " || $ source [$ i + $ j + 1 ] == '\\' )) {
168
+ while (($ idx + $ j < $ slen ) && $ source [$ idx + $ j ] != "' " ) {
169
+ if ($ source [$ idx + $ j ] == '\\' && ($ source [$ idx + $ j + 1 ] == "' " || $ source [$ idx + $ j + 1 ] == '\\' )) {
160
170
$ j += 2 ;
161
171
} else {
162
172
$ j += 1 ;
163
173
}
164
174
}
165
- $ string = substr ($ source , $ i , $ j + 1 );
175
+ $ string = substr ($ source , $ idx , $ j + 1 );
166
176
// remove multiline markers:
167
177
$ string = str_replace ("\\\n" , '' , $ string );
168
178
$ result .= $ string ;
169
- $ i = $ i + $ j + 1 ;
179
+ $ idx = $ idx + $ j + 1 ;
170
180
continue ;
171
181
}
172
182
173
183
// backtick strings
174
184
if ($ ch == "` " ) {
175
185
$ j = 1 ;
176
- while (($ i + $ j < $ slen ) && $ source [$ i + $ j ] != "` " ) {
177
- if ($ source [$ i + $ j ] == '\\' && ($ source [$ i + $ j + 1 ] == "` " || $ source [$ i + $ j + 1 ] == '\\' )) {
186
+ while (($ idx + $ j < $ slen ) && $ source [$ idx + $ j ] != "` " ) {
187
+ if ($ source [$ idx + $ j ] == '\\' && ($ source [$ idx + $ j + 1 ] == "` " || $ source [$ idx + $ j + 1 ] == '\\' )) {
178
188
$ j += 2 ;
179
189
} else {
180
190
$ j += 1 ;
181
191
}
182
192
}
183
- $ string = substr ($ source , $ i , $ j + 1 );
193
+ $ string = substr ($ source , $ idx , $ j + 1 );
184
194
// remove multiline markers:
185
195
$ string = str_replace ("\\\n" , '' , $ string );
186
196
$ result .= $ string ;
187
- $ i = $ i + $ j + 1 ;
197
+ $ idx = $ idx + $ j + 1 ;
188
198
continue ;
189
199
}
190
200
191
201
// whitespaces
192
202
if ($ ch == ' ' || $ ch == "\r" || $ ch == "\n" || $ ch == "\t" ) {
193
203
$ lch = substr ($ result , -1 );
204
+ if ($ ch == "\n" ) $ this ->line ++;
194
205
195
206
// Only consider deleting whitespace if the signs before and after
196
207
// are not equal and are not an operator which may not follow itself.
197
- if ($ i + 1 < $ slen && ((!$ lch || $ source [$ i + 1 ] == ' ' )
198
- || $ lch != $ source [$ i + 1 ]
199
- || strpos (self ::OPS , $ source [$ i + 1 ]) === false )) {
208
+ if ($ idx + 1 < $ slen && ((!$ lch || $ source [$ idx + 1 ] == ' ' )
209
+ || $ lch != $ source [$ idx + 1 ]
210
+ || strpos (self ::OPS , $ source [$ idx + 1 ]) === false )) {
200
211
// leading spaces
201
- if ($ i + 1 < $ slen && (strpos (self ::CHARS , $ source [$ i + 1 ]) !== false )) {
202
- $ i = $ i + 1 ;
212
+ if ($ idx + 1 < $ slen && (strpos (self ::CHARS , $ source [$ idx + 1 ]) !== false )) {
213
+ $ idx = $ idx + 1 ;
203
214
continue ;
204
215
}
205
216
// trailing spaces
206
217
// if this ch is space AND the last char processed
207
218
// is special, then skip the space
208
219
if ($ lch && (strpos (self ::CHARS , $ lch ) !== false )) {
209
- $ i = $ i + 1 ;
220
+ $ idx = $ idx + 1 ;
210
221
continue ;
211
222
}
212
223
}
@@ -218,9 +229,26 @@ function ($e) use ($source, $i, $j) {
218
229
219
230
// other chars
220
231
$ result .= $ ch ;
221
- $ i = $ i + 1 ;
232
+ $ idx = $ idx + 1 ;
222
233
}
223
234
224
235
return trim ($ result );
225
236
}
237
+
238
+ /**
239
+ * Helper to throw a fatal error
240
+ *
241
+ * Tries to give some context to locate the error
242
+ *
243
+ * @param string $msg
244
+ * @throws Exception
245
+ */
246
+ protected function fatal ($ msg )
247
+ {
248
+ $ before = substr ($ this ->source , max (0 , $ this ->idx - 15 ), $ this ->idx );
249
+ $ after = substr ($ this ->source , $ this ->idx , 15 );
250
+
251
+ $ msg = "$ msg on line {$ this ->line }: ' {$ before }◎ {$ after }' " ;
252
+ throw new Exception ($ msg );
253
+ }
226
254
}
0 commit comments