c41a1c74b267a765e827ccf98cc62e72f20a75625e1c55e0492db38a5be5eb0cdcdeec96a82893080adce53e7df721c6cc007e561aeb2f4f2ee82fb8e001e2 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import namespaces from 'svg-baker/namespaces';
  2. import selectAttributes from './select-attributes';
  3. import arrayFrom from './array-from';
  4. const xLinkNS = namespaces.xlink.uri;
  5. const xLinkAttrName = 'xlink:href';
  6. // eslint-disable-next-line no-useless-escape
  7. const specialUrlCharsPattern = /[{}|\\\^\[\]`"<>]/g;
  8. function encoder(url) {
  9. return url.replace(specialUrlCharsPattern, (match) => {
  10. return `%${match[0].charCodeAt(0).toString(16).toUpperCase()}`;
  11. });
  12. }
  13. function escapeRegExp(str) {
  14. return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
  15. }
  16. /**
  17. * @param {NodeList} nodes
  18. * @param {string} startsWith
  19. * @param {string} replaceWith
  20. * @return {NodeList}
  21. */
  22. function updateReferences(nodes, startsWith, replaceWith) {
  23. arrayFrom(nodes).forEach((node) => {
  24. const href = node.getAttribute(xLinkAttrName);
  25. if (href && href.indexOf(startsWith) === 0) {
  26. const newUrl = href.replace(startsWith, replaceWith);
  27. node.setAttributeNS(xLinkNS, xLinkAttrName, newUrl);
  28. }
  29. });
  30. return nodes;
  31. }
  32. /**
  33. * List of SVG attributes to update url() target in them
  34. */
  35. const attList = [
  36. 'clipPath',
  37. 'colorProfile',
  38. 'src',
  39. 'cursor',
  40. 'fill',
  41. 'filter',
  42. 'marker',
  43. 'markerStart',
  44. 'markerMid',
  45. 'markerEnd',
  46. 'mask',
  47. 'stroke',
  48. 'style'
  49. ];
  50. const attSelector = attList.map(attr => `[${attr}]`).join(',');
  51. /**
  52. * Update URLs in svg image (like `fill="url(...)"`) and update referencing elements
  53. * @param {Element} svg
  54. * @param {NodeList} references
  55. * @param {string|RegExp} startsWith
  56. * @param {string} replaceWith
  57. * @return {void}
  58. *
  59. * @example
  60. * const sprite = document.querySelector('svg.sprite');
  61. * const usages = document.querySelectorAll('use');
  62. * updateUrls(sprite, usages, '#', 'prefix#');
  63. */
  64. export default function (svg, references, startsWith, replaceWith) {
  65. const startsWithEncoded = encoder(startsWith);
  66. const replaceWithEncoded = encoder(replaceWith);
  67. const nodes = svg.querySelectorAll(attSelector);
  68. const attrs = selectAttributes(nodes, ({ localName, value }) => {
  69. return attList.indexOf(localName) !== -1 && value.indexOf(`url(${startsWithEncoded}`) !== -1;
  70. });
  71. attrs.forEach(attr => attr.value = attr.value.replace(new RegExp(escapeRegExp(startsWithEncoded), 'g'), replaceWithEncoded));
  72. updateReferences(references, startsWithEncoded, replaceWithEncoded);
  73. }