import namespaces from 'svg-baker/namespaces'; import selectAttributes from './select-attributes'; import arrayFrom from './array-from'; const xLinkNS = namespaces.xlink.uri; const xLinkAttrName = 'xlink:href'; // eslint-disable-next-line no-useless-escape const specialUrlCharsPattern = /[{}|\\\^\[\]`"<>]/g; function encoder(url) { return url.replace(specialUrlCharsPattern, (match) => { return `%${match[0].charCodeAt(0).toString(16).toUpperCase()}`; }); } function escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string } /** * @param {NodeList} nodes * @param {string} startsWith * @param {string} replaceWith * @return {NodeList} */ function updateReferences(nodes, startsWith, replaceWith) { arrayFrom(nodes).forEach((node) => { const href = node.getAttribute(xLinkAttrName); if (href && href.indexOf(startsWith) === 0) { const newUrl = href.replace(startsWith, replaceWith); node.setAttributeNS(xLinkNS, xLinkAttrName, newUrl); } }); return nodes; } /** * List of SVG attributes to update url() target in them */ const attList = [ 'clipPath', 'colorProfile', 'src', 'cursor', 'fill', 'filter', 'marker', 'markerStart', 'markerMid', 'markerEnd', 'mask', 'stroke', 'style' ]; const attSelector = attList.map(attr => `[${attr}]`).join(','); /** * Update URLs in svg image (like `fill="url(...)"`) and update referencing elements * @param {Element} svg * @param {NodeList} references * @param {string|RegExp} startsWith * @param {string} replaceWith * @return {void} * * @example * const sprite = document.querySelector('svg.sprite'); * const usages = document.querySelectorAll('use'); * updateUrls(sprite, usages, '#', 'prefix#'); */ export default function (svg, references, startsWith, replaceWith) { const startsWithEncoded = encoder(startsWith); const replaceWithEncoded = encoder(replaceWith); const nodes = svg.querySelectorAll(attSelector); const attrs = selectAttributes(nodes, ({ localName, value }) => { return attList.indexOf(localName) !== -1 && value.indexOf(`url(${startsWithEncoded}`) !== -1; }); attrs.forEach(attr => attr.value = attr.value.replace(new RegExp(escapeRegExp(startsWithEncoded), 'g'), replaceWithEncoded)); updateReferences(references, startsWithEncoded, replaceWithEncoded); }