epiphany/node_modules/svgo/plugins/convertStyleToAttrs.js
2023-12-09 22:48:07 -08:00

137 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const { attrsGroups } = require('./_collections');
exports.name = 'convertStyleToAttrs';
exports.description = 'converts style to attributes';
/**
* @type {(...args: string[]) => string}
*/
const g = (...args) => {
return '(?:' + args.join('|') + ')';
};
const stylingProps = attrsGroups.presentation;
const rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)'; // Like \" or \2051. Code points consume one space.
const rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*'; // attribute name like fill
const rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)"; // string in single quotes: 'smth'
const rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)'; // string in double quotes: "smth"
const rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$');
// Parentheses, E.g.: url(data:image/png;base64,iVBO...).
// ':' and ';' inside of it should be threated as is. (Just like in strings.)
const rParenthesis =
'\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)';
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
const rValue =
'\\s*(' +
g(
'[^!\'"();\\\\]+?',
rEscape,
rSingleQuotes,
rQuotes,
rParenthesis,
'[^;]*?'
) +
'*?' +
')';
// End of declaration. Spaces outside of capturing groups help to do natural trimming.
const rDeclEnd = '\\s*(?:;\\s*|$)';
// Important rule
const rImportant = '(\\s*!important(?![-(\\w]))?';
// Final RegExp to parse CSS declarations.
const regDeclarationBlock = new RegExp(
rAttr + ':' + rValue + rImportant + rDeclEnd,
'ig'
);
// Comments expression. Honors escape sequences and strings.
const regStripComments = new RegExp(
g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'),
'ig'
);
/**
* Convert style in attributes. Cleanups comments and illegal declarations (without colon) as a side effect.
*
* @example
* <g style="fill:#000; color: #fff;">
* ⬇
* <g fill="#000" color="#fff">
*
* @example
* <g style="fill:#000; color: #fff; -webkit-blah: blah">
* ⬇
* <g fill="#000" color="#fff" style="-webkit-blah: blah">
*
* @author Kir Belevich
*
* @type {import('./plugins-types').Plugin<'convertStyleToAttrs'>}
*/
exports.fn = (_root, params) => {
const { keepImportant = false } = params;
return {
element: {
enter: (node) => {
if (node.attributes.style != null) {
// ['opacity: 1', 'color: #000']
let styles = [];
/**
* @type {Record<string, string>}
*/
const newAttributes = {};
// Strip CSS comments preserving escape sequences and strings.
const styleValue = node.attributes.style.replace(
regStripComments,
(match) => {
return match[0] == '/'
? ''
: match[0] == '\\' && /[-g-z]/i.test(match[1])
? match[1]
: match;
}
);
regDeclarationBlock.lastIndex = 0;
// eslint-disable-next-line no-cond-assign
for (var rule; (rule = regDeclarationBlock.exec(styleValue)); ) {
if (!keepImportant || !rule[3]) {
styles.push([rule[1], rule[2]]);
}
}
if (styles.length) {
styles = styles.filter(function (style) {
if (style[0]) {
var prop = style[0].toLowerCase(),
val = style[1];
if (rQuotedString.test(val)) {
val = val.slice(1, -1);
}
if (stylingProps.includes(prop)) {
newAttributes[prop] = val;
return false;
}
}
return true;
});
Object.assign(node.attributes, newAttributes);
if (styles.length) {
node.attributes.style = styles
.map((declaration) => declaration.join(':'))
.join(';');
} else {
delete node.attributes.style;
}
}
}
},
},
};
};