diff --git a/plugins/cleanupIds.js b/plugins/cleanupIds.js index 7a30591c8..c222c5344 100644 --- a/plugins/cleanupIds.js +++ b/plugins/cleanupIds.js @@ -147,6 +147,37 @@ export const fn = (_root, params) => { const referencesById = new Map(); let deoptimized = false; + function generateIdMap() { + /** + * @param {string} id + * @returns {boolean} + */ + const isIdPreserved = (id) => + preserveIds.has(id) || hasStringPrefix(id, preserveIdPrefixes); + const idMap = new Map(); + let currentId = null; + for (const [id] of referencesById) { + const node = nodeById.get(id); + if (node != null) { + // replace referenced IDs with the minified ones + if (minify && isIdPreserved(id) === false) { + /** @type {?string} */ + let currentIdString = null; + do { + currentId = generateId(currentId); + currentIdString = getIdString(currentId); + } while ( + isIdPreserved(currentIdString) || + (referencesById.has(currentIdString) && + nodeById.get(currentIdString) == null) + ); + idMap.set(id, currentIdString); + } + } + } + return idMap; + } + return { element: { enter: (node) => { @@ -210,43 +241,64 @@ export const fn = (_root, params) => { */ const isIdPreserved = (id) => preserveIds.has(id) || hasStringPrefix(id, preserveIdPrefixes); - /** @type {?number[]} */ - let currentId = null; + + const idMap = generateIdMap(); + const elementssWithStylesUpdated = new Set(); + for (const [id, refs] of referencesById) { + // Ignore the node if no new ID was generated for it. + if (!idMap.has(id)) { + continue; + } const node = nodeById.get(id); - if (node != null) { - // replace referenced IDs with the minified ones - if (minify && isIdPreserved(id) === false) { - /** @type {?string} */ - let currentIdString = null; - do { - currentId = generateId(currentId); - currentIdString = getIdString(currentId); - } while ( - isIdPreserved(currentIdString) || - (referencesById.has(currentIdString) && - nodeById.get(currentIdString) == null) - ); - node.attributes.id = currentIdString; - for (const { element, name } of refs) { - const value = element.attributes[name]; - if (value.includes('#')) { - // replace id in href and url() - element.attributes[name] = value - .replace(`#${encodeURI(id)}`, `#${currentIdString}`) - .replace(`#${id}`, `#${currentIdString}`); - } else { - // replace id in begin attribute - element.attributes[name] = value.replace( - `${id}.`, - `${currentIdString}.`, - ); + if (!node) { + continue; + } + + // replace referenced IDs with the minified ones + const currentIdString = idMap.get(id); + node.attributes.id = currentIdString; + + for (const { element, name } of refs) { + const value = element.attributes[name]; + if (value.includes('#')) { + if (name === 'style') { + if (!elementssWithStylesUpdated.has(element)) { + // The style may have more than one ID; all must be replaced at once to eliminate the possibility of overlap. + const styles = value.split(';'); + for (let index = 0; index < styles.length; index++) { + const style = styles[index]; + const refs = findReferences('style', style); + if (refs.length) { + const id = refs[0]; + const newId = idMap.get(id); + if (newId) { + styles[index] = style + .replace(`#${encodeURI(id)}`, `#${newId}`) + .replace(`#${id}`, `#${newId}`); + } + } + } + element.attributes[name] = styles.join(';'); + elementssWithStylesUpdated.add(element); } + } else { + // replace id in href and url() + element.attributes[name] = value + .replace(`#${encodeURI(id)}`, `#${currentIdString}`) + .replace(`#${id}`, `#${currentIdString}`); } + } else { + // replace id in begin attribute + element.attributes[name] = value.replace( + `${id}.`, + `${currentIdString}.`, + ); } - // keep referenced node - nodeById.delete(id); } + + // keep referenced node + nodeById.delete(id); } // remove non-referenced IDs attributes from elements if (remove) { diff --git a/test/plugins/cleanupIds.27.svg.txt b/test/plugins/cleanupIds.27.svg.txt new file mode 100644 index 000000000..cef1aef7a --- /dev/null +++ b/test/plugins/cleanupIds.27.svg.txt @@ -0,0 +1,36 @@ +When two IDs are referenced in the same attribute, make sure they both get changed even if the new +value of the first ID is the same as the old value of the second ID. + +=== + + + + + + + + + + + + + + + + +@@@ + + + + + + + + + + + + + + +