|
|
- '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('');
- }
|