@cyberalien/svg-utils 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/helpers/hash/stringify.d.ts +1 -1
- package/lib/helpers/hash/stringify.js +8 -5
- package/lib/helpers/hash/unique.d.ts +7 -2
- package/lib/helpers/hash/unique.js +9 -4
- package/lib/index.d.ts +6 -1
- package/lib/index.js +5 -1
- package/lib/svg/ids/change.d.ts +5 -0
- package/lib/svg/ids/change.js +9 -0
- package/lib/svg/ids/duplicate.d.ts +7 -0
- package/lib/svg/ids/duplicate.js +25 -0
- package/lib/svg/ids/types.d.ts +6 -0
- package/lib/svg/ids/types.js +0 -0
- package/lib/svg/ids/unused.d.ts +7 -0
- package/lib/svg/ids/unused.js +25 -0
- package/lib/svg/ids.d.ts +3 -7
- package/lib/svg/ids.js +14 -56
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Convert hash to a string, usable in CSS for class names and keyframes
|
|
3
3
|
*/
|
|
4
|
-
declare function hashToString(value: number[], hasPrefix?: boolean, limit?: number
|
|
4
|
+
declare function hashToString(value: number[], css: boolean, hasPrefix?: boolean, limit?: number): string;
|
|
5
5
|
export { hashToString };
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const firstCSSChars = "abcdefghijklmnopqrstuvwxyz";
|
|
2
|
+
const firstIDChars = firstCSSChars + firstCSSChars.toUpperCase();
|
|
3
|
+
const numChars = "0123456789";
|
|
4
|
+
const allCSSChars = firstCSSChars + numChars + "-_";
|
|
5
|
+
const allIDChars = firstIDChars + numChars;
|
|
3
6
|
/**
|
|
4
7
|
* Convert hash to a string, usable in CSS for class names and keyframes
|
|
5
8
|
*/
|
|
6
|
-
function hashToString(value, hasPrefix = true, limit = 8
|
|
7
|
-
const firstChars =
|
|
8
|
-
const chars =
|
|
9
|
+
function hashToString(value, css, hasPrefix = true, limit = 8) {
|
|
10
|
+
const firstChars = css ? firstCSSChars : firstIDChars;
|
|
11
|
+
const chars = css ? allCSSChars : allIDChars;
|
|
9
12
|
const firstLetterRadix = firstChars.length;
|
|
10
13
|
const letterRadix = chars.length;
|
|
11
14
|
const result = [];
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
interface Options {
|
|
2
2
|
prefix?: string;
|
|
3
3
|
length: number;
|
|
4
|
-
|
|
4
|
+
css: boolean;
|
|
5
5
|
lengths?: Record<string, number>;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Hash an object, make sure hash is unique
|
|
9
9
|
*
|
|
10
|
-
* Number of unique hashes per length, with prefix:
|
|
10
|
+
* Number of unique hashes per length, with prefix for CSS:
|
|
11
11
|
* 6 chars = 2b unique hashes
|
|
12
12
|
* 7 chars = 78b unique hashes <-- got collision here
|
|
13
13
|
* 8 chars = 2.9t unique hashes
|
|
14
14
|
* 9 chars = 113t unique hashes
|
|
15
|
+
*
|
|
16
|
+
* Numer of unique hashes per length, with prefix for ID:
|
|
17
|
+
* 6 chars = 47b unique hashes
|
|
18
|
+
* 7 chars = 2.9t unique hashes
|
|
19
|
+
* 8 chars = 183t unique hashes
|
|
15
20
|
*/
|
|
16
21
|
declare function getUniqueHash(data: unknown, options: Options): string;
|
|
17
22
|
export { getUniqueHash };
|
|
@@ -7,19 +7,24 @@ const uniqueWithPrefixHashes = Object.create(null);
|
|
|
7
7
|
/**
|
|
8
8
|
* Hash an object, make sure hash is unique
|
|
9
9
|
*
|
|
10
|
-
* Number of unique hashes per length, with prefix:
|
|
10
|
+
* Number of unique hashes per length, with prefix for CSS:
|
|
11
11
|
* 6 chars = 2b unique hashes
|
|
12
12
|
* 7 chars = 78b unique hashes <-- got collision here
|
|
13
13
|
* 8 chars = 2.9t unique hashes
|
|
14
14
|
* 9 chars = 113t unique hashes
|
|
15
|
+
*
|
|
16
|
+
* Numer of unique hashes per length, with prefix for ID:
|
|
17
|
+
* 6 chars = 47b unique hashes
|
|
18
|
+
* 7 chars = 2.9t unique hashes
|
|
19
|
+
* 8 chars = 183t unique hashes
|
|
15
20
|
*/
|
|
16
21
|
function getUniqueHash(data, options) {
|
|
17
|
-
const { prefix, length, lengths,
|
|
22
|
+
const { prefix, length, lengths, css } = options;
|
|
18
23
|
const str = typeof data === "string" ? data : JSON.stringify(sortObject(data));
|
|
19
24
|
const hasPrefix = !!prefix;
|
|
20
25
|
const values = hashString(str);
|
|
21
|
-
let hash = hashToString(values, hasPrefix, length
|
|
22
|
-
if (lengths?.[hash]) hash = hashToString(values, hasPrefix, lengths[hash]
|
|
26
|
+
let hash = hashToString(values, css, hasPrefix, length);
|
|
27
|
+
if (lengths?.[hash]) hash = hashToString(values, css, hasPrefix, lengths[hash]);
|
|
23
28
|
const cache = hasPrefix ? uniqueWithPrefixHashes : uniqueHashes;
|
|
24
29
|
if (!cache[hash]) cache[hash] = str;
|
|
25
30
|
else if (cache[hash] !== str) {
|
package/lib/index.d.ts
CHANGED
|
@@ -6,4 +6,9 @@ import { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement } from "./xml/
|
|
|
6
6
|
import { iterateXMLContent } from "./xml/iterate.js";
|
|
7
7
|
import { parseXMLContent } from "./xml/parse.js";
|
|
8
8
|
import { stringifyXMLContent } from "./xml/stringify.js";
|
|
9
|
-
|
|
9
|
+
import { ChangeIDResult } from "./svg/ids/types.js";
|
|
10
|
+
import { changeIDInString } from "./svg/ids/change.js";
|
|
11
|
+
import { removeDuplicateIDs } from "./svg/ids/duplicate.js";
|
|
12
|
+
import { removeUnusedIDs } from "./svg/ids/unused.js";
|
|
13
|
+
import { changeSVGIDs } from "./svg/ids.js";
|
|
14
|
+
export { ChangeIDResult, ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement, changeIDInString, changeSVGIDs, cloneObject, compareSets, compareValues, hashString, iterateXMLContent, parseXMLContent, removeDuplicateIDs, removeUnusedIDs, sortObject, stringifyXMLContent };
|
package/lib/index.js
CHANGED
|
@@ -5,5 +5,9 @@ import { hashString } from "./helpers/hash/hash.js";
|
|
|
5
5
|
import { iterateXMLContent } from "./xml/iterate.js";
|
|
6
6
|
import { parseXMLContent } from "./xml/parse.js";
|
|
7
7
|
import { stringifyXMLContent } from "./xml/stringify.js";
|
|
8
|
+
import { changeIDInString } from "./svg/ids/change.js";
|
|
9
|
+
import { removeDuplicateIDs } from "./svg/ids/duplicate.js";
|
|
10
|
+
import { removeUnusedIDs } from "./svg/ids/unused.js";
|
|
11
|
+
import { changeSVGIDs } from "./svg/ids.js";
|
|
8
12
|
|
|
9
|
-
export { cloneObject, compareSets, compareValues, hashString, iterateXMLContent, parseXMLContent, sortObject, stringifyXMLContent };
|
|
13
|
+
export { changeIDInString, changeSVGIDs, cloneObject, compareSets, compareValues, hashString, iterateXMLContent, parseXMLContent, removeDuplicateIDs, removeUnusedIDs, sortObject, stringifyXMLContent };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change ID in a string
|
|
3
|
+
*/
|
|
4
|
+
function changeIDInString(value, oldID, newID) {
|
|
5
|
+
const escapedID = oldID.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6
|
+
return value.replace(new RegExp("([#;\"])(" + escapedID + ")([\")]|\\.[a-z])", "g"), "$1" + newID + "$3");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { changeIDInString };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ParsedXMLTagElement } from "../../xml/types.js";
|
|
2
|
+
import { ChangeIDResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Remove duplicate IDs from SVG
|
|
5
|
+
*/
|
|
6
|
+
declare function removeDuplicateIDs(root: ParsedXMLTagElement[], data: ChangeIDResult): ParsedXMLTagElement[];
|
|
7
|
+
export { removeDuplicateIDs };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { iterateXMLContent } from "../../xml/iterate.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remove duplicate IDs from SVG
|
|
5
|
+
*/
|
|
6
|
+
function removeDuplicateIDs(root, data) {
|
|
7
|
+
const remove = /* @__PURE__ */ new Set();
|
|
8
|
+
for (const id in data.map) {
|
|
9
|
+
const nodes = data.map[id];
|
|
10
|
+
if (nodes.length > 1) remove.add(id);
|
|
11
|
+
}
|
|
12
|
+
if (remove.size) {
|
|
13
|
+
const removing = /* @__PURE__ */ new Set();
|
|
14
|
+
return iterateXMLContent(root, (node) => {
|
|
15
|
+
if (node.type !== "tag") return;
|
|
16
|
+
const id = node.attribs.id;
|
|
17
|
+
if (typeof id !== "string" || !remove.has(id)) return;
|
|
18
|
+
if (removing.has(id)) return "remove";
|
|
19
|
+
removing.add(id);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return root;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { removeDuplicateIDs };
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ParsedXMLTagElement } from "../../xml/types.js";
|
|
2
|
+
import { ChangeIDResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Remove duplicate IDs from SVG
|
|
5
|
+
*/
|
|
6
|
+
declare function removeUnusedIDs(root: ParsedXMLTagElement[], data: ChangeIDResult): ParsedXMLTagElement[];
|
|
7
|
+
export { removeUnusedIDs };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { iterateXMLContent } from "../../xml/iterate.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remove duplicate IDs from SVG
|
|
5
|
+
*/
|
|
6
|
+
function removeUnusedIDs(root, data) {
|
|
7
|
+
const remove = /* @__PURE__ */ new Set();
|
|
8
|
+
for (const id in data.usage) if (!data.usage[id].length) remove.add(id);
|
|
9
|
+
if (remove.size) return iterateXMLContent(root, (node, stack) => {
|
|
10
|
+
if (node.type !== "tag") return;
|
|
11
|
+
const id = node.attribs.id;
|
|
12
|
+
if (typeof id !== "string" || !remove.has(id)) return;
|
|
13
|
+
switch (node.tag) {
|
|
14
|
+
case "mask":
|
|
15
|
+
case "clipPath":
|
|
16
|
+
case "symbol": return "remove";
|
|
17
|
+
}
|
|
18
|
+
const parentNode = stack[stack.length - 1];
|
|
19
|
+
if (parentNode?.tag === "defs") return "remove";
|
|
20
|
+
delete node.attribs.id;
|
|
21
|
+
});
|
|
22
|
+
return root;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { removeUnusedIDs };
|
package/lib/svg/ids.d.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { ParsedXMLTagElement } from "../xml/types.js";
|
|
2
|
+
import { ChangeIDResult } from "./ids/types.js";
|
|
2
3
|
type HashCallback = (id: string, content: string, tagName: string) => string;
|
|
3
|
-
type Result = Record<string, ParsedXMLTagElement[]>;
|
|
4
4
|
/**
|
|
5
5
|
* Change IDs in SVG using a callback function
|
|
6
6
|
*/
|
|
7
|
-
declare function changeSVGIDs(root: ParsedXMLTagElement[], callback: HashCallback):
|
|
8
|
-
|
|
9
|
-
* Remove duplicate IDs from SVG
|
|
10
|
-
*/
|
|
11
|
-
declare function removeDuplicateIDs(root: ParsedXMLTagElement[], data: Result): ParsedXMLTagElement[];
|
|
12
|
-
export { changeSVGIDs, removeDuplicateIDs };
|
|
7
|
+
declare function changeSVGIDs(root: ParsedXMLTagElement[], callback: HashCallback): ChangeIDResult;
|
|
8
|
+
export { changeSVGIDs };
|
package/lib/svg/ids.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { cloneObject } from "../helpers/misc/clone.js";
|
|
2
1
|
import { iterateXMLContent } from "../xml/iterate.js";
|
|
3
2
|
import { stringifyXMLContent } from "../xml/stringify.js";
|
|
3
|
+
import { changeIDInString } from "./ids/change.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Change IDs in SVG using a callback function
|
|
@@ -9,7 +9,10 @@ function changeSVGIDs(root, callback) {
|
|
|
9
9
|
const idMap = /* @__PURE__ */ new Map();
|
|
10
10
|
const idNodes = /* @__PURE__ */ new Map();
|
|
11
11
|
const nestedIDs = /* @__PURE__ */ new Map();
|
|
12
|
-
const results =
|
|
12
|
+
const results = {
|
|
13
|
+
map: Object.create(null),
|
|
14
|
+
usage: Object.create(null)
|
|
15
|
+
};
|
|
13
16
|
const usage = [];
|
|
14
17
|
const parse = (replacement) => {
|
|
15
18
|
iterateXMLContent(root, (node, stack) => {
|
|
@@ -24,8 +27,8 @@ function changeSVGIDs(root, callback) {
|
|
|
24
27
|
} else if (nodeID === replacement[0]) {
|
|
25
28
|
const newID = replacement[1];
|
|
26
29
|
attribs.id = newID;
|
|
27
|
-
if (!results[newID]) results[newID] = [node];
|
|
28
|
-
else results[newID].push(node);
|
|
30
|
+
if (!results.map[newID]) results.map[newID] = [node];
|
|
31
|
+
else results.map[newID].push(node);
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
for (const attrib in attribs) {
|
|
@@ -92,39 +95,15 @@ function changeSVGIDs(root, callback) {
|
|
|
92
95
|
const nested = nestedIDs.get(id)?.filter((nestedID) => nestedID !== id && allIDs.has(nestedID)) ?? [];
|
|
93
96
|
if (parseAll || !nested.length) {
|
|
94
97
|
const node = idNodes.get(id);
|
|
95
|
-
const
|
|
96
|
-
delete newNode.attribs.id;
|
|
97
|
-
for (const usageItem of usage) if (usageItem.node === node && usageItem.id === id) {
|
|
98
|
-
const attr = usageItem.attrib;
|
|
99
|
-
const value = newNode.attribs[attr];
|
|
100
|
-
if (value === id) newNode.attribs[attr] = "";
|
|
101
|
-
else {
|
|
102
|
-
const parts = value.split(id);
|
|
103
|
-
const newValue = [];
|
|
104
|
-
const total = parts.length;
|
|
105
|
-
for (let i = 0; i < total; i++) {
|
|
106
|
-
if (i > 0) {
|
|
107
|
-
newValue.push(parts[i - 1]);
|
|
108
|
-
if (parts[i - i].slice(-1) !== ";") {
|
|
109
|
-
newValue.push(id);
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (i < total - 1) {
|
|
114
|
-
if (parts[i].slice(0, 1) !== ".") {
|
|
115
|
-
newValue.push(parts[i]);
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
newNode.attribs[attr] = newValue.join("");
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const content = stringifyXMLContent([newNode]);
|
|
98
|
+
const content = stringifyXMLContent([node]);
|
|
124
99
|
if (!content) throw new Error(`Failed to stringify node with ID: ${id}`);
|
|
125
|
-
const
|
|
100
|
+
const cleanedContent = changeIDInString(content, id, "{id}");
|
|
101
|
+
const newID = callback(id, cleanedContent, node.tag);
|
|
126
102
|
if (newID !== id) parse([id, newID]);
|
|
127
103
|
allIDs.delete(id);
|
|
104
|
+
const idUsage = [];
|
|
105
|
+
for (const item of usage) if (item.id === id) idUsage.push(item.node);
|
|
106
|
+
results.usage[newID] = idUsage;
|
|
128
107
|
}
|
|
129
108
|
}
|
|
130
109
|
return allIDs.size !== oldSize;
|
|
@@ -135,26 +114,5 @@ function changeSVGIDs(root, callback) {
|
|
|
135
114
|
}
|
|
136
115
|
return results;
|
|
137
116
|
}
|
|
138
|
-
/**
|
|
139
|
-
* Remove duplicate IDs from SVG
|
|
140
|
-
*/
|
|
141
|
-
function removeDuplicateIDs(root, data) {
|
|
142
|
-
const remove = /* @__PURE__ */ new Set();
|
|
143
|
-
for (const id in data) {
|
|
144
|
-
const nodes = data[id];
|
|
145
|
-
if (nodes.length > 1) remove.add(id);
|
|
146
|
-
}
|
|
147
|
-
if (remove.size) {
|
|
148
|
-
const removing = /* @__PURE__ */ new Set();
|
|
149
|
-
return iterateXMLContent(root, (node) => {
|
|
150
|
-
if (node.type !== "tag") return;
|
|
151
|
-
const id = node.attribs.id;
|
|
152
|
-
if (typeof id !== "string" || !remove.has(id)) return;
|
|
153
|
-
if (removing.has(id)) return "remove";
|
|
154
|
-
removing.add(id);
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
return root;
|
|
158
|
-
}
|
|
159
117
|
|
|
160
|
-
export { changeSVGIDs
|
|
118
|
+
export { changeSVGIDs };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "Common functions for working with SVG used by various packages.",
|
|
5
5
|
"author": "Vjacheslav Trushkin",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.14",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"bugs": "https://github.com/cyberalien/svg-utils/issues",
|
|
9
9
|
"homepage": "https://cyberalien.dev/",
|