Repository: svg-sprite/svg-sprite
Pull request #682 (2022-05-23 17:49:36)

Substitute ID references in href attributes 
Initial PR #577 

Closes #577

XhmikosR: This is wrong @Kreeg. Needs a proper rebase
Kreeg: @XhmikosR yeah I kinda freaked out too when I saw that :D

Patch:
diff --git a/lib/svg-sprite/shape.js b/lib/svg-sprite/shape.js
index 9aef3b8..67b7513 100644
--- a/lib/svg-sprite/shape.js
+++ b/lib/svg-sprite/shape.js
@@ -711,6 +711,14 @@ SVGShape.prototype.setNamespace = function(ns) {
                 }
             });
 
+            // Substitute ID references in href attributes
+            select('//@href', this.dom).forEach(href => {
+                const hrefValue = href.nodeValue;
+                if (!hrefValue.startsWith('data:') && hrefValue in substIds) {
+                    href.ownerElement.setAttribute('href', `#${substIds[hrefValue]}`);
+                }
+            });
+
             // Substitute ID references in referencing attributes
             svgReferenceProperties.forEach(refProperty => {
                 select(`//@${refProperty}`, this.dom).forEach(ref => {


Test patch:
diff --git a/test/svg-sprite/shape/shape.svg.namespace.test.js b/test/svg-sprite/shape/shape.svg.namespace.test.js
index 01559de..061f3a7 100644
--- a/test/svg-sprite/shape/shape.svg.namespace.test.js
+++ b/test/svg-sprite/shape/shape.svg.namespace.test.js
@@ -77,16 +77,24 @@ describe('testing setNamespace()', () => {
                 }
             }];
             const THIRD_ELEMENTS = [{
+                nodeValue: 'data:'
+            }, {
+                nodeValue: `#${TEST_ATTR_VALUE}`,
+                ownerElement: {
+                    setAttribute: jest.fn()
+                }
+            }];
+            const FOURTH_ELEMENTS = [{
                 localName: 'TEST local name',
                 ownerElement: {
                     setAttribute: jest.fn()
                 }
             }];
 
-            const FORTH_ELEMENTS = [{
+            const SIXTH_ELEMENTS = [{
                 ownerElement: { setAttribute: jest.fn() }
             }];
-            const mockSelect = jest.fn().mockReturnValueOnce(FIRST_ELEMENTS).mockReturnValueOnce(SECOND_ELEMENTS).mockReturnValueOnce(THIRD_ELEMENTS).mockReturnValue(FORTH_ELEMENTS);
+            const mockSelect = jest.fn().mockReturnValueOnce(FIRST_ELEMENTS).mockReturnValueOnce(SECOND_ELEMENTS).mockReturnValueOnce(THIRD_ELEMENTS).mockReturnValueOnce(FOURTH_ELEMENTS).mockReturnValue(SIXTH_ELEMENTS);
 
             const mockMinifyBlock = jest.fn().mockReturnValue({ css: '' });
 
@@ -99,20 +107,22 @@ describe('testing setNamespace()', () => {
 
             shape.setNamespace(TEST_NAMESPACE);
 
-            expect(mockSelect).toHaveBeenCalledTimes(13);
+            expect(mockSelect).toHaveBeenCalledTimes(14);
             expect(mockSelect.mock.calls[0][0]).toBe('//*[@id]');
             expect(mockSelect.mock.calls[1][0]).toBe('//@xlink:href');
+            expect(mockSelect.mock.calls[2][0]).toBe('//@href');
 
             ['style', 'fill', 'stroke', 'filter', 'clip-path', 'mask', 'marker-start', 'marker-end', 'marker-mid'].forEach((ref, i) => {
-                expect(mockSelect.mock.calls[2 + i][0]).toBe(`//@${ref}`);
+                expect(mockSelect.mock.calls[3 + i][0]).toBe(`//@${ref}`);
             });
 
-            expect(mockSelect.mock.calls[11][0]).toBe('//svg:style');
             expect(mockSelect.mock.calls[12][0]).toBe('//svg:style');
+            expect(mockSelect.mock.calls[13][0]).toBe('//svg:style');
 
             expect(FIRST_ELEMENTS[0].setAttribute).toHaveBeenCalledWith('id', `${TEST_NAMESPACE}${TEST_ATTR_VALUE}`);
             expect(SECOND_ELEMENTS[1].ownerElement.setAttribute).toHaveBeenCalledWith('xlink:href', `#${TEST_NAMESPACE}${TEST_ATTR_VALUE}`);
-            expect(THIRD_ELEMENTS[0].ownerElement.setAttribute).toHaveBeenCalledWith(THIRD_ELEMENTS[0].localName, '');
+            expect(THIRD_ELEMENTS[1].ownerElement.setAttribute).toHaveBeenCalledWith('href', `#${TEST_NAMESPACE}${TEST_ATTR_VALUE}`);
+            expect(FOURTH_ELEMENTS[0].ownerElement.setAttribute).toHaveBeenCalledWith(FOURTH_ELEMENTS[0].localName, '');
             expect(shape._namespaced).toBe(true);
 
             expect(mockMinifyBlock).toHaveBeenCalledWith('', { restructure: false });
diff --git a/test/svg-sprite/shape/shape.svg.test.js b/test/svg-sprite/shape/shape.svg.test.js
index 2c6ffa1..6d1e92c 100644
--- a/test/svg-sprite/shape/shape.svg.test.js
+++ b/test/svg-sprite/shape/shape.svg.test.js
@@ -119,6 +119,66 @@ describe('testing getSVG()', () => {
         expect(shape.getSVG()).toContain(TEST_DOCTYPE_DECLARATION);
         expect(shape.getSVG()).toContain(TEST_XML_DECLARATION);
     });
+
+    it('should substitute ID references in href attributes', () => {
+        expect.hasAssertions();
+
+        const shape = new SVGShape({
+            ...TEST_FILE,
+            contents: '<svg xmlns="http://www.w3.org/2000/svg" id="abc" height="0" width="0"><use href="#abc"/></svg>'
+        }, {
+            config: {
+                shape: {
+                    meta: {},
+                    align: {}
+                },
+                svg: {
+                    doctypeDeclaration: '',
+                    namespaceIDPrefix: 'someprefix-',
+                    namespaceClassnames: false,
+                    namespaceIDs: true
+                },
+                mode: {
+                    view: true
+                }
+            },
+            verbose: jest.fn()
+        });
+        shape.complement(jest.fn);
+        shape.setNamespace('ns-');
+
+        expect(shape.getSVG()).toBe('<svg xmlns="http://www.w3.org/2000/svg" id="someprefix-ns-abc" height="0" width="0" viewBox="0 0 0 0"><use href="#someprefix-ns-abc"/></svg>');
+    });
+
+    it('should substitute ID references in xlink:href attributes', () => {
+        expect.hasAssertions();
+
+        const shape = new SVGShape({
+            ...TEST_FILE,
+            contents: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="abc" height="0" width="0"><use xlink:href="#abc"/></svg>'
+        }, {
+            config: {
+                shape: {
+                    meta: {},
+                    align: {}
+                },
+                svg: {
+                    doctypeDeclaration: '',
+                    namespaceIDPrefix: 'someprefix-',
+                    namespaceClassnames: false,
+                    namespaceIDs: true
+                },
+                mode: {
+                    view: true
+                }
+            },
+            verbose: jest.fn()
+        });
+        shape.complement(jest.fn);
+        shape.setNamespace('ns-');
+
+        expect(shape.getSVG()).toBe('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="someprefix-ns-abc" height="0" width="0" viewBox="0 0 0 0"><use xlink:href="#someprefix-ns-abc"/></svg>');
+    });
 });
 
 describe('testing _stripInlineNamespaceDeclarations()', () => {