@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.
Files changed (41) hide show
  1. package/lib/helpers/hash/types.d.ts +8 -0
  2. package/lib/helpers/hash/types.js +0 -0
  3. package/lib/helpers/hash/unique.d.ts +8 -8
  4. package/lib/helpers/hash/unique.js +16 -9
  5. package/lib/index.d.ts +20 -2
  6. package/lib/index.js +16 -1
  7. package/lib/svg/css/convert/content.d.ts +10 -0
  8. package/lib/svg/css/convert/content.js +27 -0
  9. package/lib/svg/css/convert/root.d.ts +9 -0
  10. package/lib/svg/css/convert/root.js +26 -0
  11. package/lib/svg/css/css/class.d.ts +6 -0
  12. package/lib/svg/css/css/class.js +17 -0
  13. package/lib/svg/css/css/const.d.ts +4 -0
  14. package/lib/svg/css/css/const.js +4 -0
  15. package/lib/svg/css/css/stringify.d.ts +10 -0
  16. package/lib/svg/css/css/stringify.js +26 -0
  17. package/lib/svg/css/css/toggle.d.ts +10 -0
  18. package/lib/svg/css/css/toggle.js +29 -0
  19. package/lib/svg/css/props/prop.d.ts +5 -0
  20. package/lib/svg/css/props/prop.js +65 -0
  21. package/lib/svg/css/props/props.d.ts +9 -0
  22. package/lib/svg/css/props/props.js +42 -0
  23. package/lib/svg/css/types.d.ts +23 -0
  24. package/lib/svg/css/types.js +0 -0
  25. package/lib/svg/ids/change.d.ts +8 -0
  26. package/lib/svg/{ids.js → ids/change.js} +16 -58
  27. package/lib/svg/ids/duplicate.d.ts +7 -0
  28. package/lib/svg/ids/duplicate.js +25 -0
  29. package/lib/svg/ids/string.d.ts +5 -0
  30. package/lib/svg/ids/string.js +9 -0
  31. package/lib/svg/ids/types.d.ts +6 -0
  32. package/lib/svg/ids/types.js +0 -0
  33. package/lib/svg/ids/unique.d.ts +6 -0
  34. package/lib/svg/ids/unique.js +18 -0
  35. package/lib/svg/ids/unused.d.ts +7 -0
  36. package/lib/svg/ids/unused.js +25 -0
  37. package/lib/xml/iterate.d.ts +1 -1
  38. package/lib/xml/stringify.d.ts +2 -7
  39. package/lib/xml/types.d.ts +9 -1
  40. package/package.json +1 -1
  41. package/lib/svg/ids.d.ts +0 -12
@@ -0,0 +1,8 @@
1
+ interface UniqueHashOptions {
2
+ prefix?: string;
3
+ css: boolean;
4
+ length: number;
5
+ lengths?: Record<string, number>;
6
+ throwOnCollision?: boolean;
7
+ }
8
+ export { UniqueHashOptions };
File without changes
@@ -1,17 +1,17 @@
1
- interface Options {
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: Options): string;
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 { prefix, length, lengths, css } = options;
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
- if (!cache[hash]) cache[hash] = str;
25
- else if (cache[hash] !== str) {
26
- console.warn("Data 1:", cache[hash]);
27
- console.warn("Data 2:", str);
28
- throw new Error(`Hash collision detected: ${hash}`);
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 `${prefix}${hash}`;
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
- export { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement, cloneObject, compareSets, compareValues, hashString, iterateXMLContent, parseXMLContent, sortObject, stringifyXMLContent };
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,6 @@
1
+ import { CSSRules } from "../types.js";
2
+ /**
3
+ * Get class name for CSS rules
4
+ */
5
+ declare function createCSSClassName(rules: CSSRules, prefix?: string): string;
6
+ export { createCSSClassName };
@@ -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,4 @@
1
+ declare const defaultClassProp = "class";
2
+ declare const classProps: readonly ["class"];
3
+ type ClassProp = (typeof classProps)[number];
4
+ export { ClassProp, classProps, defaultClassProp };
@@ -0,0 +1,4 @@
1
+ const defaultClassProp = "class";
2
+ const classProps = [defaultClassProp];
3
+
4
+ export { classProps, defaultClassProp };
@@ -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,5 @@
1
+ /**
2
+ * Convert property to CSS
3
+ */
4
+ declare function convertSVGPropertyToCSS(tag: string, prop: string, value: string | number): [string, string] | undefined;
5
+ export { convertSVGPropertyToCSS };
@@ -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 { cloneObject } from "../helpers/misc/clone.js";
2
- import { iterateXMLContent } from "../xml/iterate.js";
3
- import { stringifyXMLContent } from "../xml/stringify.js";
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 = Object.create(null);
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 newNode = cloneObject(node);
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 newID = callback(id, content, node.tag);
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, removeDuplicateIDs };
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,5 @@
1
+ /**
2
+ * Change ID in a string
3
+ */
4
+ declare function changeIDInString(value: string, oldID: string, newID: string): string;
5
+ export { changeIDInString };
@@ -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,6 @@
1
+ import { ParsedXMLTagElement } from "../../xml/types.js";
2
+ interface ChangeIDResult {
3
+ map: Record<string, ParsedXMLTagElement[]>;
4
+ usage: Record<string, ParsedXMLTagElement[]>;
5
+ }
6
+ export { ChangeIDResult };
File without changes
@@ -0,0 +1,6 @@
1
+ import { ParsedXMLTagElement } from "../../xml/types.js";
2
+ /**
3
+ * Create unique IDs for SVG elements
4
+ */
5
+ declare function createUniqueIDs(root: ParsedXMLTagElement[], prefix?: string): void;
6
+ export { createUniqueIDs };
@@ -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 };
@@ -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: ParsedXMLTagElement[], callback: (node: ParsedXMLNode, stack: ParsedXMLTagElement[]) => CallbackResult): ParsedXMLTagElement[];
11
+ declare function iterateXMLContent<T extends ParsedXMLTagElement | ParsedXMLNode>(root: T[], callback: (node: ParsedXMLNode, stack: ParsedXMLTagElement[]) => CallbackResult): T[];
12
12
  export { iterateXMLContent };
@@ -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?: ParseSVGOptions): string | null;
5
+ declare function stringifyXMLContent(root: ParsedXMLTagElement[], options?: StringifyXMLOptions): string | null;
11
6
  export { stringifyXMLContent };
@@ -18,4 +18,12 @@ interface ParsedXMLTextElement {
18
18
  * Element in tree
19
19
  */
20
20
  type ParsedXMLNode = ParsedXMLTagElement | ParsedXMLTextElement;
21
- export { ParsedXMLNode, ParsedXMLTagElement, ParsedXMLTextElement };
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.13",
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 };