@@ -7,6 +7,7 @@ use serde_json::{Value, json};
77use std:: collections:: HashMap ;
88use std:: path:: { Path , PathBuf } ;
99use std:: sync:: Arc ;
10+ use std:: sync:: LazyLock ;
1011use std:: time:: { Duration , Instant , SystemTime , UNIX_EPOCH } ;
1112use time:: OffsetDateTime ;
1213use time:: format_description:: well_known:: Rfc3339 ;
@@ -414,7 +415,7 @@ async fn localize_figures(
414415 }
415416 }
416417
417- let rewritten = replace_image_urls ( & markdown, & replacements) ? ;
418+ let rewritten = replace_image_urls ( & markdown, & replacements) ;
418419 Ok ( (
419420 rewritten,
420421 downloaded_figures,
@@ -582,11 +583,16 @@ fn url_suffix(url: &str) -> Option<String> {
582583 Some ( format ! ( ".{ext}" ) )
583584}
584585
585- fn replace_image_urls ( markdown : & str , replacements : & HashMap < String , String > ) -> Result < String > {
586- let markdown_pattern = Regex :: new ( r"\((https?://[^)\s]+)\)" ) ? ;
587- let html_pattern = Regex :: new ( r#"(src\s*=\s*)(['"])(https?://[^'"]+)(['"])"# ) ? ;
586+ static MARKDOWN_IMAGE_URL_PATTERN : LazyLock < Regex > = LazyLock :: new ( || {
587+ Regex :: new ( r"\((https?://[^)\s]+)\)" ) . expect ( "valid markdown image URL regex" )
588+ } ) ;
588589
589- let updated = markdown_pattern
590+ static HTML_IMAGE_URL_PATTERN : LazyLock < Regex > = LazyLock :: new ( || {
591+ Regex :: new ( r#"(src\s*=\s*)(['"])(https?://[^'"]+)(['"])"# ) . expect ( "valid HTML image URL regex" )
592+ } ) ;
593+
594+ fn replace_image_urls ( markdown : & str , replacements : & HashMap < String , String > ) -> String {
595+ let updated = MARKDOWN_IMAGE_URL_PATTERN
590596 . replace_all ( markdown, |caps : & regex:: Captures < ' _ > | {
591597 let remote_url = caps. get ( 1 ) . map ( |m| m. as_str ( ) ) . unwrap_or_default ( ) ;
592598 let replacement = replacements
@@ -597,7 +603,7 @@ fn replace_image_urls(markdown: &str, replacements: &HashMap<String, String>) ->
597603 } )
598604 . into_owned ( ) ;
599605
600- Ok ( html_pattern
606+ HTML_IMAGE_URL_PATTERN
601607 . replace_all ( & updated, |caps : & regex:: Captures < ' _ > | {
602608 let prefix = caps. get ( 1 ) . map ( |m| m. as_str ( ) ) . unwrap_or_default ( ) ;
603609 let quote = caps. get ( 2 ) . map ( |m| m. as_str ( ) ) . unwrap_or ( "\" " ) ;
@@ -609,7 +615,7 @@ fn replace_image_urls(markdown: &str, replacements: &HashMap<String, String>) ->
609615 . unwrap_or_else ( || remote_url. to_string ( ) ) ;
610616 format ! ( "{prefix}{quote}{replacement}{suffix}" )
611617 } )
612- . into_owned ( ) )
618+ . into_owned ( )
613619}
614620
615621async fn append_log ( log_path : & Path , entry : Value ) -> Result < ( ) > {
@@ -1211,7 +1217,7 @@ mod tests {
12111217 "https://x/fig.png" . to_string ( ) ,
12121218 "figures/f1.png" . to_string ( ) ,
12131219 ) ;
1214- let updated = replace_image_urls ( markdown, & replacements) . unwrap ( ) ;
1220+ let updated = replace_image_urls ( markdown, & replacements) ;
12151221 assert ! ( updated. contains( "" ) ) ;
12161222 assert ! ( updated. contains( "" ) ) ;
12171223 }
@@ -1224,10 +1230,33 @@ mod tests {
12241230 "https://x/fig.png" . to_string ( ) ,
12251231 "figures/f1.png" . to_string ( ) ,
12261232 ) ;
1227- let updated = replace_image_urls ( markdown, & replacements) . unwrap ( ) ;
1233+ let updated = replace_image_urls ( markdown, & replacements) ;
12281234 assert_eq ! ( updated, "<img src='figures/f1.png' alt='x'/>" ) ;
12291235 }
12301236
1237+ #[ test]
1238+ fn replace_image_urls_mixed_markdown_and_html ( ) {
1239+ let markdown = "start  mid <img src=\" https://x/b.jpg\" alt='x'/> end " ;
1240+ let mut replacements = HashMap :: new ( ) ;
1241+ replacements. insert ( "https://x/a.png" . to_string ( ) , "figures/a.png" . to_string ( ) ) ;
1242+ replacements. insert ( "https://x/b.jpg" . to_string ( ) , "figures/b.jpg" . to_string ( ) ) ;
1243+
1244+ let updated = replace_image_urls ( markdown, & replacements) ;
1245+ assert_eq ! (
1246+ updated,
1247+ "start  mid <img src=\" figures/b.jpg\" alt='x'/> end "
1248+ ) ;
1249+ }
1250+
1251+ #[ test]
1252+ fn replace_image_urls_no_replacements_passthrough ( ) {
1253+ let markdown = "plain text  <img src='https://x/b.jpg' alt='x'/>" ;
1254+ let replacements = HashMap :: new ( ) ;
1255+
1256+ let updated = replace_image_urls ( markdown, & replacements) ;
1257+ assert_eq ! ( updated, markdown) ;
1258+ }
1259+
12311260 #[ test]
12321261 fn prepare_output_without_overwrite_fails_on_existing_managed_artifacts ( ) {
12331262 let tmp = TempDir :: new ( ) . unwrap ( ) ;
0 commit comments