@@ -79,7 +79,7 @@ exports.JSON = async function(cssText) {
7979 }
8080
8181 function peek ( ) {
82- return cleanedCSS [ index ] ;
82+ return index < length ? cleanedCSS [ index ] : null ;
8383 }
8484
8585 function consume ( ) {
@@ -98,14 +98,15 @@ exports.JSON = async function(cssText) {
9898
9999 if ( char === '}' ) {
100100 index ++ ;
101- if ( stack . length > 0 ) stack . pop ( ) ;
101+ if ( stack . length > 0 ) stack . pop ( ) ;
102102 continue ;
103103 }
104104
105105 if ( char === '@' ) {
106106 rules . push ( parseAtRuleV2 ( ) ) ;
107107 } else {
108- rules . push ( parseSelectorOrNested ( ) ) ;
108+ const rule = parseSelectorOrNested ( ) ;
109+ if ( rule ) rules . push ( rule ) ;
109110 }
110111 }
111112
@@ -116,7 +117,7 @@ exports.JSON = async function(cssText) {
116117 }
117118 const atRuleHeader = cleanedCSS . slice ( start , index ) . trim ( ) ;
118119
119- if ( cleanedCSS [ index ] === '{' ) {
120+ if ( index < length && cleanedCSS [ index ] === '{' ) {
120121 index ++ ;
121122 const nestedRules = [ ] ;
122123 stack . push ( { type : 'at-rule' , name : atRuleHeader , rules : nestedRules , id : ruleid ++ } ) ;
@@ -127,118 +128,201 @@ exports.JSON = async function(cssText) {
127128 index ++ ;
128129 break ;
129130 }
130- nestedRules . push ( parseSelectorOrNested ( ) ) ;
131+ if ( peek ( ) === '@' ) {
132+ nestedRules . push ( parseAtRuleV2 ( ) ) ;
133+ } else {
134+ const rule = parseSelectorOrNested ( ) ;
135+ if ( rule ) nestedRules . push ( rule ) ;
136+ }
131137 }
132138
133139 return { type : 'at-rule' , name : atRuleHeader , rules : nestedRules , id : ruleid ++ } ;
134140
135- } else if ( cleanedCSS [ index ] === ';' ) {
141+ } else if ( index < length && cleanedCSS [ index ] === ';' ) {
136142 index ++ ;
137143 return { type : 'at-rule' , name : atRuleHeader , rules : [ ] , id : ruleid ++ } ;
138144 }
139145
140146 return { type : 'at-rule' , name : atRuleHeader , rules : [ ] , id : ruleid ++ } ;
141147 }
148+
142149 function parseAtRuleV2 ( ) {
143150 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 ;
153166 }
154- return { type : 'insert' , text }
155- }
156- function insert2 ( ) {
157- let reading = true ;
158- let readingString = false ;
159- let quoteChar = null ;
160167
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 ;
177199 }
200+ } else if ( char === ';' && braceCount === 0 ) {
201+ text += char ;
202+ index ++ ;
178203 break ;
179204 }
180-
181- text += cleanedCSS [ index ++ ] ;
182205 }
183- return { type : 'insert' , text } ;
206+
207+ text += char ;
208+ index ++ ;
184209 }
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 ] ;
198224
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 ;
216267 }
217- return { type : 'insert' , text } ;
218268 }
219- } else {
220- return parseAtRule ( ) ;
269+
270+ value += char ;
271+ index ++ ;
221272 }
273+
274+ return value . trim ( ) ;
222275 }
223276
224277 function parseSelectorOrNested ( ) {
225278 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+
231315 index ++ ;
232316 }
233317
234318 const selectorText = cleanedCSS . slice ( start , index ) . trim ( ) ;
235319
236- if ( cleanedCSS [ index ] === '{' ) {
320+ if ( index < length && cleanedCSS [ index ] === '{' ) {
237321 index ++ ;
238322
239323 skipWhitespace ( ) ;
240324
241- if ( / ^ [ . # a - z A - Z 0 - 9 \- \s , : ( ) % * \+ < > _ \[ \] = " ] + $ / . test ( selectorText ) ) {
325+ if ( selectorText ) {
242326 const selectors = selectorText . split ( ',' ) . map ( s => s . trim ( ) ) ;
243327 let properties = [ ] ;
244328 const nestedRules = [ ] ;
@@ -256,38 +340,39 @@ exports.JSON = async function(cssText) {
256340 }
257341
258342 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 ] !== '}' ) {
264344 index ++ ;
265345 }
266346
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 ++ ;
275364 } else if ( cleanedCSS [ index ] === '}' ) {
276365 index ++ ;
277366 break ;
278367 }
279368 }
280369
281370 return { type : 'rule' , selectors, properties, nestedRules, id : ruleid ++ } ;
282-
283- } else {
284- return null ;
285371 }
286-
287- } else {
288- return null ;
289372 }
373+
374+ return null ;
290375 }
291376
292- return rules . filter ( Boolean ) . sort ( ( a , b ) => a . id - b . id ) ; ;
377+ return rules . filter ( Boolean ) . sort ( ( a , b ) => a . id - b . id ) ;
293378}
0 commit comments