@@ -136,9 +136,11 @@ export function textContent(
136
136
continue ;
137
137
}
138
138
139
- const tagLink = checkTagLink ( data ) ;
140
- if ( tagLink ) {
141
- addRef ( tagLink ) ;
139
+ const tagLinks = checkTagLink ( data ) ;
140
+ if ( tagLinks . length ) {
141
+ for ( const tagLink of tagLinks ) {
142
+ addRef ( tagLink ) ;
143
+ }
142
144
continue ;
143
145
}
144
146
@@ -299,61 +301,150 @@ function checkReference(data: TextParserData): RelativeLink | undefined {
299
301
/**
300
302
* Looks for `<a href="./relative">`, `<img src="./relative">`, and `<source srcset="./relative">`
301
303
*/
302
- function checkTagLink ( data : TextParserData ) : RelativeLink | undefined {
304
+ function checkTagLink ( data : TextParserData ) : RelativeLink [ ] {
303
305
const { pos, token } = data ;
304
306
305
307
if ( token . text . startsWith ( "<img " , pos ) ) {
306
308
data . pos += 4 ;
307
- return checkAttribute ( data , "src" ) ;
309
+ return checkAttributes ( data , {
310
+ src : checkAttributeDirectPath ,
311
+ srcset : checkAttributeSrcSet ,
312
+ } ) ;
313
+ }
314
+
315
+ if ( token . text . startsWith ( "<link " , pos ) ) {
316
+ data . pos += 4 ;
317
+ return checkAttributes ( data , {
318
+ imagesrcset : checkAttributeSrcSet ,
319
+ } ) ;
308
320
}
309
321
310
322
if ( token . text . startsWith ( "<a " , pos ) ) {
311
323
data . pos += 3 ;
312
- return checkAttribute ( data , " href" ) ;
324
+ return checkAttributes ( data , { href : checkAttributeDirectPath } ) ;
313
325
}
314
326
315
327
if ( token . text . startsWith ( "<source " , pos ) ) {
316
328
data . pos += 8 ;
317
- const saveData = { ...data } ;
318
- const attr = checkAttribute ( data , "srcset" ) ;
319
- if ( ! attr ) {
320
- Object . assign ( data , saveData ) ;
321
- return checkAttribute ( data , "src" ) ;
322
- }
323
- return attr ;
329
+ return checkAttributes ( data , {
330
+ src : checkAttributeDirectPath ,
331
+ srcset : checkAttributeSrcSet ,
332
+ } ) ;
324
333
}
334
+
335
+ return [ ] ;
325
336
}
326
337
327
- function checkAttribute (
338
+ function checkAttributes (
328
339
data : TextParserData ,
329
- attr : string ,
330
- ) : RelativeLink | undefined {
340
+ attributes : Record <
341
+ string ,
342
+ ( data : TextParserData , text : string , pos : number , end : number ) => RelativeLink [ ]
343
+ > ,
344
+ ) : RelativeLink [ ] {
345
+ const links : RelativeLink [ ] = [ ] ;
331
346
const parser = new HtmlAttributeParser ( data . token . text , data . pos ) ;
332
347
while ( parser . state !== ParserState . END ) {
333
348
if (
334
349
parser . state === ParserState . BeforeAttributeValue &&
335
- parser . currentAttributeName === attr
350
+ attributes . hasOwnProperty ( parser . currentAttributeName )
336
351
) {
337
352
parser . step ( ) ;
338
353
339
- if ( isRelativePath ( parser . currentAttributeValue ) ) {
340
- data . pos = parser . pos ;
341
- const { target, anchor } = data . files . register (
342
- data . sourcePath ,
343
- parser . currentAttributeValue as NormalizedPath ,
344
- ) || { target : undefined , anchor : undefined } ;
345
- return {
346
- pos : parser . currentAttributeValueStart ,
347
- end : parser . currentAttributeValueEnd ,
348
- target,
349
- targetAnchor : anchor ,
350
- } ;
351
- }
352
- return ;
354
+ links . push ( ...attributes [ parser . currentAttributeName ] (
355
+ data ,
356
+ parser . currentAttributeValue ,
357
+ parser . currentAttributeValueStart ,
358
+ parser . currentAttributeValueEnd ,
359
+ ) ) ;
353
360
}
354
361
355
362
parser . step ( ) ;
356
363
}
364
+
365
+ return links ;
366
+ }
367
+
368
+ function checkAttributeDirectPath (
369
+ data : TextParserData ,
370
+ text : string ,
371
+ pos : number ,
372
+ end : number ,
373
+ ) : RelativeLink [ ] {
374
+ if ( isRelativePath ( text . trim ( ) ) ) {
375
+ const { target, anchor } = data . files . register (
376
+ data . sourcePath ,
377
+ text . trim ( ) as NormalizedPath ,
378
+ ) || { target : undefined , anchor : undefined } ;
379
+ return [ {
380
+ pos,
381
+ end,
382
+ target,
383
+ targetAnchor : anchor ,
384
+ } ] ;
385
+ }
386
+
387
+ return [ ] ;
388
+ }
389
+
390
+ // See https://html.spec.whatwg.org/multipage/images.html#srcset-attribute
391
+ function checkAttributeSrcSet ( data : TextParserData , text : string , pos : number , _end : number ) : RelativeLink [ ] {
392
+ const result : RelativeLink [ ] = [ ] ;
393
+
394
+ let textPos = 0 ;
395
+ parseImageCandidate ( ) ;
396
+ while ( textPos < text . length && text [ textPos ] == "," ) {
397
+ ++ textPos ;
398
+ parseImageCandidate ( ) ;
399
+ }
400
+
401
+ return result ;
402
+
403
+ function parseImageCandidate ( ) {
404
+ // 1. Zero or more ASCII whitespace
405
+ while ( textPos < text . length && / [ \t \r \f \n ] / . test ( text [ textPos ] ) ) ++ textPos ;
406
+ // 2. A valid non-empty URL that does not start or end with a comma
407
+ // TypeDoc: We don't exactly match this, PR welcome! For now, just permit anything
408
+ // that's not whitespace or a comma
409
+ const url = text . slice ( textPos ) . match ( / ^ [ ^ \t \r \f \n , ] + / ) ;
410
+
411
+ if ( url && isRelativePath ( url [ 0 ] ) ) {
412
+ const { target, anchor } = data . files . register (
413
+ data . sourcePath ,
414
+ url [ 0 ] as NormalizedPath ,
415
+ ) || { target : undefined , anchor : undefined } ;
416
+ result . push ( {
417
+ pos : pos + textPos ,
418
+ end : pos + textPos + url [ 0 ] . length ,
419
+ target,
420
+ targetAnchor : anchor ,
421
+ } ) ;
422
+ }
423
+ textPos += url ? url [ 0 ] . length : 0 ;
424
+
425
+ // 3. Zero or more ASCII whitespace
426
+ while ( textPos < text . length && / [ \t \r \f \n ] / . test ( text [ textPos ] ) ) ++ textPos ;
427
+
428
+ // 4. Zero or one of the following:
429
+ {
430
+ // A width descriptor, consisting of: ASCII whitespace, a valid non-negative integer giving
431
+ // a number greater than zero representing the width descriptor value, and a U+0077 LATIN
432
+ // SMALL LETTER W character.
433
+ const w = text . slice ( textPos ) . match ( / ^ \+ ? \d + \s * w / ) ;
434
+ textPos += w ? w [ 0 ] . length : 0 ;
435
+
436
+ // A pixel density descriptor, consisting of: ASCII whitespace, a valid floating-point number
437
+ // giving a number greater than zero representing the pixel density descriptor value, and a
438
+ // U+0078 LATIN SMALL LETTER X character.
439
+ if ( ! w ) {
440
+ const x = text . slice ( textPos ) . match ( / ^ \+ ? \d + ( \. \d + ) ? ( [ e E ] [ + - ] \d + ) ? \s * x / ) ;
441
+ textPos += x ? x [ 0 ] . length : 0 ;
442
+ }
443
+ }
444
+
445
+ // 5. Zero or more ASCII whitespace
446
+ while ( textPos < text . length && / [ \t \r \f \n ] / . test ( text [ textPos ] ) ) ++ textPos ;
447
+ }
357
448
}
358
449
359
450
function isRelativePath ( link : string ) {
0 commit comments