Skip to content

Commit e6deeca

Browse files
authored
fix: improve handling of url references in reference attributes (#1881)
1 parent c172c9e commit e6deeca

File tree

4 files changed

+60
-44
lines changed

4 files changed

+60
-44
lines changed

lib/svgo/tools.js

+39-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
* @typedef {import('../types').DataUri} DataUri
77
*/
88

9-
const { attrsGroups } = require('../../plugins/_collections');
9+
const { attrsGroups, referencesProps } = require('../../plugins/_collections');
10+
11+
const regReferencesUrl = /\burl\((["'])?#(.+?)\1\)/g;
12+
const regReferencesHref = /^#(.+?)$/;
13+
const regReferencesBegin = /(\w+)\.[a-zA-Z]/;
1014

1115
/**
1216
* Encode plain SVG data string into Data URI string.
@@ -188,6 +192,39 @@ exports.hasScripts = hasScripts;
188192
* @returns {boolean} If the given string includes a URL reference.
189193
*/
190194
const includesUrlReference = (body) => {
191-
return /\burl\((["'])?#(.+?)\1\)/g.test(body);
195+
return new RegExp(regReferencesUrl).test(body);
192196
};
193197
exports.includesUrlReference = includesUrlReference;
198+
199+
/**
200+
* @param {string} attribute
201+
* @param {string} value
202+
* @returns {string[]}
203+
*/
204+
const findReferences = (attribute, value) => {
205+
const results = [];
206+
207+
if (referencesProps.includes(attribute)) {
208+
const matches = value.matchAll(regReferencesUrl);
209+
for (const match of matches) {
210+
results.push(match[2]);
211+
}
212+
}
213+
214+
if (attribute === 'href' || attribute.endsWith(':href')) {
215+
const match = regReferencesHref.exec(value);
216+
if (match != null) {
217+
results.push(match[1]);
218+
}
219+
}
220+
221+
if (attribute === 'begin') {
222+
const match = regReferencesBegin.exec(value);
223+
if (match != null) {
224+
results.push(match[1]);
225+
}
226+
}
227+
228+
return results.map((body) => decodeURI(body));
229+
};
230+
exports.findReferences = findReferences;

plugins/cleanupIds.js

+4-31
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@
55
*/
66

77
const { visitSkip } = require('../lib/xast.js');
8-
const { hasScripts } = require('../lib/svgo/tools');
9-
const { referencesProps } = require('./_collections.js');
8+
const { hasScripts, findReferences } = require('../lib/svgo/tools');
109

1110
exports.name = 'cleanupIds';
1211
exports.description = 'removes unused IDs and minifies used';
1312

14-
const regReferencesUrl = /\burl\((["'])?#(.+?)\1\)/g;
15-
const regReferencesHref = /^#(.+?)$/;
16-
const regReferencesBegin = /(\w+)\.[a-zA-Z]/;
1713
const generateIdChars = [
1814
'a',
1915
'b',
@@ -191,35 +187,12 @@ exports.fn = (_root, params) => {
191187
nodeById.set(id, node);
192188
}
193189
} else {
194-
// collect all references
195-
/**
196-
* @type {string[]}
197-
*/
198-
let ids = [];
199-
if (referencesProps.includes(name)) {
200-
const matches = value.matchAll(regReferencesUrl);
201-
for (const match of matches) {
202-
ids.push(match[2]); // url() reference
203-
}
204-
}
205-
if (name === 'href' || name.endsWith(':href')) {
206-
const match = value.match(regReferencesHref);
207-
if (match != null) {
208-
ids.push(match[1]); // href reference
209-
}
210-
}
211-
if (name === 'begin') {
212-
const match = value.match(regReferencesBegin);
213-
if (match != null) {
214-
ids.push(match[1]); // href reference
215-
}
216-
}
190+
const ids = findReferences(name, value);
217191
for (const id of ids) {
218-
const decodedId = decodeURI(id);
219-
let refs = referencesById.get(decodedId);
192+
let refs = referencesById.get(id);
220193
if (refs == null) {
221194
refs = [];
222-
referencesById.set(decodedId, refs);
195+
referencesById.set(id, refs);
223196
}
224197
refs.push({ element: node, name });
225198
}

plugins/prefixIds.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ exports.fn = (_root, params, info) => {
237237
node.attributes[name].length !== 0
238238
) {
239239
node.attributes[name] = node.attributes[name].replace(
240-
/url\((.*?)\)/gi,
241-
(match, url) => {
240+
/\burl\((["'])?(#.+?)\1\)/gi,
241+
(match, _, url) => {
242242
const prefixed = prefixReference(prefixGenerator, url);
243243
if (prefixed == null) {
244244
return match;

plugins/removeHiddenElems.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @typedef {import('../lib/types').XastParent} XastParent
77
*/
88

9-
const { elemsGroups, referencesProps } = require('./_collections.js');
9+
const { elemsGroups } = require('./_collections.js');
1010
const {
1111
visit,
1212
visitSkip,
@@ -15,7 +15,7 @@ const {
1515
} = require('../lib/xast.js');
1616
const { collectStylesheet, computeStyle } = require('../lib/style.js');
1717
const { parsePathData } = require('../lib/path.js');
18-
const { hasScripts } = require('../lib/svgo/tools.js');
18+
const { hasScripts, findReferences } = require('../lib/svgo/tools.js');
1919

2020
const nonRendering = elemsGroups.nonRendering;
2121

@@ -80,6 +80,9 @@ exports.fn = (root, params) => {
8080
*/
8181
const allDefs = new Map();
8282

83+
/** @type {Set<string>} */
84+
const allReferences = new Set();
85+
8386
/**
8487
* @type {Map<string, Array<{ node: XastElement, parentNode: XastParent }>>}
8588
*/
@@ -363,7 +366,6 @@ exports.fn = (root, params) => {
363366
removeElement(node, parentNode);
364367
return;
365368
}
366-
return;
367369
}
368370

369371
// Polyline with empty points
@@ -391,6 +393,15 @@ exports.fn = (root, params) => {
391393
node.attributes.points == null
392394
) {
393395
removeElement(node, parentNode);
396+
return;
397+
}
398+
399+
for (const [name, value] of Object.entries(node.attributes)) {
400+
const ids = findReferences(name, value);
401+
402+
for (const id of ids) {
403+
allReferences.add(id);
404+
}
394405
}
395406
},
396407
},
@@ -411,13 +422,8 @@ exports.fn = (root, params) => {
411422
nonRenderedParent,
412423
] of nonRenderedNodes.entries()) {
413424
const id = nonRenderedNode.attributes.id;
414-
const selector = referencesProps
415-
.map((attr) => `[${attr}="url(#${id})"]`)
416-
.concat(`[href="#${id}"]`, `[xlink\\:href="#${id}"]`)
417-
.join(',');
418425

419-
const element = querySelector(root, selector);
420-
if (element == null) {
426+
if (!allReferences.has(id)) {
421427
detachNodeFromParent(nonRenderedNode, nonRenderedParent);
422428
}
423429
}

0 commit comments

Comments
 (0)