@@ -79,7 +79,7 @@ exports.JSON = async function(cssText) {
79
79
}
80
80
81
81
function peek ( ) {
82
- return cleanedCSS [ index ] ;
82
+ return index < length ? cleanedCSS [ index ] : null ;
83
83
}
84
84
85
85
function consume ( ) {
@@ -98,14 +98,15 @@ exports.JSON = async function(cssText) {
98
98
99
99
if ( char === '}' ) {
100
100
index ++ ;
101
- if ( stack . length > 0 ) stack . pop ( ) ;
101
+ if ( stack . length > 0 ) stack . pop ( ) ;
102
102
continue ;
103
103
}
104
104
105
105
if ( char === '@' ) {
106
106
rules . push ( parseAtRuleV2 ( ) ) ;
107
107
} else {
108
- rules . push ( parseSelectorOrNested ( ) ) ;
108
+ const rule = parseSelectorOrNested ( ) ;
109
+ if ( rule ) rules . push ( rule ) ;
109
110
}
110
111
}
111
112
@@ -116,7 +117,7 @@ exports.JSON = async function(cssText) {
116
117
}
117
118
const atRuleHeader = cleanedCSS . slice ( start , index ) . trim ( ) ;
118
119
119
- if ( cleanedCSS [ index ] === '{' ) {
120
+ if ( index < length && cleanedCSS [ index ] === '{' ) {
120
121
index ++ ;
121
122
const nestedRules = [ ] ;
122
123
stack . push ( { type : 'at-rule' , name : atRuleHeader , rules : nestedRules , id : ruleid ++ } ) ;
@@ -127,118 +128,201 @@ exports.JSON = async function(cssText) {
127
128
index ++ ;
128
129
break ;
129
130
}
130
- nestedRules . push ( parseSelectorOrNested ( ) ) ;
131
+ if ( peek ( ) === '@' ) {
132
+ nestedRules . push ( parseAtRuleV2 ( ) ) ;
133
+ } else {
134
+ const rule = parseSelectorOrNested ( ) ;
135
+ if ( rule ) nestedRules . push ( rule ) ;
136
+ }
131
137
}
132
138
133
139
return { type : 'at-rule' , name : atRuleHeader , rules : nestedRules , id : ruleid ++ } ;
134
140
135
- } else if ( cleanedCSS [ index ] === ';' ) {
141
+ } else if ( index < length && cleanedCSS [ index ] === ';' ) {
136
142
index ++ ;
137
143
return { type : 'at-rule' , name : atRuleHeader , rules : [ ] , id : ruleid ++ } ;
138
144
}
139
145
140
146
return { type : 'at-rule' , name : atRuleHeader , rules : [ ] , id : ruleid ++ } ;
141
147
}
148
+
142
149
function parseAtRuleV2 ( ) {
143
150
let text = '' ;
144
- function insert ( ) {
145
- let reading = true ;
146
- let readingValue = false ;
147
- while ( reading ) {
148
- if ( ! readingValue ) { skipWhitespace ( ) } ;
149
- if ( peek ( ) === ':' ) { readingValue = true }
150
- else if ( peek ( ) === ';' ) { readingValue = false }
151
- else if ( peek ( ) === '}' ) { reading = false } ;
152
- text += cleanedCSS [ index ++ ] ;
151
+ const startIndex = index ;
152
+
153
+ let braceCount = 0 ;
154
+ let inString = false ;
155
+ let stringChar = null ;
156
+ let escaped = false ;
157
+
158
+ while ( index < length ) {
159
+ const char = cleanedCSS [ index ] ;
160
+
161
+ if ( escaped ) {
162
+ escaped = false ;
163
+ text += char ;
164
+ index ++ ;
165
+ continue ;
153
166
}
154
- return { type : 'insert' , text }
155
- }
156
- function insert2 ( ) {
157
- let reading = true ;
158
- let readingString = false ;
159
- let quoteChar = null ;
160
167
161
- while ( reading && index < length ) {
162
- const currentChar = peek ( ) ;
163
-
164
- if ( ( currentChar === '"' || currentChar === "'" ) && cleanedCSS [ index - 1 ] !== '\\' ) {
165
- if ( ! readingString ) {
166
- readingString = true ;
167
- quoteChar = currentChar ;
168
- } else if ( currentChar === quoteChar ) {
169
- readingString = false ;
170
- }
171
- }
172
-
173
- if ( ! readingString && ( currentChar === ';' || currentChar === '}' ) ) {
174
- reading = false ;
175
- if ( currentChar === ';' ) {
176
- text += cleanedCSS [ index ++ ] ;
168
+ if ( char === '\\' ) {
169
+ escaped = true ;
170
+ text += char ;
171
+ index ++ ;
172
+ continue ;
173
+ }
174
+
175
+ if ( ( char === '"' || char === "'" ) && ! inString ) {
176
+ inString = true ;
177
+ stringChar = char ;
178
+ text += char ;
179
+ index ++ ;
180
+ continue ;
181
+ }
182
+
183
+ if ( inString && char === stringChar ) {
184
+ inString = false ;
185
+ text += char ;
186
+ index ++ ;
187
+ continue ;
188
+ }
189
+
190
+ if ( ! inString ) {
191
+ if ( char === '{' ) {
192
+ braceCount ++ ;
193
+ } else if ( char === '}' ) {
194
+ braceCount -- ;
195
+ if ( braceCount === 0 ) {
196
+ text += char ;
197
+ index ++ ;
198
+ break ;
177
199
}
200
+ } else if ( char === ';' && braceCount === 0 ) {
201
+ text += char ;
202
+ index ++ ;
178
203
break ;
179
204
}
180
-
181
- text += cleanedCSS [ index ++ ] ;
182
205
}
183
- return { type : 'insert' , text } ;
206
+
207
+ text += char ;
208
+ index ++ ;
184
209
}
185
- const nextChars = cleanedCSS . slice ( index + 1 , index + 10 ) ;
186
-
187
- if ( nextChars . startsWith ( 'property' ) ) {
188
- index += 9 ;
189
- text = '@property ' ;
190
- return insert ( ) ;
191
- } else if ( nextChars . startsWith ( 'font-face' ) ) {
192
- index += 10 ;
193
- text = '@font-face ' ;
194
- return insert ( ) ;
195
- } else if ( nextChars . startsWith ( 'import' ) ) {
196
- index += 7 ;
197
- skipWhitespace ( ) ;
210
+
211
+ return { type : 'insert' , text : text . trim ( ) , id : ruleid ++ } ;
212
+ }
213
+
214
+ function parsePropertyValue ( ) {
215
+ let value = '' ;
216
+ let inString = false ;
217
+ let stringChar = null ;
218
+ let escaped = false ;
219
+ let parenCount = 0 ;
220
+ let bracketCount = 0 ;
221
+
222
+ while ( index < length ) {
223
+ const char = cleanedCSS [ index ] ;
198
224
199
- if ( cleanedCSS . slice ( index , index + 3 ) === 'url' ) {
200
- index += 3 ;
201
- text = '@import url' ;
202
- return insert2 ( ) ;
203
- } else {
204
- text = '@import ' ;
205
- let reading = true ;
206
- while ( reading && index < length ) {
207
- const currentChar = peek ( ) ;
208
- if ( currentChar === ';' || currentChar === '}' ) {
209
- reading = false ;
210
- if ( currentChar === ';' ) {
211
- text += cleanedCSS [ index ++ ] ;
212
- }
213
- break ;
214
- }
215
- text += cleanedCSS [ index ++ ] ;
225
+ if ( escaped ) {
226
+ escaped = false ;
227
+ value += char ;
228
+ index ++ ;
229
+ continue ;
230
+ }
231
+
232
+ if ( char === '\\' ) {
233
+ escaped = true ;
234
+ value += char ;
235
+ index ++ ;
236
+ continue ;
237
+ }
238
+
239
+ if ( ( char === '"' || char === "'" ) && ! inString ) {
240
+ inString = true ;
241
+ stringChar = char ;
242
+ value += char ;
243
+ index ++ ;
244
+ continue ;
245
+ }
246
+
247
+ if ( inString && char === stringChar ) {
248
+ inString = false ;
249
+ value += char ;
250
+ index ++ ;
251
+ continue ;
252
+ }
253
+
254
+ if ( ! inString ) {
255
+ if ( char === '(' ) {
256
+ parenCount ++ ;
257
+ } else if ( char === ')' ) {
258
+ parenCount -- ;
259
+ } else if ( char === '[' ) {
260
+ bracketCount ++ ;
261
+ } else if ( char === ']' ) {
262
+ bracketCount -- ;
263
+ } else if ( char === ';' && parenCount === 0 && bracketCount === 0 ) {
264
+ break ;
265
+ } else if ( char === '}' && parenCount === 0 && bracketCount === 0 ) {
266
+ break ;
216
267
}
217
- return { type : 'insert' , text } ;
218
268
}
219
- } else {
220
- return parseAtRule ( ) ;
269
+
270
+ value += char ;
271
+ index ++ ;
221
272
}
273
+
274
+ return value . trim ( ) ;
222
275
}
223
276
224
277
function parseSelectorOrNested ( ) {
225
278
let start = index ;
226
- while (
227
- index < length &&
228
- cleanedCSS [ index ] !== '{' &&
229
- cleanedCSS [ index ] !== '}'
230
- ) {
279
+ let inString = false ;
280
+ let stringChar = null ;
281
+ let escaped = false ;
282
+
283
+ while ( index < length ) {
284
+ const char = cleanedCSS [ index ] ;
285
+
286
+ if ( escaped ) {
287
+ escaped = false ;
288
+ index ++ ;
289
+ continue ;
290
+ }
291
+
292
+ if ( char === '\\' ) {
293
+ escaped = true ;
294
+ index ++ ;
295
+ continue ;
296
+ }
297
+
298
+ if ( ( char === '"' || char === "'" ) && ! inString ) {
299
+ inString = true ;
300
+ stringChar = char ;
301
+ index ++ ;
302
+ continue ;
303
+ }
304
+
305
+ if ( inString && char === stringChar ) {
306
+ inString = false ;
307
+ index ++ ;
308
+ continue ;
309
+ }
310
+
311
+ if ( ! inString && ( char === '{' || char === '}' ) ) {
312
+ break ;
313
+ }
314
+
231
315
index ++ ;
232
316
}
233
317
234
318
const selectorText = cleanedCSS . slice ( start , index ) . trim ( ) ;
235
319
236
- if ( cleanedCSS [ index ] === '{' ) {
320
+ if ( index < length && cleanedCSS [ index ] === '{' ) {
237
321
index ++ ;
238
322
239
323
skipWhitespace ( ) ;
240
324
241
- if ( / ^ [ . # a - z A - Z 0 - 9 \- \s , : ( ) % * \+ < > _ \[ \] = " ] + $ / . test ( selectorText ) ) {
325
+ if ( selectorText ) {
242
326
const selectors = selectorText . split ( ',' ) . map ( s => s . trim ( ) ) ;
243
327
let properties = [ ] ;
244
328
const nestedRules = [ ] ;
@@ -256,38 +340,39 @@ exports.JSON = async function(cssText) {
256
340
}
257
341
258
342
let propStart = index ;
259
- while (
260
- index < length &&
261
- cleanedCSS [ index ] !== ';' &&
262
- cleanedCSS [ index ] !== '}'
263
- ) {
343
+ while ( index < length && cleanedCSS [ index ] !== ':' && cleanedCSS [ index ] !== ';' && cleanedCSS [ index ] !== '}' ) {
264
344
index ++ ;
265
345
}
266
346
267
- const line = cleanedCSS . slice ( propStart , index ) . trim ( ) ;
268
-
269
- if ( ! line ) break ;
270
-
271
- if ( line . includes ( ':' ) ) {
272
- const [ key , value ] = line . split ( ':' ) . map ( s => s . trim ( ) ) ;
273
- properties . push ( [ key , value ] ) ;
274
- if ( cleanedCSS [ index ] === ';' ) index ++ ;
347
+ if ( index >= length ) break ;
348
+
349
+ const key = cleanedCSS . slice ( propStart , index ) . trim ( ) ;
350
+
351
+ if ( cleanedCSS [ index ] === ':' ) {
352
+ index ++ ;
353
+ const value = parsePropertyValue ( ) ;
354
+
355
+ if ( key && value !== '' ) {
356
+ properties . push ( [ key , value ] ) ;
357
+ }
358
+
359
+ if ( index < length && cleanedCSS [ index ] === ';' ) {
360
+ index ++ ;
361
+ }
362
+ } else if ( cleanedCSS [ index ] === ';' ) {
363
+ index ++ ;
275
364
} else if ( cleanedCSS [ index ] === '}' ) {
276
365
index ++ ;
277
366
break ;
278
367
}
279
368
}
280
369
281
370
return { type : 'rule' , selectors, properties, nestedRules, id : ruleid ++ } ;
282
-
283
- } else {
284
- return null ;
285
371
}
286
-
287
- } else {
288
- return null ;
289
372
}
373
+
374
+ return null ;
290
375
}
291
376
292
- return rules . filter ( Boolean ) . sort ( ( a , b ) => a . id - b . id ) ; ;
377
+ return rules . filter ( Boolean ) . sort ( ( a , b ) => a . id - b . id ) ;
293
378
}
0 commit comments