@cyberalien/svg-utils 0.0.14 → 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 +2 -7
- package/lib/helpers/hash/unique.js +10 -8
- package/lib/index.d.ts +17 -4
- package/lib/index.js +14 -3
- 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 +6 -3
- package/lib/svg/ids/change.js +114 -5
- package/lib/svg/ids/string.d.ts +5 -0
- package/lib/svg/ids/string.js +9 -0
- package/lib/svg/ids/unique.d.ts +6 -0
- package/lib/svg/ids/unique.js +18 -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 -8
- package/lib/svg/ids.js +0 -118
|
File without changes
|
|
@@ -1,9 +1,4 @@
|
|
|
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
|
*
|
|
@@ -18,5 +13,5 @@ interface Options {
|
|
|
18
13
|
* 7 chars = 2.9t unique hashes
|
|
19
14
|
* 8 chars = 183t unique hashes
|
|
20
15
|
*/
|
|
21
|
-
declare function getUniqueHash(data: unknown, options:
|
|
16
|
+
declare function getUniqueHash(data: unknown, options: UniqueHashOptions): string;
|
|
22
17
|
export { getUniqueHash };
|
|
@@ -1,6 +1,6 @@
|
|
|
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);
|
|
@@ -19,20 +19,22 @@ const uniqueWithPrefixHashes = Object.create(null);
|
|
|
19
19
|
* 8 chars = 183t unique hashes
|
|
20
20
|
*/
|
|
21
21
|
function getUniqueHash(data, options) {
|
|
22
|
-
const {
|
|
22
|
+
const { length, lengths, css } = options;
|
|
23
|
+
const prefix = options.prefix || "";
|
|
23
24
|
const str = typeof data === "string" ? data : JSON.stringify(sortObject(data));
|
|
24
25
|
const hasPrefix = !!prefix;
|
|
25
26
|
const values = hashString(str);
|
|
26
27
|
let hash = hashToString(values, css, hasPrefix, length);
|
|
27
28
|
if (lengths?.[hash]) hash = hashToString(values, css, hasPrefix, lengths[hash]);
|
|
28
29
|
const cache = hasPrefix ? uniqueWithPrefixHashes : uniqueHashes;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
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);
|
|
34
36
|
}
|
|
35
|
-
return
|
|
37
|
+
return result;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export { getUniqueHash };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,14 +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";
|
|
10
|
-
import { changeIDInString } from "./svg/ids/
|
|
13
|
+
import { changeIDInString } from "./svg/ids/string.js";
|
|
11
14
|
import { removeDuplicateIDs } from "./svg/ids/duplicate.js";
|
|
12
15
|
import { removeUnusedIDs } from "./svg/ids/unused.js";
|
|
13
|
-
import { changeSVGIDs } from "./svg/ids.js";
|
|
14
|
-
|
|
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,13 +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";
|
|
8
|
-
import { changeIDInString } from "./svg/ids/
|
|
10
|
+
import { changeIDInString } from "./svg/ids/string.js";
|
|
9
11
|
import { removeDuplicateIDs } from "./svg/ids/duplicate.js";
|
|
10
12
|
import { removeUnusedIDs } from "./svg/ids/unused.js";
|
|
11
|
-
import { changeSVGIDs } from "./svg/ids.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";
|
|
12
23
|
|
|
13
|
-
export { changeIDInString, changeSVGIDs, cloneObject, compareSets, compareValues, hashString, iterateXMLContent, parseXMLContent, removeDuplicateIDs, removeUnusedIDs, 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
|
package/lib/svg/ids/change.d.ts
CHANGED
|
@@ -1,5 +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;
|
|
1
4
|
/**
|
|
2
|
-
* Change
|
|
5
|
+
* Change IDs in SVG using a callback function
|
|
3
6
|
*/
|
|
4
|
-
declare function
|
|
5
|
-
export {
|
|
7
|
+
declare function changeSVGIDs(root: ParsedXMLTagElement[], callback: HashCallback): ChangeIDResult;
|
|
8
|
+
export { changeSVGIDs };
|
package/lib/svg/ids/change.js
CHANGED
|
@@ -1,9 +1,118 @@
|
|
|
1
|
+
import { iterateXMLContent } from "../../xml/iterate.js";
|
|
2
|
+
import { stringifyXMLContent } from "../../xml/stringify.js";
|
|
3
|
+
import { changeIDInString } from "./string.js";
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
|
-
* Change
|
|
6
|
+
* Change IDs in SVG using a callback function
|
|
3
7
|
*/
|
|
4
|
-
function
|
|
5
|
-
const
|
|
6
|
-
|
|
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 results = {
|
|
13
|
+
map: Object.create(null),
|
|
14
|
+
usage: Object.create(null)
|
|
15
|
+
};
|
|
16
|
+
const usage = [];
|
|
17
|
+
const parse = (replacement) => {
|
|
18
|
+
iterateXMLContent(root, (node, stack) => {
|
|
19
|
+
if (node.type !== "tag") return;
|
|
20
|
+
const attribs = node.attribs;
|
|
21
|
+
const nodeID = attribs.id;
|
|
22
|
+
if (typeof nodeID === "string") {
|
|
23
|
+
if (!replacement) {
|
|
24
|
+
if (idNodes.has(nodeID)) throw new Error(`Duplicate ID found: ${nodeID}`);
|
|
25
|
+
idNodes.set(nodeID, node);
|
|
26
|
+
idMap.set(node, nodeID);
|
|
27
|
+
} else if (nodeID === replacement[0]) {
|
|
28
|
+
const newID = replacement[1];
|
|
29
|
+
attribs.id = newID;
|
|
30
|
+
if (!results.map[newID]) results.map[newID] = [node];
|
|
31
|
+
else results.map[newID].push(node);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const attrib in attribs) {
|
|
35
|
+
const value = attribs[attrib];
|
|
36
|
+
if (typeof value !== "string") continue;
|
|
37
|
+
const add = (id) => {
|
|
38
|
+
usage.push({
|
|
39
|
+
node,
|
|
40
|
+
attrib,
|
|
41
|
+
id
|
|
42
|
+
});
|
|
43
|
+
[node, ...stack].forEach((node$1) => {
|
|
44
|
+
const parentID = idMap.get(node$1);
|
|
45
|
+
if (parentID) {
|
|
46
|
+
const nested = nestedIDs.get(parentID) || [];
|
|
47
|
+
if (!nested.includes(id)) {
|
|
48
|
+
nested.push(id);
|
|
49
|
+
nestedIDs.set(parentID, nested);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
switch (attrib) {
|
|
55
|
+
case "id": break;
|
|
56
|
+
case "begin":
|
|
57
|
+
case "end": {
|
|
58
|
+
const newValue = value.split(";").map((part) => {
|
|
59
|
+
const chunks = part.trim().split(".");
|
|
60
|
+
if (chunks.length < 2) return part;
|
|
61
|
+
const time = chunks[1];
|
|
62
|
+
if (time?.startsWith("begin") || time?.startsWith("end")) {
|
|
63
|
+
const id = chunks.shift();
|
|
64
|
+
if (!replacement) add(id);
|
|
65
|
+
else if (id === replacement[0]) return `${replacement[1]}.${chunks.join(".")}`;
|
|
66
|
+
}
|
|
67
|
+
return part;
|
|
68
|
+
}).join(";");
|
|
69
|
+
if (replacement) attribs[attrib] = newValue;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "href":
|
|
73
|
+
case "xlink:href":
|
|
74
|
+
if (value.startsWith("#")) {
|
|
75
|
+
const id = value.slice(1);
|
|
76
|
+
if (!replacement) add(id);
|
|
77
|
+
else if (id === replacement[0]) attribs[attrib] = `#${replacement[1]}`;
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
default: if (value.startsWith("url(#")) {
|
|
81
|
+
const id = value.slice(5, -1);
|
|
82
|
+
if (!replacement) add(id);
|
|
83
|
+
else if (id === replacement[0]) attribs[attrib] = `url(#${replacement[1]})`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
parse();
|
|
90
|
+
if (!idMap.size) return results;
|
|
91
|
+
const allIDs = new Set(idMap.values());
|
|
92
|
+
const parseIDs = (parseAll = false) => {
|
|
93
|
+
const oldSize = allIDs.size;
|
|
94
|
+
for (const id of allIDs) {
|
|
95
|
+
const nested = nestedIDs.get(id)?.filter((nestedID) => nestedID !== id && allIDs.has(nestedID)) ?? [];
|
|
96
|
+
if (parseAll || !nested.length) {
|
|
97
|
+
const node = idNodes.get(id);
|
|
98
|
+
const content = stringifyXMLContent([node]);
|
|
99
|
+
if (!content) throw new Error(`Failed to stringify node with ID: ${id}`);
|
|
100
|
+
const cleanedContent = changeIDInString(content, id, "{id}");
|
|
101
|
+
const newID = callback(id, cleanedContent, node.tag);
|
|
102
|
+
if (newID !== id) parse([id, newID]);
|
|
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;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return allIDs.size !== oldSize;
|
|
110
|
+
};
|
|
111
|
+
while (allIDs.size) if (!parseIDs()) {
|
|
112
|
+
parseIDs(true);
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
return results;
|
|
7
116
|
}
|
|
8
117
|
|
|
9
|
-
export {
|
|
118
|
+
export { changeSVGIDs };
|
|
@@ -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,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 };
|
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,8 +0,0 @@
|
|
|
1
|
-
import { ParsedXMLTagElement } from "../xml/types.js";
|
|
2
|
-
import { ChangeIDResult } from "./ids/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 };
|
package/lib/svg/ids.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { iterateXMLContent } from "../xml/iterate.js";
|
|
2
|
-
import { stringifyXMLContent } from "../xml/stringify.js";
|
|
3
|
-
import { changeIDInString } from "./ids/change.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 results = {
|
|
13
|
-
map: Object.create(null),
|
|
14
|
-
usage: Object.create(null)
|
|
15
|
-
};
|
|
16
|
-
const usage = [];
|
|
17
|
-
const parse = (replacement) => {
|
|
18
|
-
iterateXMLContent(root, (node, stack) => {
|
|
19
|
-
if (node.type !== "tag") return;
|
|
20
|
-
const attribs = node.attribs;
|
|
21
|
-
const nodeID = attribs.id;
|
|
22
|
-
if (typeof nodeID === "string") {
|
|
23
|
-
if (!replacement) {
|
|
24
|
-
if (idNodes.has(nodeID)) throw new Error(`Duplicate ID found: ${nodeID}`);
|
|
25
|
-
idNodes.set(nodeID, node);
|
|
26
|
-
idMap.set(node, nodeID);
|
|
27
|
-
} else if (nodeID === replacement[0]) {
|
|
28
|
-
const newID = replacement[1];
|
|
29
|
-
attribs.id = newID;
|
|
30
|
-
if (!results.map[newID]) results.map[newID] = [node];
|
|
31
|
-
else results.map[newID].push(node);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
for (const attrib in attribs) {
|
|
35
|
-
const value = attribs[attrib];
|
|
36
|
-
if (typeof value !== "string") continue;
|
|
37
|
-
const add = (id) => {
|
|
38
|
-
usage.push({
|
|
39
|
-
node,
|
|
40
|
-
attrib,
|
|
41
|
-
id
|
|
42
|
-
});
|
|
43
|
-
[node, ...stack].forEach((node$1) => {
|
|
44
|
-
const parentID = idMap.get(node$1);
|
|
45
|
-
if (parentID) {
|
|
46
|
-
const nested = nestedIDs.get(parentID) || [];
|
|
47
|
-
if (!nested.includes(id)) {
|
|
48
|
-
nested.push(id);
|
|
49
|
-
nestedIDs.set(parentID, nested);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
};
|
|
54
|
-
switch (attrib) {
|
|
55
|
-
case "id": break;
|
|
56
|
-
case "begin":
|
|
57
|
-
case "end": {
|
|
58
|
-
const newValue = value.split(";").map((part) => {
|
|
59
|
-
const chunks = part.trim().split(".");
|
|
60
|
-
if (chunks.length < 2) return part;
|
|
61
|
-
const time = chunks[1];
|
|
62
|
-
if (time?.startsWith("begin") || time?.startsWith("end")) {
|
|
63
|
-
const id = chunks.shift();
|
|
64
|
-
if (!replacement) add(id);
|
|
65
|
-
else if (id === replacement[0]) return `${replacement[1]}.${chunks.join(".")}`;
|
|
66
|
-
}
|
|
67
|
-
return part;
|
|
68
|
-
}).join(";");
|
|
69
|
-
if (replacement) attribs[attrib] = newValue;
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
case "href":
|
|
73
|
-
case "xlink:href":
|
|
74
|
-
if (value.startsWith("#")) {
|
|
75
|
-
const id = value.slice(1);
|
|
76
|
-
if (!replacement) add(id);
|
|
77
|
-
else if (id === replacement[0]) attribs[attrib] = `#${replacement[1]}`;
|
|
78
|
-
}
|
|
79
|
-
break;
|
|
80
|
-
default: if (value.startsWith("url(#")) {
|
|
81
|
-
const id = value.slice(5, -1);
|
|
82
|
-
if (!replacement) add(id);
|
|
83
|
-
else if (id === replacement[0]) attribs[attrib] = `url(#${replacement[1]})`;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
parse();
|
|
90
|
-
if (!idMap.size) return results;
|
|
91
|
-
const allIDs = new Set(idMap.values());
|
|
92
|
-
const parseIDs = (parseAll = false) => {
|
|
93
|
-
const oldSize = allIDs.size;
|
|
94
|
-
for (const id of allIDs) {
|
|
95
|
-
const nested = nestedIDs.get(id)?.filter((nestedID) => nestedID !== id && allIDs.has(nestedID)) ?? [];
|
|
96
|
-
if (parseAll || !nested.length) {
|
|
97
|
-
const node = idNodes.get(id);
|
|
98
|
-
const content = stringifyXMLContent([node]);
|
|
99
|
-
if (!content) throw new Error(`Failed to stringify node with ID: ${id}`);
|
|
100
|
-
const cleanedContent = changeIDInString(content, id, "{id}");
|
|
101
|
-
const newID = callback(id, cleanedContent, node.tag);
|
|
102
|
-
if (newID !== id) parse([id, newID]);
|
|
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;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return allIDs.size !== oldSize;
|
|
110
|
-
};
|
|
111
|
-
while (allIDs.size) if (!parseIDs()) {
|
|
112
|
-
parseIDs(true);
|
|
113
|
-
return results;
|
|
114
|
-
}
|
|
115
|
-
return results;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export { changeSVGIDs };
|