Skip to content

Commit

Permalink
fix: improve handling of url references in reference attributes (#1881)
Browse files Browse the repository at this point in the history
  • Loading branch information
SethFalco committed Dec 10, 2023
1 parent c172c9e commit e6deeca
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 44 deletions.
41 changes: 39 additions & 2 deletions lib/svgo/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
* @typedef {import('../types').DataUri} DataUri
*/

const { attrsGroups } = require('../../plugins/_collections');
const { attrsGroups, referencesProps } = require('../../plugins/_collections');

const regReferencesUrl = /\burl\((["'])?#(.+?)\1\)/g;
const regReferencesHref = /^#(.+?)$/;
const regReferencesBegin = /(\w+)\.[a-zA-Z]/;

/**
* Encode plain SVG data string into Data URI string.
Expand Down Expand Up @@ -188,6 +192,39 @@ exports.hasScripts = hasScripts;
* @returns {boolean} If the given string includes a URL reference.
*/
const includesUrlReference = (body) => {
return /\burl\((["'])?#(.+?)\1\)/g.test(body);
return new RegExp(regReferencesUrl).test(body);
};
exports.includesUrlReference = includesUrlReference;

/**
* @param {string} attribute
* @param {string} value
* @returns {string[]}
*/
const findReferences = (attribute, value) => {
const results = [];

if (referencesProps.includes(attribute)) {
const matches = value.matchAll(regReferencesUrl);
for (const match of matches) {
results.push(match[2]);
}
}

if (attribute === 'href' || attribute.endsWith(':href')) {
const match = regReferencesHref.exec(value);
if (match != null) {
results.push(match[1]);
}
}

if (attribute === 'begin') {
const match = regReferencesBegin.exec(value);
if (match != null) {
results.push(match[1]);
}
}

return results.map((body) => decodeURI(body));
};
exports.findReferences = findReferences;
35 changes: 4 additions & 31 deletions plugins/cleanupIds.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
*/

const { visitSkip } = require('../lib/xast.js');
const { hasScripts } = require('../lib/svgo/tools');
const { referencesProps } = require('./_collections.js');
const { hasScripts, findReferences } = require('../lib/svgo/tools');

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

const regReferencesUrl = /\burl\((["'])?#(.+?)\1\)/g;
const regReferencesHref = /^#(.+?)$/;
const regReferencesBegin = /(\w+)\.[a-zA-Z]/;
const generateIdChars = [
'a',
'b',
Expand Down Expand Up @@ -191,35 +187,12 @@ exports.fn = (_root, params) => {
nodeById.set(id, node);
}
} else {
// collect all references
/**
* @type {string[]}
*/
let ids = [];
if (referencesProps.includes(name)) {
const matches = value.matchAll(regReferencesUrl);
for (const match of matches) {
ids.push(match[2]); // url() reference
}
}
if (name === 'href' || name.endsWith(':href')) {
const match = value.match(regReferencesHref);
if (match != null) {
ids.push(match[1]); // href reference
}
}
if (name === 'begin') {
const match = value.match(regReferencesBegin);
if (match != null) {
ids.push(match[1]); // href reference
}
}
const ids = findReferences(name, value);
for (const id of ids) {
const decodedId = decodeURI(id);
let refs = referencesById.get(decodedId);
let refs = referencesById.get(id);
if (refs == null) {
refs = [];
referencesById.set(decodedId, refs);
referencesById.set(id, refs);
}
refs.push({ element: node, name });
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/prefixIds.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ exports.fn = (_root, params, info) => {
node.attributes[name].length !== 0
) {
node.attributes[name] = node.attributes[name].replace(
/url\((.*?)\)/gi,
(match, url) => {
/\burl\((["'])?(#.+?)\1\)/gi,
(match, _, url) => {
const prefixed = prefixReference(prefixGenerator, url);
if (prefixed == null) {
return match;
Expand Down
24 changes: 15 additions & 9 deletions plugins/removeHiddenElems.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @typedef {import('../lib/types').XastParent} XastParent
*/

const { elemsGroups, referencesProps } = require('./_collections.js');
const { elemsGroups } = require('./_collections.js');
const {
visit,
visitSkip,
Expand All @@ -15,7 +15,7 @@ const {
} = require('../lib/xast.js');
const { collectStylesheet, computeStyle } = require('../lib/style.js');
const { parsePathData } = require('../lib/path.js');
const { hasScripts } = require('../lib/svgo/tools.js');
const { hasScripts, findReferences } = require('../lib/svgo/tools.js');

const nonRendering = elemsGroups.nonRendering;

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

/** @type {Set<string>} */
const allReferences = new Set();

/**
* @type {Map<string, Array<{ node: XastElement, parentNode: XastParent }>>}
*/
Expand Down Expand Up @@ -363,7 +366,6 @@ exports.fn = (root, params) => {
removeElement(node, parentNode);
return;
}
return;
}

// Polyline with empty points
Expand Down Expand Up @@ -391,6 +393,15 @@ exports.fn = (root, params) => {
node.attributes.points == null
) {
removeElement(node, parentNode);
return;
}

for (const [name, value] of Object.entries(node.attributes)) {
const ids = findReferences(name, value);

for (const id of ids) {
allReferences.add(id);
}
}
},
},
Expand All @@ -411,13 +422,8 @@ exports.fn = (root, params) => {
nonRenderedParent,
] of nonRenderedNodes.entries()) {
const id = nonRenderedNode.attributes.id;
const selector = referencesProps
.map((attr) => `[${attr}="url(#${id})"]`)
.concat(`[href="#${id}"]`, `[xlink\\:href="#${id}"]`)
.join(',');

const element = querySelector(root, selector);
if (element == null) {
if (!allReferences.has(id)) {
detachNodeFromParent(nonRenderedNode, nonRenderedParent);
}
}
Expand Down

0 comments on commit e6deeca

Please sign in to comment.