@cyberalien/svg-utils 0.0.1 → 0.0.3
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/css.d.ts +5 -0
- package/lib/helpers/hash/css.js +27 -0
- package/lib/helpers/hash/hash.d.ts +5 -0
- package/lib/helpers/hash/hash.js +18 -0
- package/lib/helpers/hash/unique.d.ts +5 -0
- package/lib/helpers/hash/unique.js +23 -0
- package/lib/helpers/misc/clone.d.ts +5 -0
- package/lib/helpers/misc/clone.js +8 -0
- package/lib/helpers/misc/compare.d.ts +9 -0
- package/lib/helpers/misc/compare.js +19 -0
- package/lib/helpers/misc/sort-object.d.ts +7 -0
- package/lib/helpers/misc/sort-object.js +21 -0
- package/lib/index.d.ts +5 -1
- package/lib/index.js +5 -1
- package/lib/svg/ids.d.ts +7 -0
- package/lib/svg/ids.js +107 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const firstChars = "abcdefghijklmnopqrstuvwxyz";
|
|
2
|
+
const chars = firstChars + "0123456789_-";
|
|
3
|
+
const firstLetterRadix = 26;
|
|
4
|
+
const letterRadix = 38;
|
|
5
|
+
/**
|
|
6
|
+
* Convert hash to a string, usable in CSS for class names and keyframes
|
|
7
|
+
*/
|
|
8
|
+
function hashToCSSString(value, limit = 8, hasPrefix = false) {
|
|
9
|
+
const result = [];
|
|
10
|
+
let num = value.shift() ?? 0;
|
|
11
|
+
if (!hasPrefix) {
|
|
12
|
+
result.push(firstChars[num % firstLetterRadix]);
|
|
13
|
+
num = Math.floor(num / firstLetterRadix);
|
|
14
|
+
}
|
|
15
|
+
while (true) {
|
|
16
|
+
while (num < 1) {
|
|
17
|
+
if (!value.length) return result.join("");
|
|
18
|
+
num = value.shift() ?? 0;
|
|
19
|
+
}
|
|
20
|
+
const isLastChar = result.length === limit - 1;
|
|
21
|
+
result.push(isLastChar ? firstChars[num % firstLetterRadix] : chars[num % letterRadix]);
|
|
22
|
+
if (result.length === limit) return result.join("");
|
|
23
|
+
num = Math.floor(num / letterRadix);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { hashToCSSString };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple hashing function, based on https://gist.github.com/jlevy/c246006675becc446360a798e2b2d781
|
|
3
|
+
*/
|
|
4
|
+
function hashString(str, seed = 0) {
|
|
5
|
+
let h1 = 3735928559 ^ seed, h2 = 1103547991 ^ seed;
|
|
6
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
7
|
+
ch = str.charCodeAt(i);
|
|
8
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
9
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
10
|
+
}
|
|
11
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
12
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
13
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
14
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
15
|
+
return [h2 >>> 0, h1 >>> 0];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { hashString };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { sortObject } from "../misc/sort-object.js";
|
|
2
|
+
import { hashString } from "./hash.js";
|
|
3
|
+
import { hashToCSSString } from "./css.js";
|
|
4
|
+
|
|
5
|
+
const uniqueHashes = Object.create(null);
|
|
6
|
+
const uniqueWithPrefixHashes = Object.create(null);
|
|
7
|
+
/**
|
|
8
|
+
* Hash an object, make sure hash is unique
|
|
9
|
+
*/
|
|
10
|
+
function uniqueCSSHash(data, hasPrefix = false) {
|
|
11
|
+
const str = JSON.stringify(sortObject(data));
|
|
12
|
+
const hash = hashToCSSString(hashString(str), 6, hasPrefix);
|
|
13
|
+
const cache = hasPrefix ? uniqueWithPrefixHashes : uniqueHashes;
|
|
14
|
+
if (!cache[hash]) cache[hash] = str;
|
|
15
|
+
else if (cache[hash] !== str) {
|
|
16
|
+
console.warn("Data 1:", cache[hash]);
|
|
17
|
+
console.warn("Data 2:", str);
|
|
18
|
+
throw new Error(`Hash collision detected: ${hash}`);
|
|
19
|
+
}
|
|
20
|
+
return hash;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { uniqueCSSHash };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare sets, returns true if identical
|
|
3
|
+
*/
|
|
4
|
+
declare function compareSets<T>(set1: Set<T> | undefined, set2: Set<T> | undefined): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Compare two values, returns true if identical
|
|
7
|
+
*/
|
|
8
|
+
declare function compareValues<T>(value1: T, value2: T): boolean;
|
|
9
|
+
export { compareSets, compareValues };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { sortObject } from "./sort-object.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compare sets, returns true if identical
|
|
5
|
+
*/
|
|
6
|
+
function compareSets(set1, set2) {
|
|
7
|
+
if (!set1 || !set2) return false;
|
|
8
|
+
if (set1.size !== set2.size) return false;
|
|
9
|
+
for (const value of set1) if (!set2.has(value)) return false;
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Compare two values, returns true if identical
|
|
14
|
+
*/
|
|
15
|
+
function compareValues(value1, value2) {
|
|
16
|
+
return value1 === value2 || JSON.stringify(sortObject(value1)) === JSON.stringify(sortObject(value2));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { compareSets, compareValues };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sort object keys to generate consistend JSON output
|
|
3
|
+
*
|
|
4
|
+
* Used to hash objects
|
|
5
|
+
*/
|
|
6
|
+
function sortObject(data) {
|
|
7
|
+
if (typeof data !== "object" || data === null) return data;
|
|
8
|
+
if (Array.isArray(data)) return data.map(sortObject);
|
|
9
|
+
if (data instanceof Set) {
|
|
10
|
+
const values = Array.from(data).map(sortObject);
|
|
11
|
+
values.sort();
|
|
12
|
+
return new Set(values);
|
|
13
|
+
}
|
|
14
|
+
const keys = Object.keys(data);
|
|
15
|
+
keys.sort();
|
|
16
|
+
const newObject = Object.create(null);
|
|
17
|
+
for (const key of keys) newObject[key] = sortObject(data[key]);
|
|
18
|
+
return newObject;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { sortObject };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { hashString } from "./helpers/hash/hash.js";
|
|
2
|
+
import { cloneObject } from "./helpers/misc/clone.js";
|
|
3
|
+
import { compareSets, compareValues } from "./helpers/misc/compare.js";
|
|
4
|
+
import { sortObject } from "./helpers/misc/sort-object.js";
|
|
1
5
|
import { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement } from "./xml/types.js";
|
|
2
6
|
import { iterateXMLContent } from "./xml/iterate.js";
|
|
3
7
|
import { parseXMLContent } from "./xml/parse.js";
|
|
4
8
|
import { stringifyXMLContent } from "./xml/stringify.js";
|
|
5
|
-
export { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement, iterateXMLContent, parseXMLContent, stringifyXMLContent };
|
|
9
|
+
export { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement, cloneObject, compareSets, compareValues, hashString, iterateXMLContent, parseXMLContent, sortObject, stringifyXMLContent };
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { cloneObject } from "./helpers/misc/clone.js";
|
|
2
|
+
import { sortObject } from "./helpers/misc/sort-object.js";
|
|
3
|
+
import { compareSets, compareValues } from "./helpers/misc/compare.js";
|
|
4
|
+
import { hashString } from "./helpers/hash/hash.js";
|
|
1
5
|
import { iterateXMLContent } from "./xml/iterate.js";
|
|
2
6
|
import { parseXMLContent } from "./xml/parse.js";
|
|
3
7
|
import { stringifyXMLContent } from "./xml/stringify.js";
|
|
4
8
|
|
|
5
|
-
export { iterateXMLContent, parseXMLContent, stringifyXMLContent };
|
|
9
|
+
export { cloneObject, compareSets, compareValues, hashString, iterateXMLContent, parseXMLContent, sortObject, stringifyXMLContent };
|
package/lib/svg/ids.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ParsedXMLTagElement } from "../xml/types.js";
|
|
2
|
+
type HashCallback = (id: string, content: string, tagName: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* Change IDs in SVG using a callback function
|
|
5
|
+
*/
|
|
6
|
+
declare function changeSVGIDs(root: ParsedXMLTagElement[], callback: HashCallback): void;
|
|
7
|
+
export { changeSVGIDs };
|
package/lib/svg/ids.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { cloneObject } from "../helpers/misc/clone.js";
|
|
2
|
+
import { iterateXMLContent } from "../xml/iterate.js";
|
|
3
|
+
import { stringifyXMLContent } from "../xml/stringify.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Change IDs in SVG using a callback function
|
|
7
|
+
*/
|
|
8
|
+
function changeSVGIDs(root, callback) {
|
|
9
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
10
|
+
const idNodes = /* @__PURE__ */ new Map();
|
|
11
|
+
const nestedIDs = /* @__PURE__ */ new Map();
|
|
12
|
+
const usage = [];
|
|
13
|
+
const parse = (replacement) => {
|
|
14
|
+
iterateXMLContent(root, (node, stack) => {
|
|
15
|
+
if (node.type !== "tag") return;
|
|
16
|
+
const attribs = node.attribs;
|
|
17
|
+
const nodeID = attribs.id;
|
|
18
|
+
if (typeof nodeID === "string") {
|
|
19
|
+
if (!replacement) {
|
|
20
|
+
if (idNodes.has(nodeID)) throw new Error(`Duplicate ID found: ${nodeID}`);
|
|
21
|
+
idNodes.set(nodeID, node);
|
|
22
|
+
idMap.set(node, nodeID);
|
|
23
|
+
} else if (nodeID === replacement[0]) attribs.id = replacement[1];
|
|
24
|
+
}
|
|
25
|
+
for (const attrib in attribs) {
|
|
26
|
+
const value = attribs[attrib];
|
|
27
|
+
if (typeof value !== "string") continue;
|
|
28
|
+
const add = (id) => {
|
|
29
|
+
usage.push({
|
|
30
|
+
node,
|
|
31
|
+
attrib,
|
|
32
|
+
id
|
|
33
|
+
});
|
|
34
|
+
[node, ...stack].forEach((node$1) => {
|
|
35
|
+
const parentID = idMap.get(node$1);
|
|
36
|
+
if (parentID) {
|
|
37
|
+
const nested = nestedIDs.get(parentID) || [];
|
|
38
|
+
if (!nested.includes(id)) {
|
|
39
|
+
nested.push(id);
|
|
40
|
+
nestedIDs.set(parentID, nested);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
switch (attrib) {
|
|
46
|
+
case "id": break;
|
|
47
|
+
case "begin":
|
|
48
|
+
case "end": {
|
|
49
|
+
const newValue = value.split(";").map((part) => {
|
|
50
|
+
const chunks = part.trim().split(".");
|
|
51
|
+
if (chunks.length < 2) return part;
|
|
52
|
+
const time = chunks[1];
|
|
53
|
+
if (time?.startsWith("begin") || time?.startsWith("end")) {
|
|
54
|
+
const id = chunks.shift();
|
|
55
|
+
if (!replacement) add(id);
|
|
56
|
+
else if (id === replacement[0]) return `${replacement[1]}.${chunks.join(".")}`;
|
|
57
|
+
}
|
|
58
|
+
return part;
|
|
59
|
+
}).join(";");
|
|
60
|
+
if (replacement) attribs[attrib] = newValue;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "href":
|
|
64
|
+
case "xlink:href":
|
|
65
|
+
if (value.startsWith("#")) {
|
|
66
|
+
const id = value.slice(1);
|
|
67
|
+
if (!replacement) add(id);
|
|
68
|
+
else if (id === replacement[0]) attribs[attrib] = `#${replacement[1]}`;
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
default: if (value.startsWith("url(#")) {
|
|
72
|
+
const id = value.slice(5, -1);
|
|
73
|
+
if (!replacement) add(id);
|
|
74
|
+
else if (id === replacement[0]) attribs[attrib] = `url(#${replacement[1]})`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
parse();
|
|
81
|
+
if (!idMap.size) return;
|
|
82
|
+
const allIDs = new Set(idMap.values());
|
|
83
|
+
const parseIDs = (parseAll = false) => {
|
|
84
|
+
const oldSize = allIDs.size;
|
|
85
|
+
for (const id of allIDs) {
|
|
86
|
+
const nested = nestedIDs.get(id)?.filter((nestedID) => nestedID !== id && allIDs.has(nestedID)) ?? [];
|
|
87
|
+
if (parseAll || !nested.length) {
|
|
88
|
+
const node = idNodes.get(id);
|
|
89
|
+
const newNode = cloneObject(node);
|
|
90
|
+
delete newNode.attribs.id;
|
|
91
|
+
for (const usageItem of usage) if (usageItem.node === node && usageItem.id === id) delete newNode.attribs[usageItem.attrib];
|
|
92
|
+
const content = stringifyXMLContent([newNode]);
|
|
93
|
+
if (!content) throw new Error(`Failed to stringify node with ID: ${id}`);
|
|
94
|
+
const newID = callback(id, content, node.tag);
|
|
95
|
+
if (newID !== id) parse([id, newID]);
|
|
96
|
+
allIDs.delete(id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return allIDs.size !== oldSize;
|
|
100
|
+
};
|
|
101
|
+
while (allIDs.size) if (!parseIDs()) {
|
|
102
|
+
parseIDs(true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
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.3",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"bugs": "https://github.com/cyberalien/svg-utils/issues",
|
|
9
9
|
"homepage": "https://cyberalien.dev/",
|