@@ -136,9 +136,11 @@ export function textContent(
136136 continue ;
137137 }
138138
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+ }
142144 continue ;
143145 }
144146
@@ -299,61 +301,150 @@ function checkReference(data: TextParserData): RelativeLink | undefined {
299301/**
300302 * Looks for `<a href="./relative">`, `<img src="./relative">`, and `<source srcset="./relative">`
301303 */
302- function checkTagLink ( data : TextParserData ) : RelativeLink | undefined {
304+ function checkTagLink ( data : TextParserData ) : RelativeLink [ ] {
303305 const { pos, token } = data ;
304306
305307 if ( token . text . startsWith ( "<img " , pos ) ) {
306308 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+ } ) ;
308320 }
309321
310322 if ( token . text . startsWith ( "<a " , pos ) ) {
311323 data . pos += 3 ;
312- return checkAttribute ( data , " href" ) ;
324+ return checkAttributes ( data , { href : checkAttributeDirectPath } ) ;
313325 }
314326
315327 if ( token . text . startsWith ( "<source " , pos ) ) {
316328 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+ } ) ;
324333 }
334+
335+ return [ ] ;
325336}
326337
327- function checkAttribute (
338+ function checkAttributes (
328339 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 [ ] = [ ] ;
331346 const parser = new HtmlAttributeParser ( data . token . text , data . pos ) ;
332347 while ( parser . state !== ParserState . END ) {
333348 if (
334349 parser . state === ParserState . BeforeAttributeValue &&
335- parser . currentAttributeName === attr
350+ attributes . hasOwnProperty ( parser . currentAttributeName )
336351 ) {
337352 parser . step ( ) ;
338353
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+ ) ) ;
353360 }
354361
355362 parser . step ( ) ;
356363 }
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+ }
357448}
358449
359450function isRelativePath ( link : string ) {
0 commit comments