source: trip-planner-front/node_modules/svgo/plugins/prefixIds.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 6.5 KB
Line 
1'use strict';
2
3const csstree = require('css-tree');
4const { referencesProps } = require('./_collections.js');
5
6/**
7 * @typedef {import('../lib/types').XastElement} XastElement
8 * @typedef {import('../lib/types').PluginInfo} PluginInfo
9 */
10
11exports.type = 'visitor';
12exports.name = 'prefixIds';
13exports.active = false;
14exports.description = 'prefix IDs';
15
16/**
17 * extract basename from path
18 * @type {(path: string) => string}
19 */
20const getBasename = (path) => {
21 // extract everything after latest slash or backslash
22 const matched = path.match(/[/\\]?([^/\\]+)$/);
23 if (matched) {
24 return matched[1];
25 }
26 return '';
27};
28
29/**
30 * escapes a string for being used as ID
31 * @type {(string: string) => string}
32 */
33const escapeIdentifierName = (str) => {
34 return str.replace(/[. ]/g, '_');
35};
36
37/**
38 * @type {(string: string) => string}
39 */
40const unquote = (string) => {
41 if (
42 (string.startsWith('"') && string.endsWith('"')) ||
43 (string.startsWith("'") && string.endsWith("'"))
44 ) {
45 return string.slice(1, -1);
46 }
47 return string;
48};
49
50/**
51 * prefix an ID
52 * @type {(prefix: string, name: string) => string}
53 */
54const prefixId = (prefix, value) => {
55 if (value.startsWith(prefix)) {
56 return value;
57 }
58 return prefix + value;
59};
60
61/**
62 * prefix an #ID
63 * @type {(prefix: string, name: string) => string | null}
64 */
65const prefixReference = (prefix, value) => {
66 if (value.startsWith('#')) {
67 return '#' + prefixId(prefix, value.slice(1));
68 }
69 return null;
70};
71
72/**
73 * Prefixes identifiers
74 *
75 * @author strarsis <strarsis@gmail.com>
76 *
77 * @type {import('../lib/types').Plugin<{
78 * prefix?: boolean | string | ((node: XastElement, info: PluginInfo) => string),
79 * delim?: string,
80 * prefixIds?: boolean,
81 * prefixClassNames?: boolean,
82 * }>}
83 */
84exports.fn = (_root, params, info) => {
85 const { delim = '__', prefixIds = true, prefixClassNames = true } = params;
86
87 return {
88 element: {
89 enter: (node) => {
90 /**
91 * prefix, from file name or option
92 * @type {string}
93 */
94 let prefix = 'prefix' + delim;
95 if (typeof params.prefix === 'function') {
96 prefix = params.prefix(node, info) + delim;
97 } else if (typeof params.prefix === 'string') {
98 prefix = params.prefix + delim;
99 } else if (params.prefix === false) {
100 prefix = '';
101 } else if (info.path != null && info.path.length > 0) {
102 prefix = escapeIdentifierName(getBasename(info.path)) + delim;
103 }
104
105 // prefix id/class selectors and url() references in styles
106 if (node.name === 'style') {
107 // skip empty <style/> elements
108 if (node.children.length === 0) {
109 return;
110 }
111
112 // parse styles
113 let cssText = '';
114 if (
115 node.children[0].type === 'text' ||
116 node.children[0].type === 'cdata'
117 ) {
118 cssText = node.children[0].value;
119 }
120 /**
121 * @type {null | csstree.CssNode}
122 */
123 let cssAst = null;
124 try {
125 cssAst = csstree.parse(cssText, {
126 parseValue: true,
127 parseCustomProperty: false,
128 });
129 } catch {
130 return;
131 }
132
133 csstree.walk(cssAst, (node) => {
134 // #ID, .class selectors
135 if (
136 (prefixIds && node.type === 'IdSelector') ||
137 (prefixClassNames && node.type === 'ClassSelector')
138 ) {
139 node.name = prefixId(prefix, node.name);
140 return;
141 }
142 // url(...) references
143 if (
144 node.type === 'Url' &&
145 node.value.value &&
146 node.value.value.length > 0
147 ) {
148 const prefixed = prefixReference(
149 prefix,
150 unquote(node.value.value)
151 );
152 if (prefixed != null) {
153 node.value.value = prefixed;
154 }
155 }
156 });
157
158 // update styles
159 if (
160 node.children[0].type === 'text' ||
161 node.children[0].type === 'cdata'
162 ) {
163 node.children[0].value = csstree.generate(cssAst);
164 }
165 return;
166 }
167
168 // prefix an ID attribute value
169 if (
170 prefixIds &&
171 node.attributes.id != null &&
172 node.attributes.id.length !== 0
173 ) {
174 node.attributes.id = prefixId(prefix, node.attributes.id);
175 }
176
177 // prefix a class attribute value
178 if (
179 prefixClassNames &&
180 node.attributes.class != null &&
181 node.attributes.class.length !== 0
182 ) {
183 node.attributes.class = node.attributes.class
184 .split(/\s+/)
185 .map((name) => prefixId(prefix, name))
186 .join(' ');
187 }
188
189 // prefix a href attribute value
190 // xlink:href is deprecated, must be still supported
191 for (const name of ['href', 'xlink:href']) {
192 if (
193 node.attributes[name] != null &&
194 node.attributes[name].length !== 0
195 ) {
196 const prefixed = prefixReference(prefix, node.attributes[name]);
197 if (prefixed != null) {
198 node.attributes[name] = prefixed;
199 }
200 }
201 }
202
203 // prefix an URL attribute value
204 for (const name of referencesProps) {
205 if (
206 node.attributes[name] != null &&
207 node.attributes[name].length !== 0
208 ) {
209 // extract id reference from url(...) value
210 const matches = /url\((.*?)\)/gi.exec(node.attributes[name]);
211 if (matches != null) {
212 const value = matches[1];
213 const prefixed = prefixReference(prefix, value);
214 if (prefixed != null) {
215 node.attributes[name] = `url(${prefixed})`;
216 }
217 }
218 }
219 }
220
221 // prefix begin/end attribute value
222 for (const name of ['begin', 'end']) {
223 if (
224 node.attributes[name] != null &&
225 node.attributes[name].length !== 0
226 ) {
227 const parts = node.attributes[name].split(/\s*;\s+/).map((val) => {
228 if (val.endsWith('.end') || val.endsWith('.start')) {
229 const [id, postfix] = val.split('.');
230 return `${prefixId(prefix, id)}.${postfix}`;
231 }
232 return val;
233 });
234 node.attributes[name] = parts.join('; ');
235 }
236 }
237 },
238 },
239 };
240};
Note: See TracBrowser for help on using the repository browser.