@@ -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
@@ -297,52 +299,152 @@ function checkReference(data: TextParserData): RelativeLink | undefined {
297299}
298300
299301/**
300- * Looks for `<a href="./relative">` and `<img src="./relative">`
302+ * 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 } ) ;
325+ }
326+
327+ if ( token . text . startsWith ( "<source " , pos ) ) {
328+ data . pos += 8 ;
329+ return checkAttributes ( data , {
330+ src : checkAttributeDirectPath ,
331+ srcset : checkAttributeSrcSet ,
332+ } ) ;
313333 }
334+
335+ return [ ] ;
314336}
315337
316- function checkAttribute (
338+ function checkAttributes (
317339 data : TextParserData ,
318- attr : string ,
319- ) : RelativeLink | undefined {
340+ attributes : Record <
341+ string ,
342+ ( data : TextParserData , text : string , pos : number , end : number ) => RelativeLink [ ]
343+ > ,
344+ ) : RelativeLink [ ] {
345+ const links : RelativeLink [ ] = [ ] ;
320346 const parser = new HtmlAttributeParser ( data . token . text , data . pos ) ;
321347 while ( parser . state !== ParserState . END ) {
322348 if (
323349 parser . state === ParserState . BeforeAttributeValue &&
324- parser . currentAttributeName === attr
350+ attributes . hasOwnProperty ( parser . currentAttributeName )
325351 ) {
326352 parser . step ( ) ;
327353
328- if ( isRelativePath ( parser . currentAttributeValue ) ) {
329- data . pos = parser . pos ;
330- const { target, anchor } = data . files . register (
331- data . sourcePath ,
332- parser . currentAttributeValue as NormalizedPath ,
333- ) || { target : undefined , anchor : undefined } ;
334- return {
335- pos : parser . currentAttributeValueStart ,
336- end : parser . currentAttributeValueEnd ,
337- target,
338- targetAnchor : anchor ,
339- } ;
340- }
341- return ;
354+ links . push ( ...attributes [ parser . currentAttributeName ] (
355+ data ,
356+ parser . currentAttributeValue ,
357+ parser . currentAttributeValueStart ,
358+ parser . currentAttributeValueEnd ,
359+ ) ) ;
342360 }
343361
344362 parser . step ( ) ;
345363 }
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+ }
346448}
347449
348450function isRelativePath ( link : string ) {
0 commit comments