parse-srcset.js
10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/**
* Srcset Parser
*
* By Alex Bell | MIT License
*
* JS Parser for the string value that appears in markup <img srcset="here">
*
* @returns Array [{url: _, d: _, w: _, h:_}, ...]
*
* Based super duper closely on the reference algorithm at:
* https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-srcset-attribute
*
* Most comments are copied in directly from the spec
* (except for comments in parens).
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.parseSrcset = factory();
}
}(this, function () {
// 1. Let input be the value passed to this algorithm.
return function (input) {
// UTILITY FUNCTIONS
// Manual is faster than RegEx
// http://bjorn.tipling.com/state-and-regular-expressions-in-javascript
// http://jsperf.com/whitespace-character/5
function isSpace(c) {
return (c === "\u0020" || // space
c === "\u0009" || // horizontal tab
c === "\u000A" || // new line
c === "\u000C" || // form feed
c === "\u000D"); // carriage return
}
function collectCharacters(regEx) {
var chars,
match = regEx.exec(input.substring(pos));
if (match) {
chars = match[ 0 ];
pos += chars.length;
return chars;
}
}
var inputLength = input.length,
// (Don't use \s, to avoid matching non-breaking space)
regexLeadingSpaces = /^[ \t\n\r\u000c]+/,
regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/,
regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/,
regexTrailingCommas = /[,]+$/,
regexNonNegativeInteger = /^\d+$/,
// ( Positive or negative or unsigned integers or decimals, without or without exponents.
// Must include at least one digit.
// According to spec tests any decimal point must be followed by a digit.
// No leading plus sign is allowed.)
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/,
url,
descriptors,
currentDescriptor,
state,
c,
// 2. Let position be a pointer into input, initially pointing at the start
// of the string.
pos = 0,
// 3. Let candidates be an initially empty source set.
candidates = [];
// 4. Splitting loop: Collect a sequence of characters that are space
// characters or U+002C COMMA characters. If any U+002C COMMA characters
// were collected, that is a parse error.
while (true) {
collectCharacters(regexLeadingCommasOrSpaces);
// 5. If position is past the end of input, return candidates and abort these steps.
if (pos >= inputLength) {
return candidates; // (we're done, this is the sole return path)
}
// 6. Collect a sequence of characters that are not space characters,
// and let that be url.
url = collectCharacters(regexLeadingNotSpaces);
// 7. Let descriptors be a new empty list.
descriptors = [];
// 8. If url ends with a U+002C COMMA character (,), follow these substeps:
// (1). Remove all trailing U+002C COMMA characters from url. If this removed
// more than one character, that is a parse error.
if (url.slice(-1) === ",") {
url = url.replace(regexTrailingCommas, "");
// (Jump ahead to step 9 to skip tokenization and just push the candidate).
parseDescriptors();
// Otherwise, follow these substeps:
} else {
tokenize();
} // (close else of step 8)
// 16. Return to the step labeled splitting loop.
} // (Close of big while loop.)
/**
* Tokenizes descriptor properties prior to parsing
* Returns undefined.
*/
function tokenize() {
// 8.1. Descriptor tokeniser: Skip whitespace
collectCharacters(regexLeadingSpaces);
// 8.2. Let current descriptor be the empty string.
currentDescriptor = "";
// 8.3. Let state be in descriptor.
state = "in descriptor";
while (true) {
// 8.4. Let c be the character at position.
c = input.charAt(pos);
// Do the following depending on the value of state.
// For the purpose of this step, "EOF" is a special character representing
// that position is past the end of input.
// In descriptor
if (state === "in descriptor") {
// Do the following, depending on the value of c:
// Space character
// If current descriptor is not empty, append current descriptor to
// descriptors and let current descriptor be the empty string.
// Set state to after descriptor.
if (isSpace(c)) {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
currentDescriptor = "";
state = "after descriptor";
}
// U+002C COMMA (,)
// Advance position to the next character in input. If current descriptor
// is not empty, append current descriptor to descriptors. Jump to the step
// labeled descriptor parser.
} else if (c === ",") {
pos += 1;
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return;
// U+0028 LEFT PARENTHESIS (()
// Append c to current descriptor. Set state to in parens.
} else if (c === "\u0028") {
currentDescriptor = currentDescriptor + c;
state = "in parens";
// EOF
// If current descriptor is not empty, append current descriptor to
// descriptors. Jump to the step labeled descriptor parser.
} else if (c === "") {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return;
// Anything else
// Append c to current descriptor.
} else {
currentDescriptor = currentDescriptor + c;
}
// (end "in descriptor"
// In parens
} else if (state === "in parens") {
// U+0029 RIGHT PARENTHESIS ())
// Append c to current descriptor. Set state to in descriptor.
if (c === ")") {
currentDescriptor = currentDescriptor + c;
state = "in descriptor";
// EOF
// Append current descriptor to descriptors. Jump to the step labeled
// descriptor parser.
} else if (c === "") {
descriptors.push(currentDescriptor);
parseDescriptors();
return;
// Anything else
// Append c to current descriptor.
} else {
currentDescriptor = currentDescriptor + c;
}
// After descriptor
} else if (state === "after descriptor") {
// Do the following, depending on the value of c:
// Space character: Stay in this state.
if (isSpace(c)) {
// EOF: Jump to the step labeled descriptor parser.
} else if (c === "") {
parseDescriptors();
return;
// Anything else
// Set state to in descriptor. Set position to the previous character in input.
} else {
state = "in descriptor";
pos -= 1;
}
}
// Advance position to the next character in input.
pos += 1;
// Repeat this step.
} // (close while true loop)
}
/**
* Adds descriptor properties to a candidate, pushes to the candidates array
* @return undefined
*/
// Declared outside of the while loop so that it's only created once.
function parseDescriptors() {
// 9. Descriptor parser: Let error be no.
var pError = false,
// 10. Let width be absent.
// 11. Let density be absent.
// 12. Let future-compat-h be absent. (We're implementing it now as h)
w, d, h, i,
candidate = {},
desc, lastChar, value, intVal, floatVal;
// 13. For each descriptor in descriptors, run the appropriate set of steps
// from the following list:
for (i = 0 ; i < descriptors.length; i++) {
desc = descriptors[ i ];
lastChar = desc[ desc.length - 1 ];
value = desc.substring(0, desc.length - 1);
intVal = parseInt(value, 10);
floatVal = parseFloat(value);
// If the descriptor consists of a valid non-negative integer followed by
// a U+0077 LATIN SMALL LETTER W character
if (regexNonNegativeInteger.test(value) && (lastChar === "w")) {
// If width and density are not both absent, then let error be yes.
if (w || d) {pError = true;}
// Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes.
// Otherwise, let width be the result.
if (intVal === 0) {pError = true;} else {w = intVal;}
// If the descriptor consists of a valid floating-point number followed by
// a U+0078 LATIN SMALL LETTER X character
} else if (regexFloatingPoint.test(value) && (lastChar === "x")) {
// If width, density and future-compat-h are not all absent, then let error
// be yes.
if (w || d || h) {pError = true;}
// Apply the rules for parsing floating-point number values to the descriptor.
// If the result is less than zero, let error be yes. Otherwise, let density
// be the result.
if (floatVal < 0) {pError = true;} else {d = floatVal;}
// If the descriptor consists of a valid non-negative integer followed by
// a U+0068 LATIN SMALL LETTER H character
} else if (regexNonNegativeInteger.test(value) && (lastChar === "h")) {
// If height and density are not both absent, then let error be yes.
if (h || d) {pError = true;}
// Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes. Otherwise, let future-compat-h
// be the result.
if (intVal === 0) {pError = true;} else {h = intVal;}
// Anything else, Let error be yes.
} else {pError = true;}
} // (close step 13 for loop)
// 15. If error is still no, then append a new image source to candidates whose
// URL is url, associated with a width width if not absent and a pixel
// density density if not absent. Otherwise, there is a parse error.
if (!pError) {
candidate.url = url;
if (w) { candidate.w = w;}
if (d) { candidate.d = d;}
if (h) { candidate.h = h;}
candidates.push(candidate);
} else if (console && console.log) {
console.log("Invalid srcset descriptor found in '" +
input + "' at '" + desc + "'.");
}
} // (close parseDescriptors fn)
}
}));