@cyberalien/svg-utils 0.0.13 → 0.0.15
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/types.d.ts +8 -0
- package/lib/helpers/hash/types.js +0 -0
- package/lib/helpers/hash/unique.d.ts +8 -8
- package/lib/helpers/hash/unique.js +16 -9
- package/lib/index.d.ts +20 -2
- package/lib/index.js +16 -1
- package/lib/svg/css/convert/content.d.ts +10 -0
- package/lib/svg/css/convert/content.js +27 -0
- package/lib/svg/css/convert/root.d.ts +9 -0
- package/lib/svg/css/convert/root.js +26 -0
- package/lib/svg/css/css/class.d.ts +6 -0
- package/lib/svg/css/css/class.js +17 -0
- package/lib/svg/css/css/const.d.ts +4 -0
- package/lib/svg/css/css/const.js +4 -0
- package/lib/svg/css/css/stringify.d.ts +10 -0
- package/lib/svg/css/css/stringify.js +26 -0
- package/lib/svg/css/css/toggle.d.ts +10 -0
- package/lib/svg/css/css/toggle.js +29 -0
- package/lib/svg/css/props/prop.d.ts +5 -0
- package/lib/svg/css/props/prop.js +65 -0
- package/lib/svg/css/props/props.d.ts +9 -0
- package/lib/svg/css/props/props.js +42 -0
- package/lib/svg/css/types.d.ts +23 -0
- package/lib/svg/css/types.js +0 -0
- package/lib/svg/ids/change.d.ts +8 -0
- package/lib/svg/{ids.js → ids/change.js} +16 -58
- package/lib/svg/ids/duplicate.d.ts +7 -0
- package/lib/svg/ids/duplicate.js +25 -0
- package/lib/svg/ids/string.d.ts +5 -0
- package/lib/svg/ids/string.js +9 -0
- package/lib/svg/ids/types.d.ts +6 -0
- package/lib/svg/ids/types.js +0 -0
- package/lib/svg/ids/unique.d.ts +6 -0
- package/lib/svg/ids/unique.js +18 -0
- package/lib/svg/ids/unused.d.ts +7 -0
- package/lib/svg/ids/unused.js +25 -0
- package/lib/xml/iterate.d.ts +1 -1
- package/lib/xml/stringify.d.ts +2 -7
- package/lib/xml/types.d.ts +9 -1
- package/package.json +1 -1
- package/lib/svg/ids.d.ts +0 -12
|
File without changes
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
prefix?: string;
|
|
3
|
-
length: number;
|
|
4
|
-
css: boolean;
|
|
5
|
-
lengths?: Record<string, number>;
|
|
6
|
-
}
|
|
1
|
+
import { UniqueHashOptions } from "./types.js";
|
|
7
2
|
/**
|
|
8
3
|
* Hash an object, make sure hash is unique
|
|
9
4
|
*
|
|
10
|
-
* Number of unique hashes per length, with prefix:
|
|
5
|
+
* Number of unique hashes per length, with prefix for CSS:
|
|
11
6
|
* 6 chars = 2b unique hashes
|
|
12
7
|
* 7 chars = 78b unique hashes <-- got collision here
|
|
13
8
|
* 8 chars = 2.9t unique hashes
|
|
14
9
|
* 9 chars = 113t unique hashes
|
|
10
|
+
*
|
|
11
|
+
* Numer of unique hashes per length, with prefix for ID:
|
|
12
|
+
* 6 chars = 47b unique hashes
|
|
13
|
+
* 7 chars = 2.9t unique hashes
|
|
14
|
+
* 8 chars = 183t unique hashes
|
|
15
15
|
*/
|
|
16
|
-
declare function getUniqueHash(data: unknown, options:
|
|
16
|
+
declare function getUniqueHash(data: unknown, options: UniqueHashOptions): string;
|
|
17
17
|
export { getUniqueHash };
|
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
import { sortObject } from "../misc/sort-object.js";
|
|
2
|
-
import { hashString } from "./hash.js";
|
|
3
2
|
import { hashToString } from "./stringify.js";
|
|
3
|
+
import { hashString } from "./hash.js";
|
|
4
4
|
|
|
5
5
|
const uniqueHashes = Object.create(null);
|
|
6
6
|
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 {
|
|
22
|
+
const { length, lengths, css } = options;
|
|
23
|
+
const prefix = options.prefix || "";
|
|
18
24
|
const str = typeof data === "string" ? data : JSON.stringify(sortObject(data));
|
|
19
25
|
const hasPrefix = !!prefix;
|
|
20
26
|
const values = hashString(str);
|
|
21
27
|
let hash = hashToString(values, css, hasPrefix, length);
|
|
22
28
|
if (lengths?.[hash]) hash = hashToString(values, css, hasPrefix, lengths[hash]);
|
|
23
29
|
const cache = hasPrefix ? uniqueWithPrefixHashes : uniqueHashes;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
throw new Error(
|
|
30
|
+
const result = `${prefix}${hash}`;
|
|
31
|
+
if (!cache[result]) cache[result] = str;
|
|
32
|
+
else if (cache[result] !== str) {
|
|
33
|
+
const msg = `Hash collision detected: ${hash}`;
|
|
34
|
+
if (options.throwOnCollision) throw new Error(msg);
|
|
35
|
+
else console.warn(msg);
|
|
29
36
|
}
|
|
30
|
-
return
|
|
37
|
+
return result;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
export { getUniqueHash };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
import { hashString } from "./helpers/hash/hash.js";
|
|
2
|
+
import { hashToString } from "./helpers/hash/stringify.js";
|
|
3
|
+
import { UniqueHashOptions } from "./helpers/hash/types.js";
|
|
4
|
+
import { getUniqueHash } from "./helpers/hash/unique.js";
|
|
2
5
|
import { cloneObject } from "./helpers/misc/clone.js";
|
|
3
6
|
import { compareSets, compareValues } from "./helpers/misc/compare.js";
|
|
4
7
|
import { sortObject } from "./helpers/misc/sort-object.js";
|
|
5
|
-
import { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement } from "./xml/types.js";
|
|
8
|
+
import { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement, StringifyXMLOptions } from "./xml/types.js";
|
|
6
9
|
import { iterateXMLContent } from "./xml/iterate.js";
|
|
7
10
|
import { parseXMLContent } from "./xml/parse.js";
|
|
8
11
|
import { stringifyXMLContent } from "./xml/stringify.js";
|
|
9
|
-
|
|
12
|
+
import { ChangeIDResult } from "./svg/ids/types.js";
|
|
13
|
+
import { changeIDInString } from "./svg/ids/string.js";
|
|
14
|
+
import { removeDuplicateIDs } from "./svg/ids/duplicate.js";
|
|
15
|
+
import { removeUnusedIDs } from "./svg/ids/unused.js";
|
|
16
|
+
import { changeSVGIDs } from "./svg/ids/change.js";
|
|
17
|
+
import { createUniqueIDs } from "./svg/ids/unique.js";
|
|
18
|
+
import { CSSRules, ConvertedSVGContent, SVGConvertedToCSSProperties, SVGPropertyType } from "./svg/css/types.js";
|
|
19
|
+
import { ClassProp, classProps, defaultClassProp } from "./svg/css/css/const.js";
|
|
20
|
+
import { createCSSClassName } from "./svg/css/css/class.js";
|
|
21
|
+
import { stringifyCSSRules, stringifyCSSSelector } from "./svg/css/css/stringify.js";
|
|
22
|
+
import { splitClassName, toggleClassName } from "./svg/css/css/toggle.js";
|
|
23
|
+
import { convertSVGPropertyToCSS } from "./svg/css/props/prop.js";
|
|
24
|
+
import { extractSVGTagPropertiesForCSS } from "./svg/css/props/props.js";
|
|
25
|
+
import { convertSVGRootToCSS } from "./svg/css/convert/root.js";
|
|
26
|
+
import { convertSVGContentToCSS } from "./svg/css/convert/content.js";
|
|
27
|
+
export { CSSRules, ChangeIDResult, ClassProp, ConvertedSVGContent, ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement, SVGConvertedToCSSProperties, SVGPropertyType, StringifyXMLOptions, UniqueHashOptions, changeIDInString, changeSVGIDs, classProps, cloneObject, compareSets, compareValues, convertSVGContentToCSS, convertSVGPropertyToCSS, convertSVGRootToCSS, createCSSClassName, createUniqueIDs, defaultClassProp, extractSVGTagPropertiesForCSS, getUniqueHash, hashString, hashToString, iterateXMLContent, parseXMLContent, removeDuplicateIDs, removeUnusedIDs, sortObject, splitClassName, stringifyCSSRules, stringifyCSSSelector, stringifyXMLContent, toggleClassName };
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { cloneObject } from "./helpers/misc/clone.js";
|
|
2
2
|
import { sortObject } from "./helpers/misc/sort-object.js";
|
|
3
3
|
import { compareSets, compareValues } from "./helpers/misc/compare.js";
|
|
4
|
+
import { hashToString } from "./helpers/hash/stringify.js";
|
|
4
5
|
import { hashString } from "./helpers/hash/hash.js";
|
|
6
|
+
import { getUniqueHash } from "./helpers/hash/unique.js";
|
|
5
7
|
import { iterateXMLContent } from "./xml/iterate.js";
|
|
6
8
|
import { parseXMLContent } from "./xml/parse.js";
|
|
7
9
|
import { stringifyXMLContent } from "./xml/stringify.js";
|
|
10
|
+
import { changeIDInString } from "./svg/ids/string.js";
|
|
11
|
+
import { removeDuplicateIDs } from "./svg/ids/duplicate.js";
|
|
12
|
+
import { removeUnusedIDs } from "./svg/ids/unused.js";
|
|
13
|
+
import { changeSVGIDs } from "./svg/ids/change.js";
|
|
14
|
+
import { createUniqueIDs } from "./svg/ids/unique.js";
|
|
15
|
+
import { classProps, defaultClassProp } from "./svg/css/css/const.js";
|
|
16
|
+
import { createCSSClassName } from "./svg/css/css/class.js";
|
|
17
|
+
import { stringifyCSSRules, stringifyCSSSelector } from "./svg/css/css/stringify.js";
|
|
18
|
+
import { splitClassName, toggleClassName } from "./svg/css/css/toggle.js";
|
|
19
|
+
import { convertSVGPropertyToCSS } from "./svg/css/props/prop.js";
|
|
20
|
+
import { extractSVGTagPropertiesForCSS } from "./svg/css/props/props.js";
|
|
21
|
+
import { convertSVGRootToCSS } from "./svg/css/convert/root.js";
|
|
22
|
+
import { convertSVGContentToCSS } from "./svg/css/convert/content.js";
|
|
8
23
|
|
|
9
|
-
export { cloneObject, compareSets, compareValues, hashString, iterateXMLContent, parseXMLContent, sortObject, stringifyXMLContent };
|
|
24
|
+
export { changeIDInString, changeSVGIDs, classProps, cloneObject, compareSets, compareValues, convertSVGContentToCSS, convertSVGPropertyToCSS, convertSVGRootToCSS, createCSSClassName, createUniqueIDs, defaultClassProp, extractSVGTagPropertiesForCSS, getUniqueHash, hashString, hashToString, iterateXMLContent, parseXMLContent, removeDuplicateIDs, removeUnusedIDs, sortObject, splitClassName, stringifyCSSRules, stringifyCSSSelector, stringifyXMLContent, toggleClassName };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { StringifyXMLOptions } from "../../../xml/types.js";
|
|
2
|
+
import { ConvertedSVGContent } from "../types.js";
|
|
3
|
+
interface Options extends StringifyXMLOptions {
|
|
4
|
+
classNamePrefix?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
*/
|
|
9
|
+
declare function convertSVGContentToCSS(content: string, options?: Options): ConvertedSVGContent;
|
|
10
|
+
export { convertSVGContentToCSS };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { parseXMLContent } from "../../../xml/parse.js";
|
|
2
|
+
import { stringifyXMLContent } from "../../../xml/stringify.js";
|
|
3
|
+
import { stringifyCSSRules } from "../css/stringify.js";
|
|
4
|
+
import { convertSVGRootToCSS } from "./root.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
*/
|
|
9
|
+
function convertSVGContentToCSS(content, options) {
|
|
10
|
+
const root = parseXMLContent(content);
|
|
11
|
+
if (!root) return { content };
|
|
12
|
+
const classNames = convertSVGRootToCSS(root, options?.classNamePrefix);
|
|
13
|
+
if (classNames) {
|
|
14
|
+
const newContent = stringifyXMLContent(root, options);
|
|
15
|
+
if (newContent) {
|
|
16
|
+
const rules = Object.create(null);
|
|
17
|
+
for (const className in classNames) rules[className] = stringifyCSSRules(classNames[className]);
|
|
18
|
+
return {
|
|
19
|
+
content: newContent,
|
|
20
|
+
rules
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { content };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { convertSVGContentToCSS };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ParsedXMLTagElement } from "../../../xml/types.js";
|
|
2
|
+
import { CSSRules } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Convert SVG tags tree to SVG+CSS
|
|
5
|
+
*
|
|
6
|
+
* Returns used CSS class names with rules
|
|
7
|
+
*/
|
|
8
|
+
declare function convertSVGRootToCSS(root: ParsedXMLTagElement[], classNamePrefix?: string): Record<string, CSSRules>;
|
|
9
|
+
export { convertSVGRootToCSS };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { iterateXMLContent } from "../../../xml/iterate.js";
|
|
2
|
+
import { createCSSClassName } from "../css/class.js";
|
|
3
|
+
import { toggleClassName } from "../css/toggle.js";
|
|
4
|
+
import { extractSVGTagPropertiesForCSS } from "../props/props.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert SVG tags tree to SVG+CSS
|
|
8
|
+
*
|
|
9
|
+
* Returns used CSS class names with rules
|
|
10
|
+
*/
|
|
11
|
+
function convertSVGRootToCSS(root, classNamePrefix = "") {
|
|
12
|
+
const rules = Object.create(null);
|
|
13
|
+
iterateXMLContent(root, (node) => {
|
|
14
|
+
if (node.type === "tag") {
|
|
15
|
+
const props = extractSVGTagPropertiesForCSS(node);
|
|
16
|
+
if (props) {
|
|
17
|
+
const className = createCSSClassName(props.rules, classNamePrefix);
|
|
18
|
+
toggleClassName(node.attribs, className, true);
|
|
19
|
+
rules[className] = props.rules;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return rules;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { convertSVGRootToCSS };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { sortObject } from "../../../helpers/misc/sort-object.js";
|
|
2
|
+
import { getUniqueHash } from "../../../helpers/hash/unique.js";
|
|
3
|
+
|
|
4
|
+
const length = 6;
|
|
5
|
+
/**
|
|
6
|
+
* Get class name for CSS rules
|
|
7
|
+
*/
|
|
8
|
+
function createCSSClassName(rules, prefix = "") {
|
|
9
|
+
const sorted = sortObject(rules);
|
|
10
|
+
return getUniqueHash(sorted, {
|
|
11
|
+
css: true,
|
|
12
|
+
length,
|
|
13
|
+
prefix
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { createCSSClassName };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CSSRules } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Stringify CSS rules
|
|
4
|
+
*/
|
|
5
|
+
declare function stringifyCSSRules(rules: CSSRules, depth?: number): string;
|
|
6
|
+
/**
|
|
7
|
+
* Stringify CSS selector with rules
|
|
8
|
+
*/
|
|
9
|
+
declare function stringifyCSSSelector(selector: string, rules: string | CSSRules, depth?: number): string;
|
|
10
|
+
export { stringifyCSSRules, stringifyCSSSelector };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function indent(depth) {
|
|
2
|
+
return " ".repeat(depth);
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Stringify CSS rules
|
|
6
|
+
*/
|
|
7
|
+
function stringifyCSSRules(rules, depth = 1) {
|
|
8
|
+
const tab = indent(depth);
|
|
9
|
+
const lines = [];
|
|
10
|
+
for (const key in rules) {
|
|
11
|
+
const value = rules[key];
|
|
12
|
+
lines.push(`${tab}${key}: ${value};\n`);
|
|
13
|
+
}
|
|
14
|
+
return lines.join("");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Stringify CSS selector with rules
|
|
18
|
+
*/
|
|
19
|
+
function stringifyCSSSelector(selector, rules, depth = 0) {
|
|
20
|
+
const content = typeof rules === "string" ? rules : stringifyCSSRules(rules, depth + 1);
|
|
21
|
+
if (!content.length) return "";
|
|
22
|
+
const tab = indent(depth);
|
|
23
|
+
return `${tab}${selector} {\n${content}${tab}}\n`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { stringifyCSSRules, stringifyCSSSelector };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ClassProp } from "./const.js";
|
|
2
|
+
/**
|
|
3
|
+
* Split class name by spaces
|
|
4
|
+
*/
|
|
5
|
+
declare function splitClassName(className: string): string[];
|
|
6
|
+
/**
|
|
7
|
+
* Add/remove class name
|
|
8
|
+
*/
|
|
9
|
+
declare function toggleClassName(attribs: Record<string, unknown>, className: string, add: boolean, prop?: ClassProp): void;
|
|
10
|
+
export { splitClassName, toggleClassName };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defaultClassProp } from "./const.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Split class name by spaces
|
|
5
|
+
*/
|
|
6
|
+
function splitClassName(className) {
|
|
7
|
+
return className.trim().split(/\s+/);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Add/remove class name
|
|
11
|
+
*/
|
|
12
|
+
function toggleClassName(attribs, className, add, prop = defaultClassProp) {
|
|
13
|
+
const oldValue = attribs[prop];
|
|
14
|
+
if (typeof oldValue !== "string") {
|
|
15
|
+
attribs[prop] = className;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const list = new Set(oldValue.split(/\s+/g));
|
|
19
|
+
if (!add) {
|
|
20
|
+
if (!list.has(className)) return;
|
|
21
|
+
list.delete(className);
|
|
22
|
+
} else {
|
|
23
|
+
if (list.has(className)) return;
|
|
24
|
+
list.add(className);
|
|
25
|
+
}
|
|
26
|
+
attribs[prop] = Array.from(list).sort().join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { splitClassName, toggleClassName };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const propTypes = [
|
|
2
|
+
"path",
|
|
3
|
+
"px",
|
|
4
|
+
"raw"
|
|
5
|
+
];
|
|
6
|
+
const commonShapes = { px: [
|
|
7
|
+
"width",
|
|
8
|
+
"height",
|
|
9
|
+
"x",
|
|
10
|
+
"y",
|
|
11
|
+
"cx",
|
|
12
|
+
"cy",
|
|
13
|
+
"r",
|
|
14
|
+
"rx",
|
|
15
|
+
"ry"
|
|
16
|
+
] };
|
|
17
|
+
const props = {
|
|
18
|
+
"*": {
|
|
19
|
+
px: ["stroke-width"],
|
|
20
|
+
raw: [
|
|
21
|
+
"fill",
|
|
22
|
+
"stroke",
|
|
23
|
+
"color",
|
|
24
|
+
"opacity"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"path": { path: ["d"] },
|
|
28
|
+
"ellipse": commonShapes,
|
|
29
|
+
"circle": commonShapes,
|
|
30
|
+
"rect": commonShapes,
|
|
31
|
+
"stop": { raw: ["stop-color", "stop-opacity"] }
|
|
32
|
+
};
|
|
33
|
+
const skipTags = [
|
|
34
|
+
"animate",
|
|
35
|
+
"animateMotion",
|
|
36
|
+
"animateTransform",
|
|
37
|
+
"set",
|
|
38
|
+
"discard"
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Get property type
|
|
42
|
+
*/
|
|
43
|
+
function getSVGPropertyType(tag, prop) {
|
|
44
|
+
if (skipTags.includes(tag)) return;
|
|
45
|
+
for (const type of propTypes) if (props[tag]?.[type]?.includes(prop) || props["*"]?.[type]?.includes(prop)) return type;
|
|
46
|
+
if (prop.startsWith("stroke") || prop.startsWith("fill")) return "raw";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Convert property to CSS
|
|
50
|
+
*/
|
|
51
|
+
function convertSVGPropertyToCSS(tag, prop, value) {
|
|
52
|
+
switch (getSVGPropertyType(tag, prop)) {
|
|
53
|
+
case "path":
|
|
54
|
+
if (typeof value !== "string") return;
|
|
55
|
+
return [prop, `path("${value.replace(/\s+/g, " ")}")`];
|
|
56
|
+
case "px":
|
|
57
|
+
if (typeof value === "string" && !value.match(/^[0-9.-]+$/)) return [prop, value];
|
|
58
|
+
return [prop, `${value}px`];
|
|
59
|
+
case "raw":
|
|
60
|
+
if (typeof value === "string" && value.startsWith("url(")) return;
|
|
61
|
+
return [prop, `${value}`];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { convertSVGPropertyToCSS };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ParsedXMLTagElement } from "../../../xml/types.js";
|
|
2
|
+
import { SVGConvertedToCSSProperties } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extract SVG tag properties that can be converted to CSS
|
|
5
|
+
*
|
|
6
|
+
* Returns CSS rules, does not add a class to tag, but does remove properties from tag
|
|
7
|
+
*/
|
|
8
|
+
declare function extractSVGTagPropertiesForCSS(tag: ParsedXMLTagElement): SVGConvertedToCSSProperties | undefined;
|
|
9
|
+
export { extractSVGTagPropertiesForCSS };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { iterateXMLContent } from "../../../xml/iterate.js";
|
|
2
|
+
import { convertSVGPropertyToCSS } from "./prop.js";
|
|
3
|
+
|
|
4
|
+
const animationTags = [
|
|
5
|
+
"animate",
|
|
6
|
+
"set",
|
|
7
|
+
"discard"
|
|
8
|
+
];
|
|
9
|
+
/**
|
|
10
|
+
* Extract SVG tag properties that can be converted to CSS
|
|
11
|
+
*
|
|
12
|
+
* Returns CSS rules, does not add a class to tag, but does remove properties from tag
|
|
13
|
+
*/
|
|
14
|
+
function extractSVGTagPropertiesForCSS(tag) {
|
|
15
|
+
const result = {
|
|
16
|
+
props: [],
|
|
17
|
+
rules: Object.create(null)
|
|
18
|
+
};
|
|
19
|
+
const animatedProps = /* @__PURE__ */ new Set();
|
|
20
|
+
iterateXMLContent(tag.children, (node) => {
|
|
21
|
+
if (node.type === "tag") {
|
|
22
|
+
if (animationTags.includes(node.tag)) {
|
|
23
|
+
const prop = node.attribs.attributeName;
|
|
24
|
+
if (typeof prop === "string") animatedProps.add(prop);
|
|
25
|
+
}
|
|
26
|
+
if (node.tag === "animateTransform") animatedProps.add("transform");
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
for (const prop in tag.attribs) if (!animatedProps.has(prop)) {
|
|
30
|
+
const value = tag.attribs[prop];
|
|
31
|
+
const converted = convertSVGPropertyToCSS(tag.tag, prop, value);
|
|
32
|
+
if (converted) {
|
|
33
|
+
const [propName, propValue] = converted;
|
|
34
|
+
result.props.push(propName);
|
|
35
|
+
result.rules[propName] = propValue;
|
|
36
|
+
delete tag.attribs[prop];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result.props.length > 0 ? result : void 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { extractSVGTagPropertiesForCSS };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG property types
|
|
3
|
+
*/
|
|
4
|
+
type SVGPropertyType = 'path' | 'px' | 'raw';
|
|
5
|
+
/**
|
|
6
|
+
* CSS rules
|
|
7
|
+
*/
|
|
8
|
+
type CSSRules = Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Result of converting SVG properties to CSS
|
|
11
|
+
*/
|
|
12
|
+
interface SVGConvertedToCSSProperties {
|
|
13
|
+
props: string[];
|
|
14
|
+
rules: CSSRules;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Result of converting SVG content to SVG+CSS
|
|
18
|
+
*/
|
|
19
|
+
interface ConvertedSVGContent {
|
|
20
|
+
content: string;
|
|
21
|
+
rules?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
export { CSSRules, ConvertedSVGContent, SVGConvertedToCSSProperties, SVGPropertyType };
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ParsedXMLTagElement } from "../../xml/types.js";
|
|
2
|
+
import { ChangeIDResult } from "./types.js";
|
|
3
|
+
type HashCallback = (id: string, content: string, tagName: string) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Change IDs in SVG using a callback function
|
|
6
|
+
*/
|
|
7
|
+
declare function changeSVGIDs(root: ParsedXMLTagElement[], callback: HashCallback): ChangeIDResult;
|
|
8
|
+
export { changeSVGIDs };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { iterateXMLContent } from "../../xml/iterate.js";
|
|
2
|
+
import { stringifyXMLContent } from "../../xml/stringify.js";
|
|
3
|
+
import { changeIDInString } from "./string.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 };
|
|
@@ -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 };
|
|
@@ -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 };
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getUniqueHash } from "../../helpers/hash/unique.js";
|
|
2
|
+
import { changeSVGIDs } from "./change.js";
|
|
3
|
+
|
|
4
|
+
const length = 8;
|
|
5
|
+
const lengths = {};
|
|
6
|
+
/**
|
|
7
|
+
* Create unique IDs for SVG elements
|
|
8
|
+
*/
|
|
9
|
+
function createUniqueIDs(root, prefix = "SVG") {
|
|
10
|
+
changeSVGIDs(root, (id, content) => getUniqueHash(content, {
|
|
11
|
+
css: false,
|
|
12
|
+
prefix,
|
|
13
|
+
length,
|
|
14
|
+
lengths
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { createUniqueIDs };
|
|
@@ -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/xml/iterate.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ import { ParsedXMLNode, ParsedXMLTagElement } from "./types.js";
|
|
|
8
8
|
* - 'abort': stop iteration
|
|
9
9
|
*/
|
|
10
10
|
type CallbackResult = void | 'remove' | 'skip' | 'abort';
|
|
11
|
-
declare function iterateXMLContent(root:
|
|
11
|
+
declare function iterateXMLContent<T extends ParsedXMLTagElement | ParsedXMLNode>(root: T[], callback: (node: ParsedXMLNode, stack: ParsedXMLTagElement[]) => CallbackResult): T[];
|
|
12
12
|
export { iterateXMLContent };
|
package/lib/xml/stringify.d.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { ParsedXMLTagElement } from "./types.js";
|
|
2
|
-
interface ParseSVGOptions {
|
|
3
|
-
useSelfClosing?: boolean;
|
|
4
|
-
numberTemplate?: string;
|
|
5
|
-
prettyPrint?: boolean | string;
|
|
6
|
-
}
|
|
1
|
+
import { ParsedXMLTagElement, StringifyXMLOptions } from "./types.js";
|
|
7
2
|
/**
|
|
8
3
|
* Convert parsed XML content to string
|
|
9
4
|
*/
|
|
10
|
-
declare function stringifyXMLContent(root: ParsedXMLTagElement[], options?:
|
|
5
|
+
declare function stringifyXMLContent(root: ParsedXMLTagElement[], options?: StringifyXMLOptions): string | null;
|
|
11
6
|
export { stringifyXMLContent };
|
package/lib/xml/types.d.ts
CHANGED
|
@@ -18,4 +18,12 @@ interface ParsedXMLTextElement {
|
|
|
18
18
|
* Element in tree
|
|
19
19
|
*/
|
|
20
20
|
type ParsedXMLNode = ParsedXMLTagElement | ParsedXMLTextElement;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Options for stringifying XML
|
|
23
|
+
*/
|
|
24
|
+
interface StringifyXMLOptions {
|
|
25
|
+
useSelfClosing?: boolean;
|
|
26
|
+
numberTemplate?: string;
|
|
27
|
+
prettyPrint?: boolean | string;
|
|
28
|
+
}
|
|
29
|
+
export { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement, StringifyXMLOptions };
|
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.15",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"bugs": "https://github.com/cyberalien/svg-utils/issues",
|
|
9
9
|
"homepage": "https://cyberalien.dev/",
|
package/lib/svg/ids.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { ParsedXMLTagElement } from "../xml/types.js";
|
|
2
|
-
type HashCallback = (id: string, content: string, tagName: string) => string;
|
|
3
|
-
type Result = Record<string, ParsedXMLTagElement[]>;
|
|
4
|
-
/**
|
|
5
|
-
* Change IDs in SVG using a callback function
|
|
6
|
-
*/
|
|
7
|
-
declare function changeSVGIDs(root: ParsedXMLTagElement[], callback: HashCallback): Result;
|
|
8
|
-
/**
|
|
9
|
-
* Remove duplicate IDs from SVG
|
|
10
|
-
*/
|
|
11
|
-
declare function removeDuplicateIDs(root: ParsedXMLTagElement[], data: Result): ParsedXMLTagElement[];
|
|
12
|
-
export { changeSVGIDs, removeDuplicateIDs };
|