|
|
'use strict';
exports.type = 'full';
exports.active = true;
exports.description = 'removes unused IDs and minifies used';
exports.params = { remove: true, minify: true, prefix: '', preserve: [], preservePrefixes: [], force: false };
var referencesProps = new Set(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;
/** * Remove unused and minify used IDs * (only if there are no any <style> or <script>). * * @param {Object} item current iteration item * @param {Object} params plugin params * * @author Kir Belevich */ exports.fn = function(data, params) { var currentID, currentIDstring, IDs = new Map(), referencesIDs = new Map(), hasStyleOrScript = false, preserveIDs = new Set(Array.isArray(params.preserve) ? params.preserve : params.preserve ? [params.preserve] : []), preserveIDPrefixes = new Set(Array.isArray(params.preservePrefixes) ? params.preservePrefixes : (params.preservePrefixes ? [params.preservePrefixes] : [])), idValuePrefix = '#', idValuePostfix = '.';
/** * 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];
// quit if <style> or <script> present ('force' param prevents quitting)
if (!params.force) { if (item.isElem(styleOrScript)) { hasStyleOrScript = true; continue; } // Don't remove IDs if the whole SVG consists only of defs.
if (item.isElem('defs') && item.parentNode.isElem('svg')) { var hasDefsOnly = true; for (var j = i + 1; j < items.content.length; j++) { if (items.content[j].isElem()) { hasDefsOnly = false; break; } } if (hasDefsOnly) { break; } } } // …and don't remove any ID if yes
if (item.isElem()) { item.eachAttr(function(attr) { var key, match;
// save IDs
if (attr.name === 'id') { key = attr.value; if (IDs.has(key)) { item.removeAttr('id'); // remove repeated id
} else { IDs.set(key, item); } return; } // save references
if (referencesProps.has(attr.name) && (match = attr.value.match(regReferencesUrl))) { key = match[2]; // url() reference
} else if ( attr.local === 'href' && (match = attr.value.match(regReferencesHref)) || attr.name === 'begin' && (match = attr.value.match(regReferencesBegin)) ) { key = match[1]; // href reference
} if (key) { var ref = referencesIDs.get(key) || []; ref.push(attr); referencesIDs.set(key, ref); } }); } // go deeper
if (item.content) { monkeys(item); } } return items; }
data = monkeys(data);
if (hasStyleOrScript) { return data; }
const idPreserved = id => preserveIDs.has(id) || idMatchesPrefix(preserveIDPrefixes, id);
for (var ref of referencesIDs) { var key = ref[0];
if (IDs.has(key)) { // replace referenced IDs with the minified ones
if (params.minify && !idPreserved(key)) { do { currentIDstring = getIDstring(currentID = generateID(currentID), params); } while (idPreserved(currentIDstring));
IDs.get(key).attr('id').value = currentIDstring;
for (var attr of ref[1]) { attr.value = attr.value.includes(idValuePrefix) ? attr.value.replace(idValuePrefix + key, idValuePrefix + currentIDstring) : attr.value.replace(key + idValuePostfix, currentIDstring + idValuePostfix); } } // don't remove referenced IDs
IDs.delete(key); } } // remove non-referenced IDs attributes from elements
if (params.remove) { for(var keyElem of IDs) { if (!idPreserved(keyElem[0])) { keyElem[1].removeAttr('id'); } } } return data; };
/** * Check if an ID starts with any one of a list of strings. * * @param {Array} of prefix strings * @param {String} current ID * @return {Boolean} if currentID starts with one of the strings in prefixArray */ function idMatchesPrefix(prefixArray, currentID) { if (!currentID) return false;
for (var prefix of prefixArray) if (currentID.startsWith(prefix)) return true; return false; }
/** * Generate unique minimal ID. * * @param {Array} [currentID] current ID * @return {Array} generated ID array */ 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]++; } } } 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 */ function getIDstring(arr, params) { var str = params.prefix; return str + arr.map(i => generateIDchars[i]).join(''); }
|