index.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. /* eslint-disable no-cond-assign, default-case */
  2. const escapeForRegexp = require('escape-string-regexp');
  3. const URL_PATTERN = /url\(#([^ ]+?)\s*\)/g;
  4. const defaultPattern = '[id]';
  5. /**
  6. * @param {string} id
  7. * @param {string|Function} pattern
  8. */
  9. function renameId(id, pattern) {
  10. const result = (typeof pattern === 'function' ? pattern(id) : pattern).toString();
  11. const re = new RegExp(escapeForRegexp('[id]'), 'g');
  12. return result.replace(re, id);
  13. }
  14. /**
  15. * @param {string|Function} [pattern]
  16. * @returns {Function}
  17. */
  18. function plugin(pattern) {
  19. const p = pattern || defaultPattern;
  20. return (tree) => {
  21. const mappedIds = {};
  22. tree.match({ attrs: { id: /.*/ } }, (node) => {
  23. const { attrs } = node;
  24. const currentId = attrs.id;
  25. const newId = renameId(currentId, p);
  26. attrs.id = newId;
  27. mappedIds[currentId] = {
  28. id: newId,
  29. referenced: false,
  30. node
  31. };
  32. return node;
  33. });
  34. tree.match({ tag: /.*/ }, (node) => {
  35. const { attrs } = node;
  36. if (node.tag === 'style') {
  37. while (true) {
  38. const content = Array.isArray(node.content) ? node.content.join('') : node.content.toString();
  39. const match = URL_PATTERN.exec(content);
  40. if (match === null) {
  41. break;
  42. }
  43. const id = match[1];
  44. if (mappedIds[id]) {
  45. mappedIds[id].referenced = true;
  46. const re = new RegExp(escapeForRegexp(match[0]), 'g');
  47. node.content = content.replace(re, `url(#${mappedIds[id].id})`);
  48. }
  49. }
  50. }
  51. if ('attrs' in node === false) {
  52. return node;
  53. }
  54. Object.keys(attrs).forEach((attrName) => {
  55. const value = attrs[attrName];
  56. let id;
  57. let match;
  58. while ((match = URL_PATTERN.exec(value)) !== null) {
  59. id = match[1];
  60. if (mappedIds[id]) {
  61. mappedIds[id].referenced = true;
  62. const re = new RegExp(escapeForRegexp(match[0]), 'g');
  63. attrs[attrName] = value.replace(re, `url(#${mappedIds[id].id})`);
  64. }
  65. }
  66. let idObj;
  67. switch (attrName) {
  68. case 'href':
  69. case 'xlink:href':
  70. if (value.substring(0, 1) !== '#') {
  71. break;
  72. }
  73. id = value.substring(1);
  74. idObj = mappedIds[id];
  75. if (idObj) {
  76. idObj.referenced = false;
  77. attrs[attrName] = `#${idObj.id}`;
  78. }
  79. break;
  80. case 'for':
  81. if (node.tag !== 'label') {
  82. break;
  83. }
  84. id = value;
  85. idObj = mappedIds[id];
  86. if (idObj) {
  87. idObj.referenced = false;
  88. attrs[attrName] = idObj.id;
  89. }
  90. break;
  91. }
  92. });
  93. return node;
  94. });
  95. return tree;
  96. };
  97. }
  98. module.exports = plugin;