summaryrefslogtreecommitdiff
path: root/node_modules/svgo/plugins/prefixIds.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/svgo/plugins/prefixIds.js')
-rw-r--r--node_modules/svgo/plugins/prefixIds.js229
1 files changed, 229 insertions, 0 deletions
diff --git a/node_modules/svgo/plugins/prefixIds.js b/node_modules/svgo/plugins/prefixIds.js
new file mode 100644
index 00000000..66762936
--- /dev/null
+++ b/node_modules/svgo/plugins/prefixIds.js
@@ -0,0 +1,229 @@
+'use strict';
+
+const csstree = require('css-tree');
+const { referencesProps } = require('./_collections.js');
+
+exports.name = 'prefixIds';
+exports.description = 'prefix IDs';
+
+/**
+ * extract basename from path
+ * @type {(path: string) => string}
+ */
+const getBasename = (path) => {
+ // extract everything after latest slash or backslash
+ const matched = path.match(/[/\\]?([^/\\]+)$/);
+ if (matched) {
+ return matched[1];
+ }
+ return '';
+};
+
+/**
+ * escapes a string for being used as ID
+ * @type {(string: string) => string}
+ */
+const escapeIdentifierName = (str) => {
+ return str.replace(/[. ]/g, '_');
+};
+
+/**
+ * @type {(string: string) => string}
+ */
+const unquote = (string) => {
+ if (
+ (string.startsWith('"') && string.endsWith('"')) ||
+ (string.startsWith("'") && string.endsWith("'"))
+ ) {
+ return string.slice(1, -1);
+ }
+ return string;
+};
+
+/**
+ * prefix an ID
+ * @type {(prefix: string, name: string) => string}
+ */
+const prefixId = (prefix, value) => {
+ if (value.startsWith(prefix)) {
+ return value;
+ }
+ return prefix + value;
+};
+
+/**
+ * prefix an #ID
+ * @type {(prefix: string, name: string) => string | null}
+ */
+const prefixReference = (prefix, value) => {
+ if (value.startsWith('#')) {
+ return '#' + prefixId(prefix, value.slice(1));
+ }
+ return null;
+};
+
+/** @type {(value: any) => any} */
+const toAny = (value) => value;
+
+/**
+ * Prefixes identifiers
+ *
+ * @author strarsis <strarsis@gmail.com>
+ *
+ * @type {import('./plugins-types').Plugin<'prefixIds'>}
+ */
+exports.fn = (_root, params, info) => {
+ const { delim = '__', prefixIds = true, prefixClassNames = true } = params;
+
+ return {
+ element: {
+ enter: (node) => {
+ /**
+ * prefix, from file name or option
+ * @type {string}
+ */
+ let prefix = 'prefix' + delim;
+ if (typeof params.prefix === 'function') {
+ prefix = params.prefix(node, info) + delim;
+ } else if (typeof params.prefix === 'string') {
+ prefix = params.prefix + delim;
+ } else if (params.prefix === false) {
+ prefix = '';
+ } else if (info.path != null && info.path.length > 0) {
+ prefix = escapeIdentifierName(getBasename(info.path)) + delim;
+ }
+
+ // prefix id/class selectors and url() references in styles
+ if (node.name === 'style') {
+ // skip empty <style/> elements
+ if (node.children.length === 0) {
+ return;
+ }
+
+ // parse styles
+ let cssText = '';
+ if (
+ node.children[0].type === 'text' ||
+ node.children[0].type === 'cdata'
+ ) {
+ cssText = node.children[0].value;
+ }
+ /**
+ * @type {null | csstree.CssNode}
+ */
+ let cssAst = null;
+ try {
+ cssAst = csstree.parse(cssText, {
+ parseValue: true,
+ parseCustomProperty: false,
+ });
+ } catch {
+ return;
+ }
+
+ csstree.walk(cssAst, (node) => {
+ // #ID, .class selectors
+ if (
+ (prefixIds && node.type === 'IdSelector') ||
+ (prefixClassNames && node.type === 'ClassSelector')
+ ) {
+ node.name = prefixId(prefix, node.name);
+ return;
+ }
+ // url(...) references
+ // csstree v2 changed this type
+ if (node.type === 'Url' && toAny(node.value).length > 0) {
+ const prefixed = prefixReference(
+ prefix,
+ unquote(toAny(node.value))
+ );
+ if (prefixed != null) {
+ toAny(node).value = prefixed;
+ }
+ }
+ });
+
+ // update styles
+ if (
+ node.children[0].type === 'text' ||
+ node.children[0].type === 'cdata'
+ ) {
+ node.children[0].value = csstree.generate(cssAst);
+ }
+ return;
+ }
+
+ // prefix an ID attribute value
+ if (
+ prefixIds &&
+ node.attributes.id != null &&
+ node.attributes.id.length !== 0
+ ) {
+ node.attributes.id = prefixId(prefix, node.attributes.id);
+ }
+
+ // prefix a class attribute value
+ if (
+ prefixClassNames &&
+ node.attributes.class != null &&
+ node.attributes.class.length !== 0
+ ) {
+ node.attributes.class = node.attributes.class
+ .split(/\s+/)
+ .map((name) => prefixId(prefix, name))
+ .join(' ');
+ }
+
+ // prefix a href attribute value
+ // xlink:href is deprecated, must be still supported
+ for (const name of ['href', 'xlink:href']) {
+ if (
+ node.attributes[name] != null &&
+ node.attributes[name].length !== 0
+ ) {
+ const prefixed = prefixReference(prefix, node.attributes[name]);
+ if (prefixed != null) {
+ node.attributes[name] = prefixed;
+ }
+ }
+ }
+
+ // prefix an URL attribute value
+ for (const name of referencesProps) {
+ if (
+ node.attributes[name] != null &&
+ node.attributes[name].length !== 0
+ ) {
+ node.attributes[name] = node.attributes[name].replace(
+ /url\((.*?)\)/gi,
+ (match, url) => {
+ const prefixed = prefixReference(prefix, url);
+ if (prefixed == null) {
+ return match;
+ }
+ return `url(${prefixed})`;
+ }
+ );
+ }
+ }
+
+ // prefix begin/end attribute value
+ for (const name of ['begin', 'end']) {
+ if (
+ node.attributes[name] != null &&
+ node.attributes[name].length !== 0
+ ) {
+ const parts = node.attributes[name].split(/\s*;\s+/).map((val) => {
+ if (val.endsWith('.end') || val.endsWith('.start')) {
+ const [id, postfix] = val.split('.');
+ return `${prefixId(prefix, id)}.${postfix}`;
+ }
+ return val;
+ });
+ node.attributes[name] = parts.join('; ');
+ }
+ }
+ },
+ },
+ };
+};