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