Skip to content

Commit

Permalink
Remove unused <use> elements when deleting empty symbols (issue 1765).
Browse files Browse the repository at this point in the history
  • Loading branch information
johnkenny54 committed Aug 5, 2024
1 parent 9078e8c commit 26a98ea
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
45 changes: 45 additions & 0 deletions plugins/removeEmptyContainers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { elemsGroups } from './_collections.js';
import { detachNodeFromParent } from '../lib/xast.js';
import { findReferences } from '../lib/svgo/tools.js';

/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastParent} XastParent
*/

export const name = 'removeEmptyContainers';
export const description = 'removes empty container elements';
Expand All @@ -20,8 +26,30 @@ export const description = 'removes empty container elements';
* @type {import('./plugins-types.js').Plugin<'removeEmptyContainers'>}
*/
export const fn = () => {
const removedIds = new Set();
/**
* @type {Map<string, {node:XastElement,parent:XastParent}[]>}
*/
const usesById = new Map();

return {
element: {
enter: (node, parentNode) => {
if (node.name === 'use') {
// Record uses so those referencing empty containers can be removed.
for (const [name, value] of Object.entries(node.attributes)) {
const ids = findReferences(name, value);
for (const id of ids) {
let references = usesById.get(id);
if (references === undefined) {
references = [];
usesById.set(id, references);
}
references.push({ node: node, parent: parentNode });
}
}
}
},
exit: (node, parentNode) => {
// remove only empty non-svg containers
if (
Expand Down Expand Up @@ -50,7 +78,24 @@ export const fn = () => {
if (parentNode.type === 'element' && parentNode.name === 'switch') {
return;
}

detachNodeFromParent(node, parentNode);
if (node.attributes.id) {
removedIds.add(node.attributes.id);
}
},
},
root: {
exit: () => {
// Remove any <use> elements that referenced an empty container.
for (const id of removedIds) {
const uses = usesById.get(id);
if (uses) {
for (const use of uses) {
detachNodeFromParent(use.node, use.parent);
}
}
}
},
},
};
Expand Down
31 changes: 31 additions & 0 deletions test/plugins/removeEmptyContainers.07.svg.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
If a container with an id attribute is removed, remove any <use>s associated with the id.

===

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<symbol id="a">
<path d="M 10 10 H 90" style="stroke:black;stroke-width:2"/>
</symbol>
<symbol id="b">
<path d="M 10 20 H 90"/>
</symbol>
<symbol id="c"/>
<symbol id="d"/>
<use xlink:href="#a"/>
<use href="#b" style="stroke:red;stroke-width:2"/>
<use xlink:href="#c"/>
<use href="#d" style="stroke:red;stroke-width:2"/>
</svg>

@@@

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<symbol id="a">
<path d="M 10 10 H 90" style="stroke:black;stroke-width:2"/>
</symbol>
<symbol id="b">
<path d="M 10 20 H 90"/>
</symbol>
<use xlink:href="#a"/>
<use href="#b" style="stroke:red;stroke-width:2"/>
</svg>

0 comments on commit 26a98ea

Please sign in to comment.