Repository: Y2Z/monolith
Pull request #445 (2025-03-16 03:39:33)

Fix embedding mechanism of images linked within SVG


Patch:
diff --git a/src/html.rs b/src/html.rs
index 6bcd22c..564a4cb 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -860,12 +860,6 @@ pub fn walk_and_embed_assets(
                         }
                     }
                 }
-                "svg" => {
-                    if options.no_images {
-                        // Remove all children
-                        node.children.borrow_mut().clear();
-                    }
-                }
                 "input" => {
                     if let Some(input_attr_type_value) = get_node_attr(node, "type") {
                         if input_attr_type_value.eq_ignore_ascii_case("image") {
@@ -892,16 +886,43 @@ pub fn walk_and_embed_assets(
                         }
                     }
                 }
-                "image" | "use" => {
+                "svg" => {
+                    if options.no_images {
+                        // Remove all children
+                        node.children.borrow_mut().clear();
+                    }
+                }
+                "image" => {
                     let attr_names: [&str; 2] = ["href", "xlink:href"];
 
                     for attr_name in attr_names.into_iter() {
                         if let Some(image_attr_href_value) = get_node_attr(node, attr_name) {
+                            if options.no_images {
+                                set_node_attr(node, attr_name, None);
+                            } else {
+                                retrieve_and_embed_asset(
+                                    cache,
+                                    client,
+                                    document_url,
+                                    node,
+                                    attr_name,
+                                    &image_attr_href_value,
+                                    options,
+                                );
+                            }
+                        }
+                    }
+                }
+                "use" => {
+                    let attr_names: [&str; 2] = ["href", "xlink:href"];
+
+                    for attr_name in attr_names.into_iter() {
+                        if let Some(use_attr_href_value) = get_node_attr(node, attr_name) {
                             if options.no_images {
                                 set_node_attr(node, attr_name, None);
                             } else {
                                 let image_asset_url: Url =
-                                    resolve_url(document_url, &image_attr_href_value);
+                                    resolve_url(document_url, &use_attr_href_value);
 
                                 match retrieve_asset(
                                     cache,


Test patch:
diff --git a/tests/_data_/svg/image.html b/tests/_data_/svg/image.html
new file mode 100644
index 0000000..0b83e2f
--- /dev/null
+++ b/tests/_data_/svg/image.html
@@ -0,0 +1,7 @@
+<html>
+    <body>
+        <svg height="24" width="24">
+            <image href="image.svg" width="24" height="24"></use>
+        </svg>
+    </body>
+</html>
diff --git a/tests/cli/local_files.rs b/tests/cli/local_files.rs
index 0013267..f0adb6d 100644
--- a/tests/cli/local_files.rs
+++ b/tests/cli/local_files.rs
@@ -224,7 +224,7 @@ mod passing {
             )
         );
 
-        // STDOUT should contain HTML with date URL for background-image in it
+        // STDOUT should contain HTML with one symbol extracted from SVG file
         assert_eq!(
             String::from_utf8_lossy(&out.stdout),
             "<html><head></head><body>\n<button class=\"tm-votes-lever__button\" data-test-id=\"votes-lever-upvote-button\" title=\"Like\" type=\"button\">\n  <svg class=\"tm-svg-img tm-votes-lever__icon\" height=\"24\" width=\"24\">\n    <title>Like</title>\n    <use xlink:href=\"#icon-1\"><symbol id=\"icon-1\">\n      <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M10 20h4V10h3l-5-6.5L7 10h3v10Z\"></path>\n    </symbol></use>\n  </svg>\n</button>\n\n\n</body></html>\n"
@@ -234,6 +234,37 @@ mod passing {
         out.assert().code(0);
     }
 
+    #[test]
+    fn embed_svg_local_asset_via_image() {
+        let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
+        let path_html: &Path = Path::new("tests/_data_/svg/image.html");
+        let path_svg: &Path = Path::new("tests/_data_/svg/image.svg");
+
+        let out = cmd.arg("-M").arg(path_html.as_os_str()).output().unwrap();
+
+        // STDERR should list files that got retrieved
+        assert_eq!(
+            String::from_utf8_lossy(&out.stderr),
+            format!(
+                "\
+                {file_url_html}\n\
+                {file_url_svg}\n\
+                ",
+                file_url_html = Url::from_file_path(fs::canonicalize(path_html).unwrap()).unwrap(),
+                file_url_svg = Url::from_file_path(fs::canonicalize(path_svg).unwrap()).unwrap(),
+            )
+        );
+
+        // STDOUT should contain HTML with data URL of SVG file
+        assert_eq!(
+            String::from_utf8_lossy(&out.stdout),
+            "<html><head></head><body>\n        <svg height=\"24\" width=\"24\">\n            <image href=\"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGJhc2VQcm9maWxlPSJmdWxsIiB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InJlZCIgLz4KICAgIDxjaXJjbGUgY3g9IjE1MCIgY3k9IjEwMCIgcj0iODAiIGZpbGw9ImdyZWVuIiAvPgogICAgPHRleHQgeD0iMTUwIiB5PSIxMjUiIGZvbnQtc2l6ZT0iNjAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IndoaXRlIj5TVkc8L3RleHQ+Cjwvc3ZnPgo=\" width=\"24\" height=\"24\">\n        </image></svg>\n    \n\n</body></html>\n"
+        );
+
+        // Exit code should be 0
+        out.assert().code(0);
+    }
+
     #[test]
     fn discard_integrity_for_local_files() {
         let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();