@@ -19,6 +19,376 @@ export function pluginRunner(plugins) {
19
19
} ;
20
20
}
21
21
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
+
22
392
export function getFilter ( filter , defaultFilter = null ) {
23
393
return ( attribute , value , resourcePath ) => {
24
394
if ( defaultFilter && ! defaultFilter ( value ) ) {
0 commit comments