@@ -181,6 +181,33 @@ function replaceOpeningTags(
181181 return parts . join ( "" ) ;
182182}
183183
184+ function replaceIdTags ( html : string , id : string , replace : ( tag : string ) => string ) : string {
185+ const idPattern = new RegExp ( `id=["']${ escapeRegex ( id ) } ["']` , "gi" ) ;
186+ const lastClosing = html . lastIndexOf ( ">" ) ;
187+ const parts : string [ ] = [ ] ;
188+ let cursor = 0 ;
189+ let candidate = idPattern . exec ( html ) ;
190+ for ( const { index, end } of iterateOpeningTags ( html , / < / g) ) {
191+ if ( index < cursor ) continue ;
192+ let targetEnd = - 1 ;
193+ while ( candidate && candidate . index < end ) {
194+ const candidateEnd = candidate . index + candidate [ 0 ] . length ;
195+ if ( candidate . index > index && candidateEnd <= lastClosing ) targetEnd = candidateEnd ;
196+ // Preserve the old greedy prefix's last matching ID, including overlaps.
197+ idPattern . lastIndex = candidate . index + 1 ;
198+ candidate = idPattern . exec ( html ) ;
199+ }
200+ if ( targetEnd < 0 ) continue ;
201+ // An authored ID can itself contain '>', so its closer may follow the
202+ // initial span. The lastClosing check guarantees a closing delimiter.
203+ const closing = html . indexOf ( ">" , targetEnd ) + 1 ;
204+ parts . push ( html . slice ( cursor , index ) , replace ( html . slice ( index , closing ) ) ) ;
205+ cursor = closing ;
206+ }
207+ parts . push ( html . slice ( cursor ) ) ;
208+ return parts . join ( "" ) ;
209+ }
210+
184211function compileTag (
185212 tag : string ,
186213 isVideo : boolean ,
@@ -302,9 +329,7 @@ export function compileTimingAttrs(html: string): CompilationResult {
302329export function injectDurations ( html : string , resolutions : ResolvedDuration [ ] ) : string {
303330 for ( const { id, duration } of resolutions ) {
304331 // Match the element's opening tag by id
305- const idPattern = new RegExp ( `(<[^>]*id=["']${ escapeRegex ( id ) } ["'][^>]*>)` , "gi" ) ;
306-
307- html = html . replace ( idPattern , ( tag ) => {
332+ html = replaceIdTags ( html , id , ( tag ) => {
308333 let result = tag ;
309334
310335 // Add data-duration if missing
@@ -373,9 +398,7 @@ export function extractResolvedMedia(html: string): ResolvedMediaElement[] {
373398 */
374399export function clampDurations ( html : string , clamps : ResolvedDuration [ ] ) : string {
375400 for ( const { id, duration } of clamps ) {
376- const idPattern = new RegExp ( `(<[^>]*id=["']${ escapeRegex ( id ) } ["'][^>]*>)` , "gi" ) ;
377-
378- html = html . replace ( idPattern , ( tag ) => {
401+ html = replaceIdTags ( html , id , ( tag ) => {
379402 // Replace data-duration value
380403 tag = tag . replace ( / d a t a - d u r a t i o n = [ " ' ] [ ^ " ' ] * [ " ' ] / , `data-duration="${ duration } "` ) ;
381404
0 commit comments