Skip to content

Commit

Permalink
Check for reference by <use> before deleting defaults in removeUnknow…
Browse files Browse the repository at this point in the history
…nsAndDefaults.
  • Loading branch information
johnkenny54 committed Sep 19, 2024
1 parent e90cdad commit d8c3ea8
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 3 deletions.
80 changes: 78 additions & 2 deletions plugins/removeUnknownsAndDefaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
presentationNonInheritableGroupAttrs,
} from './_collections.js';
import { visitSkip, detachNodeFromParent } from '../lib/xast.js';
import { findReferences } from '../lib/svgo/tools.js';

export const name = 'removeUnknownsAndDefaults';
export const description =
Expand Down Expand Up @@ -83,6 +84,20 @@ for (const [name, config] of Object.entries(elems)) {
attributesDefaultsPerElement.set(name, attributesDefaults);
}

/**
* @param {import('../lib/types.js').XastElement} element
* @param {string} attName
*/
function getUseID(element, attName) {
const value = element.attributes[attName];
if (value) {
const ids = findReferences(attName, value);
if (ids) {
return ids[0];
}
}
}

/**
* Remove unknown elements content and attributes,
* remove attributes with default values.
Expand All @@ -92,6 +107,40 @@ for (const [name, config] of Object.entries(elems)) {
* @type {import('./plugins-types.js').Plugin<'removeUnknownsAndDefaults'>}
*/
export const fn = (root, params, info) => {
/**
* @param {import('../lib/types.js').XastElement} element
* @returns {boolean}
*/
function elementIsUsed(element) {
if (usedIDs.size === 0) {
return false;
}
// See if the element or any of its parents are used.
while (true) {
if (element.attributes.id && usedIDs.has(element.attributes.id)) {
return true;
}
const parent = element.parentNode;
if (parent.type === 'root') {
return false;
}
element = parent;
}
}

/**
* @param {import('../lib/types.js').XastElement} node
* @param {string} attName
*/
function saveForUsageCheck(node, attName) {
let attNames = deleteIfUnused.get(node);
if (!attNames) {
attNames = [];
deleteIfUnused.set(node, attNames);
}
attNames.push(attName);
}

const {
unknownContent = true,
unknownAttrs = true,
Expand All @@ -108,6 +157,11 @@ export const fn = (root, params, info) => {
return;
}

/** @type {Map<import('../lib/types.js').XastElement,string[]>} */
const deleteIfUnused = new Map();
/** @type {Set<string>} */
const usedIDs = new Set();

return {
instruction: {
enter: (node) => {
Expand All @@ -127,6 +181,17 @@ export const fn = (root, params, info) => {
return visitSkip;
}

if (node.name === 'use') {
let id = getUseID(node, 'href');
if (!id) {
id = getUseID(node, 'xlink:href');
}
if (id) {
usedIDs.add(id);
}
return;
}

// remove unknown element's content
if (unknownContent && parentNode.type === 'element') {
const allowedChildren = allowedChildrenPerElement.get(
Expand Down Expand Up @@ -187,7 +252,6 @@ export const fn = (root, params, info) => {
}
if (
defaultAttrs &&
node.attributes.id == null &&
attributesDefaults &&
attributesDefaults.get(name) === value
) {
Expand All @@ -196,7 +260,7 @@ export const fn = (root, params, info) => {
? computedParentStyle.get(name)
: undefined;
if (value === undefined) {
delete node.attributes[name];
saveForUsageCheck(node, name);
}
}
if (uselessOverrides && node.attributes.id == null) {
Expand All @@ -213,5 +277,17 @@ export const fn = (root, params, info) => {
}
},
},
root: {
exit: () => {
for (const [element, attNames] of deleteIfUnused.entries()) {
if (elementIsUsed(element)) {
continue;
}
for (const attName of attNames) {
delete element.attributes[attName];
}
}
},
},
};
};
2 changes: 1 addition & 1 deletion test/plugins/removeUnknownsAndDefaults.01.svg.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@

<svg xmlns="http://www.w3.org/2000/svg" xmlns:test="http://" y="10" test:attr="val" xml:space="preserve">
<rect/>
<rect fill="#000" id="black-rect"/>
<rect id="black-rect"/>
</svg>
36 changes: 36 additions & 0 deletions test/plugins/removeUnknownsAndDefaults.18.svg.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Preserve defaults on elements in <defs> that may be <use>d.

See: https://github.com/svg/svgo/issues/1486

===

<svg xmlns="http://www.w3.org/2000/svg" viewBox="350 -953 560 490">
<defs>
<g id="bubble_2" fill="#ff0000">
<text x="791" y="-761" dy="0.7ex" text-anchor="start" stroke="none">China</text>
</g>
</defs>
<g text-anchor="middle">
<g text-anchor="start">
<use href="#bubble_2"/>
</g>
<use href="#bubble_2"/>
</g>
</svg>

@@@

<svg xmlns="http://www.w3.org/2000/svg" viewBox="350 -953 560 490">
<defs>
<g id="bubble_2" fill="#ff0000">
<text x="791" y="-761" dy="0.7ex" text-anchor="start" stroke="none">China</text>
</g>
</defs>
<g text-anchor="middle">
<g text-anchor="start">
<use href="#bubble_2"/>
</g>
<use href="#bubble_2"/>
</g>
</svg>

0 comments on commit d8c3ea8

Please sign in to comment.