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);
|
}
|