schnee effeckt und fehler Korektur
This commit is contained in:
451
node_modules/svgo/plugins/cleanupIDs.js
generated
vendored
451
node_modules/svgo/plugins/cleanupIDs.js
generated
vendored
@@ -1,208 +1,297 @@
|
||||
'use strict';
|
||||
|
||||
exports.type = 'full';
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
*/
|
||||
|
||||
const { visitSkip } = require('../lib/xast.js');
|
||||
const { referencesProps } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'cleanupIDs';
|
||||
exports.active = true;
|
||||
|
||||
exports.description = 'removes unused IDs and minifies used';
|
||||
|
||||
exports.params = {
|
||||
remove: true,
|
||||
minify: true,
|
||||
prefix: ''
|
||||
};
|
||||
|
||||
var referencesProps = require('./_collections').referencesProps,
|
||||
regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/,
|
||||
regReferencesHref = /^#(.+?)$/,
|
||||
regReferencesBegin = /^(\w+?)\./,
|
||||
styleOrScript = ['style', 'script'],
|
||||
generateIDchars = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
|
||||
],
|
||||
maxIDindex = generateIDchars.length - 1;
|
||||
const regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/;
|
||||
const regReferencesHref = /^#(.+?)$/;
|
||||
const regReferencesBegin = /(\w+)\./;
|
||||
const generateIDchars = [
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
];
|
||||
const maxIDindex = generateIDchars.length - 1;
|
||||
|
||||
/**
|
||||
* Remove unused and minify used IDs
|
||||
* (only if there are no any <style> or <script>).
|
||||
* Check if an ID starts with any one of a list of strings.
|
||||
*
|
||||
* @param {Object} item current iteration item
|
||||
* @param {Object} params plugin params
|
||||
*
|
||||
* @author Kir Belevich
|
||||
* @type {(string: string, prefixes: Array<string>) => boolean}
|
||||
*/
|
||||
exports.fn = function(data, params) {
|
||||
|
||||
var currentID,
|
||||
currentIDstring,
|
||||
IDs = Object.create(null),
|
||||
referencesIDs = Object.create(null),
|
||||
idPrefix = 'id-', // prefix IDs so that values like '__proto__' don't break the work
|
||||
hasStyleOrScript = false;
|
||||
|
||||
/**
|
||||
* Bananas!
|
||||
*
|
||||
* @param {Array} items input items
|
||||
* @return {Array} output items
|
||||
*/
|
||||
function monkeys(items) {
|
||||
|
||||
for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) {
|
||||
|
||||
var item = items.content[i],
|
||||
match;
|
||||
|
||||
// check if <style> of <script> presents
|
||||
if (item.isElem(styleOrScript)) {
|
||||
hasStyleOrScript = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// …and don't remove any ID if yes
|
||||
if (item.isElem()) {
|
||||
|
||||
item.eachAttr(function(attr) {
|
||||
var key;
|
||||
// save IDs
|
||||
if (attr.name === 'id') {
|
||||
key = idPrefix + attr.value;
|
||||
if (key in IDs) {
|
||||
item.removeAttr('id');
|
||||
} else {
|
||||
IDs[key] = item;
|
||||
}
|
||||
}
|
||||
|
||||
// save IDs url() references
|
||||
else if (referencesProps.indexOf(attr.name) > -1) {
|
||||
match = attr.value.match(regReferencesUrl);
|
||||
|
||||
if (match) {
|
||||
key = idPrefix + match[2];
|
||||
if (referencesIDs[key]) {
|
||||
referencesIDs[key].push(attr);
|
||||
} else {
|
||||
referencesIDs[key] = [attr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save IDs href references
|
||||
else if (
|
||||
attr.local === 'href' && (match = attr.value.match(regReferencesHref)) ||
|
||||
attr.name === 'begin' && (match = attr.value.match(regReferencesBegin))
|
||||
) {
|
||||
key = idPrefix + match[1];
|
||||
if (referencesIDs[key]) {
|
||||
referencesIDs[key].push(attr);
|
||||
} else {
|
||||
referencesIDs[key] = [attr];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// go deeper
|
||||
if (item.content) {
|
||||
monkeys(item);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
|
||||
const hasStringPrefix = (string, prefixes) => {
|
||||
for (const prefix of prefixes) {
|
||||
if (string.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
data = monkeys(data);
|
||||
|
||||
if (hasStyleOrScript) {
|
||||
return data;
|
||||
}
|
||||
|
||||
for (var k in referencesIDs) {
|
||||
if (IDs[k]) {
|
||||
|
||||
// replace referenced IDs with the minified ones
|
||||
if (params.minify) {
|
||||
|
||||
currentIDstring = getIDstring(currentID = generateID(currentID), params);
|
||||
IDs[k].attr('id').value = currentIDstring;
|
||||
|
||||
referencesIDs[k].forEach(function(attr) {
|
||||
k = k.replace(idPrefix, '');
|
||||
attr.value = attr.value
|
||||
.replace('#' + k, '#' + currentIDstring)
|
||||
.replace(k + '.', currentIDstring + '.');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// don't remove referenced IDs
|
||||
delete IDs[idPrefix + k];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// remove non-referenced IDs attributes from elements
|
||||
if (params.remove) {
|
||||
|
||||
for(var ID in IDs) {
|
||||
IDs[ID].removeAttr('id');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate unique minimal ID.
|
||||
*
|
||||
* @param {Array} [currentID] current ID
|
||||
* @return {Array} generated ID array
|
||||
* @type {(currentID: null | Array<number>) => Array<number>}
|
||||
*/
|
||||
function generateID(currentID) {
|
||||
|
||||
if (!currentID) return [0];
|
||||
|
||||
currentID[currentID.length - 1]++;
|
||||
|
||||
for(var i = currentID.length - 1; i > 0; i--) {
|
||||
if (currentID[i] > maxIDindex) {
|
||||
currentID[i] = 0;
|
||||
|
||||
if (currentID[i - 1] !== undefined) {
|
||||
currentID[i - 1]++;
|
||||
}
|
||||
}
|
||||
const generateID = (currentID) => {
|
||||
if (currentID == null) {
|
||||
return [0];
|
||||
}
|
||||
currentID[currentID.length - 1] += 1;
|
||||
for (let i = currentID.length - 1; i > 0; i--) {
|
||||
if (currentID[i] > maxIDindex) {
|
||||
currentID[i] = 0;
|
||||
if (currentID[i - 1] !== undefined) {
|
||||
currentID[i - 1]++;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentID[0] > maxIDindex) {
|
||||
currentID[0] = 0;
|
||||
currentID.unshift(0);
|
||||
}
|
||||
|
||||
return currentID;
|
||||
|
||||
}
|
||||
}
|
||||
if (currentID[0] > maxIDindex) {
|
||||
currentID[0] = 0;
|
||||
currentID.unshift(0);
|
||||
}
|
||||
return currentID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get string from generated ID array.
|
||||
*
|
||||
* @param {Array} arr input ID array
|
||||
* @return {String} output ID string
|
||||
* @type {(arr: Array<number>, prefix: string) => string}
|
||||
*/
|
||||
function getIDstring(arr, params) {
|
||||
const getIDstring = (arr, prefix) => {
|
||||
return prefix + arr.map((i) => generateIDchars[i]).join('');
|
||||
};
|
||||
|
||||
var str = params.prefix;
|
||||
/**
|
||||
* Remove unused and minify used IDs
|
||||
* (only if there are no any <style> or <script>).
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* remove?: boolean,
|
||||
* minify?: boolean,
|
||||
* prefix?: string,
|
||||
* preserve?: Array<string>,
|
||||
* preservePrefixes?: Array<string>,
|
||||
* force?: boolean,
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params) => {
|
||||
const {
|
||||
remove = true,
|
||||
minify = true,
|
||||
prefix = '',
|
||||
preserve = [],
|
||||
preservePrefixes = [],
|
||||
force = false,
|
||||
} = params;
|
||||
const preserveIDs = new Set(
|
||||
Array.isArray(preserve) ? preserve : preserve ? [preserve] : []
|
||||
);
|
||||
const preserveIDPrefixes = Array.isArray(preservePrefixes)
|
||||
? preservePrefixes
|
||||
: preservePrefixes
|
||||
? [preservePrefixes]
|
||||
: [];
|
||||
/**
|
||||
* @type {Map<string, XastElement>}
|
||||
*/
|
||||
const nodeById = new Map();
|
||||
/**
|
||||
* @type {Map<string, Array<{element: XastElement, name: string, value: string }>>}
|
||||
*/
|
||||
const referencesById = new Map();
|
||||
let deoptimized = false;
|
||||
|
||||
arr.forEach(function(i) {
|
||||
str += generateIDchars[i];
|
||||
});
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (force == false) {
|
||||
// deoptimize if style or script elements are present
|
||||
if (
|
||||
(node.name === 'style' || node.name === 'script') &&
|
||||
node.children.length !== 0
|
||||
) {
|
||||
deoptimized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
return str;
|
||||
// avoid removing IDs if the whole SVG consists only of defs
|
||||
if (node.name === 'svg') {
|
||||
let hasDefsOnly = true;
|
||||
for (const child of node.children) {
|
||||
if (child.type !== 'element' || child.name !== 'defs') {
|
||||
hasDefsOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasDefsOnly) {
|
||||
return visitSkip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
if (name === 'id') {
|
||||
// collect all ids
|
||||
const id = value;
|
||||
if (nodeById.has(id)) {
|
||||
delete node.attributes.id; // remove repeated id
|
||||
} else {
|
||||
nodeById.set(id, node);
|
||||
}
|
||||
} else {
|
||||
// collect all references
|
||||
/**
|
||||
* @type {null | string}
|
||||
*/
|
||||
let id = null;
|
||||
if (referencesProps.includes(name)) {
|
||||
const match = value.match(regReferencesUrl);
|
||||
if (match != null) {
|
||||
id = match[2]; // url() reference
|
||||
}
|
||||
}
|
||||
if (name === 'href' || name.endsWith(':href')) {
|
||||
const match = value.match(regReferencesHref);
|
||||
if (match != null) {
|
||||
id = match[1]; // href reference
|
||||
}
|
||||
}
|
||||
if (name === 'begin') {
|
||||
const match = value.match(regReferencesBegin);
|
||||
if (match != null) {
|
||||
id = match[1]; // href reference
|
||||
}
|
||||
}
|
||||
if (id != null) {
|
||||
let refs = referencesById.get(id);
|
||||
if (refs == null) {
|
||||
refs = [];
|
||||
referencesById.set(id, refs);
|
||||
}
|
||||
refs.push({ element: node, name, value });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
root: {
|
||||
exit: () => {
|
||||
if (deoptimized) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {(id: string) => boolean}
|
||||
**/
|
||||
const isIdPreserved = (id) =>
|
||||
preserveIDs.has(id) || hasStringPrefix(id, preserveIDPrefixes);
|
||||
/**
|
||||
* @type {null | Array<number>}
|
||||
*/
|
||||
let currentID = null;
|
||||
for (const [id, refs] of referencesById) {
|
||||
const node = nodeById.get(id);
|
||||
if (node != null) {
|
||||
// replace referenced IDs with the minified ones
|
||||
if (minify && isIdPreserved(id) === false) {
|
||||
/**
|
||||
* @type {null | string}
|
||||
*/
|
||||
let currentIDString = null;
|
||||
do {
|
||||
currentID = generateID(currentID);
|
||||
currentIDString = getIDstring(currentID, prefix);
|
||||
} while (isIdPreserved(currentIDString));
|
||||
node.attributes.id = currentIDString;
|
||||
for (const { element, name, value } of refs) {
|
||||
if (value.includes('#')) {
|
||||
// replace id in href and url()
|
||||
element.attributes[name] = value.replace(
|
||||
`#${id}`,
|
||||
`#${currentIDString}`
|
||||
);
|
||||
} else {
|
||||
// replace id in begin attribute
|
||||
element.attributes[name] = value.replace(
|
||||
`${id}.`,
|
||||
`${currentIDString}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// keep referenced node
|
||||
nodeById.delete(id);
|
||||
}
|
||||
}
|
||||
// remove non-referenced IDs attributes from elements
|
||||
if (remove) {
|
||||
for (const [id, node] of nodeById) {
|
||||
if (isIdPreserved(id) === false) {
|
||||
delete node.attributes.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user