@@ -19,6 +19,376 @@ export function pluginRunner(plugins) {
1919 } ;
2020}
2121
22+ function isASCIIWhitespace ( character ) {
23+ return (
24+ // Horizontal tab
25+ character === '\u0009' ||
26+ // New line
27+ character === '\u000A' ||
28+ // Form feed
29+ character === '\u000C' ||
30+ // Carriage return
31+ character === '\u000D' ||
32+ // Space
33+ character === '\u0020'
34+ ) ;
35+ }
36+
37+ // (Don't use \s, to avoid matching non-breaking space)
38+ // eslint-disable-next-line no-control-regex
39+ const regexLeadingSpaces = / ^ [ \t \n \r \u000c ] + / ;
40+ // eslint-disable-next-line no-control-regex
41+ const regexLeadingCommasOrSpaces = / ^ [ , \t \n \r \u000c ] + / ;
42+ // eslint-disable-next-line no-control-regex
43+ const regexLeadingNotSpaces = / ^ [ ^ \t \n \r \u000c ] + / ;
44+ const regexTrailingCommas = / [ , ] + $ / ;
45+ const regexNonNegativeInteger = / ^ \d + $ / ;
46+
47+ // ( Positive or negative or unsigned integers or decimals, without or without exponents.
48+ // Must include at least one digit.
49+ // According to spec tests any decimal point must be followed by a digit.
50+ // No leading plus sign is allowed.)
51+ // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
52+ const regexFloatingPoint = / ^ - ? (?: [ 0 - 9 ] + | [ 0 - 9 ] * \. [ 0 - 9 ] + ) (?: [ e E ] [ + - ] ? [ 0 - 9 ] + ) ? $ / ;
53+
54+ export function parseSrcset ( input ) {
55+ // 1. Let input be the value passed to this algorithm.
56+ const inputLength = input . length ;
57+
58+ let url ;
59+ let descriptors ;
60+ let currentDescriptor ;
61+ let state ;
62+ let c ;
63+
64+ // 2. Let position be a pointer into input, initially pointing at the start
65+ // of the string.
66+ let position = 0 ;
67+ let startUrlPosition ;
68+
69+ // eslint-disable-next-line consistent-return
70+ function collectCharacters ( regEx ) {
71+ let chars ;
72+ const match = regEx . exec ( input . substring ( position ) ) ;
73+
74+ if ( match ) {
75+ [ chars ] = match ;
76+ position += chars . length ;
77+
78+ return chars ;
79+ }
80+ }
81+
82+ // 3. Let candidates be an initially empty source set.
83+ const candidates = [ ] ;
84+
85+ // 4. Splitting loop: Collect a sequence of characters that are space
86+ // characters or U+002C COMMA characters. If any U+002C COMMA characters
87+ // were collected, that is a parse error.
88+ // eslint-disable-next-line no-constant-condition
89+ while ( true ) {
90+ collectCharacters ( regexLeadingCommasOrSpaces ) ;
91+
92+ // 5. If position is past the end of input, return candidates and abort these steps.
93+ if ( position >= inputLength ) {
94+ if ( candidates . length === 0 ) {
95+ throw new Error ( 'Must contain one or more image candidate strings' ) ;
96+ }
97+
98+ // (we're done, this is the sole return path)
99+ return candidates ;
100+ }
101+
102+ // 6. Collect a sequence of characters that are not space characters,
103+ // and let that be url.
104+ startUrlPosition = position ;
105+ url = collectCharacters ( regexLeadingNotSpaces ) ;
106+
107+ // 7. Let descriptors be a new empty list.
108+ descriptors = [ ] ;
109+
110+ // 8. If url ends with a U+002C COMMA character (,), follow these substeps:
111+ // (1). Remove all trailing U+002C COMMA characters from url. If this removed
112+ // more than one character, that is a parse error.
113+ if ( url . slice ( - 1 ) === ',' ) {
114+ url = url . replace ( regexTrailingCommas , '' ) ;
115+
116+ // (Jump ahead to step 9 to skip tokenization and just push the candidate).
117+ parseDescriptors ( ) ;
118+ }
119+ // Otherwise, follow these substeps:
120+ else {
121+ tokenize ( ) ;
122+ }
123+
124+ // 16. Return to the step labeled splitting loop.
125+ }
126+
127+ /**
128+ * Tokenizes descriptor properties prior to parsing
129+ * Returns undefined.
130+ */
131+ function tokenize ( ) {
132+ // 8.1. Descriptor tokenizer: Skip whitespace
133+ collectCharacters ( regexLeadingSpaces ) ;
134+
135+ // 8.2. Let current descriptor be the empty string.
136+ currentDescriptor = '' ;
137+
138+ // 8.3. Let state be in descriptor.
139+ state = 'in descriptor' ;
140+
141+ // eslint-disable-next-line no-constant-condition
142+ while ( true ) {
143+ // 8.4. Let c be the character at position.
144+ c = input . charAt ( position ) ;
145+
146+ // Do the following depending on the value of state.
147+ // For the purpose of this step, "EOF" is a special character representing
148+ // that position is past the end of input.
149+
150+ // In descriptor
151+ if ( state === 'in descriptor' ) {
152+ // Do the following, depending on the value of c:
153+
154+ // Space character
155+ // If current descriptor is not empty, append current descriptor to
156+ // descriptors and let current descriptor be the empty string.
157+ // Set state to after descriptor.
158+ if ( isASCIIWhitespace ( c ) ) {
159+ if ( currentDescriptor ) {
160+ descriptors . push ( currentDescriptor ) ;
161+ currentDescriptor = '' ;
162+ state = 'after descriptor' ;
163+ }
164+ }
165+ // U+002C COMMA (,)
166+ // Advance position to the next character in input. If current descriptor
167+ // is not empty, append current descriptor to descriptors. Jump to the step
168+ // labeled descriptor parser.
169+ else if ( c === ',' ) {
170+ position += 1 ;
171+
172+ if ( currentDescriptor ) {
173+ descriptors . push ( currentDescriptor ) ;
174+ }
175+
176+ parseDescriptors ( ) ;
177+
178+ return ;
179+ }
180+ // U+0028 LEFT PARENTHESIS (()
181+ // Append c to current descriptor. Set state to in parens.
182+ else if ( c === '\u0028' ) {
183+ currentDescriptor += c ;
184+ state = 'in parens' ;
185+ }
186+ // EOF
187+ // If current descriptor is not empty, append current descriptor to
188+ // descriptors. Jump to the step labeled descriptor parser.
189+ else if ( c === '' ) {
190+ if ( currentDescriptor ) {
191+ descriptors . push ( currentDescriptor ) ;
192+ }
193+
194+ parseDescriptors ( ) ;
195+
196+ return ;
197+
198+ // Anything else
199+ // Append c to current descriptor.
200+ } else {
201+ currentDescriptor += c ;
202+ }
203+ }
204+ // In parens
205+ else if ( state === 'in parens' ) {
206+ // U+0029 RIGHT PARENTHESIS ())
207+ // Append c to current descriptor. Set state to in descriptor.
208+ if ( c === ')' ) {
209+ currentDescriptor += c ;
210+ state = 'in descriptor' ;
211+ }
212+ // EOF
213+ // Append current descriptor to descriptors. Jump to the step labeled
214+ // descriptor parser.
215+ else if ( c === '' ) {
216+ descriptors . push ( currentDescriptor ) ;
217+ parseDescriptors ( ) ;
218+ return ;
219+ }
220+ // Anything else
221+ // Append c to current descriptor.
222+ else {
223+ currentDescriptor += c ;
224+ }
225+ }
226+ // After descriptor
227+ else if ( state === 'after descriptor' ) {
228+ // Do the following, depending on the value of c:
229+ if ( isASCIIWhitespace ( c ) ) {
230+ // Space character: Stay in this state.
231+ }
232+ // EOF: Jump to the step labeled descriptor parser.
233+ else if ( c === '' ) {
234+ parseDescriptors ( ) ;
235+ return ;
236+ }
237+ // Anything else
238+ // Set state to in descriptor. Set position to the previous character in input.
239+ else {
240+ state = 'in descriptor' ;
241+ position -= 1 ;
242+ }
243+ }
244+
245+ // Advance position to the next character in input.
246+ position += 1 ;
247+ }
248+ }
249+
250+ /**
251+ * Adds descriptor properties to a candidate, pushes to the candidates array
252+ * @return undefined
253+ */
254+ // Declared outside of the while loop so that it's only created once.
255+ function parseDescriptors ( ) {
256+ // 9. Descriptor parser: Let error be no.
257+ let pError = false ;
258+
259+ // 10. Let width be absent.
260+ // 11. Let density be absent.
261+ // 12. Let future-compat-h be absent. (We're implementing it now as h)
262+ let w ;
263+ let d ;
264+ let h ;
265+ let i ;
266+ const candidate = { } ;
267+ let desc ;
268+ let lastChar ;
269+ let value ;
270+ let intVal ;
271+ let floatVal ;
272+
273+ // 13. For each descriptor in descriptors, run the appropriate set of steps
274+ // from the following list:
275+ for ( i = 0 ; i < descriptors . length ; i ++ ) {
276+ desc = descriptors [ i ] ;
277+
278+ lastChar = desc [ desc . length - 1 ] ;
279+ value = desc . substring ( 0 , desc . length - 1 ) ;
280+ intVal = parseInt ( value , 10 ) ;
281+ floatVal = parseFloat ( value ) ;
282+
283+ // If the descriptor consists of a valid non-negative integer followed by
284+ // a U+0077 LATIN SMALL LETTER W character
285+ if ( regexNonNegativeInteger . test ( value ) && lastChar === 'w' ) {
286+ // If width and density are not both absent, then let error be yes.
287+ if ( w || d ) {
288+ pError = true ;
289+ }
290+
291+ // Apply the rules for parsing non-negative integers to the descriptor.
292+ // If the result is zero, let error be yes.
293+ // Otherwise, let width be the result.
294+ if ( intVal === 0 ) {
295+ pError = true ;
296+ } else {
297+ w = intVal ;
298+ }
299+ }
300+ // If the descriptor consists of a valid floating-point number followed by
301+ // a U+0078 LATIN SMALL LETTER X character
302+ else if ( regexFloatingPoint . test ( value ) && lastChar === 'x' ) {
303+ // If width, density and future-compat-h are not all absent, then let error
304+ // be yes.
305+ if ( w || d || h ) {
306+ pError = true ;
307+ }
308+
309+ // Apply the rules for parsing floating-point number values to the descriptor.
310+ // If the result is less than zero, let error be yes. Otherwise, let density
311+ // be the result.
312+ if ( floatVal < 0 ) {
313+ pError = true ;
314+ } else {
315+ d = floatVal ;
316+ }
317+ }
318+ // If the descriptor consists of a valid non-negative integer followed by
319+ // a U+0068 LATIN SMALL LETTER H character
320+ else if ( regexNonNegativeInteger . test ( value ) && lastChar === 'h' ) {
321+ // If height and density are not both absent, then let error be yes.
322+ if ( h || d ) {
323+ pError = true ;
324+ }
325+
326+ // Apply the rules for parsing non-negative integers to the descriptor.
327+ // If the result is zero, let error be yes. Otherwise, let future-compat-h
328+ // be the result.
329+ if ( intVal === 0 ) {
330+ pError = true ;
331+ } else {
332+ h = intVal ;
333+ }
334+
335+ // Anything else, Let error be yes.
336+ } else {
337+ pError = true ;
338+ }
339+ }
340+
341+ // 15. If error is still no, then append a new image source to candidates whose
342+ // URL is url, associated with a width width if not absent and a pixel
343+ // density density if not absent. Otherwise, there is a parse error.
344+ if ( ! pError ) {
345+ candidate . source = { value : url , startIndex : startUrlPosition } ;
346+
347+ if ( w ) {
348+ candidate . width = { value : w } ;
349+ }
350+
351+ if ( d ) {
352+ candidate . density = { value : d } ;
353+ }
354+
355+ if ( h ) {
356+ candidate . height = { value : h } ;
357+ }
358+
359+ candidates . push ( candidate ) ;
360+ } else {
361+ throw new Error (
362+ `Invalid srcset descriptor found in '${ input } ' at '${ desc } '`
363+ ) ;
364+ }
365+ }
366+ }
367+
368+ export function parseSrc ( input ) {
369+ if ( ! input ) {
370+ throw new Error ( 'Must be non-empty' ) ;
371+ }
372+
373+ let startIndex = 0 ;
374+ let value = input ;
375+
376+ while ( isASCIIWhitespace ( value . substring ( 0 , 1 ) ) ) {
377+ startIndex += 1 ;
378+ value = value . substring ( 1 , value . length ) ;
379+ }
380+
381+ while ( isASCIIWhitespace ( value . substring ( value . length - 1 , value . length ) ) ) {
382+ value = value . substring ( 0 , value . length - 1 ) ;
383+ }
384+
385+ if ( ! value ) {
386+ throw new Error ( 'Must be non-empty' ) ;
387+ }
388+
389+ return { value, startIndex } ;
390+ }
391+
22392export function getFilter ( filter , defaultFilter = null ) {
23393 return ( attribute , value , resourcePath ) => {
24394 if ( defaultFilter && ! defaultFilter ( value ) ) {
0 commit comments