cleanupIDs.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. 'use strict';
  2. exports.type = 'full';
  3. exports.active = true;
  4. exports.description = 'removes unused IDs and minifies used';
  5. exports.params = {
  6. remove: true,
  7. minify: true,
  8. prefix: ''
  9. };
  10. var referencesProps = require('./_collections').referencesProps,
  11. regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/,
  12. regReferencesHref = /^#(.+?)$/,
  13. regReferencesBegin = /^(\w+?)\./,
  14. styleOrScript = ['style', 'script'],
  15. generateIDchars = [
  16. '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',
  17. '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'
  18. ],
  19. maxIDindex = generateIDchars.length - 1;
  20. /**
  21. * Remove unused and minify used IDs
  22. * (only if there are no any <style> or <script>).
  23. *
  24. * @param {Object} item current iteration item
  25. * @param {Object} params plugin params
  26. *
  27. * @author Kir Belevich
  28. */
  29. exports.fn = function(data, params) {
  30. var currentID,
  31. currentIDstring,
  32. IDs = Object.create(null),
  33. referencesIDs = Object.create(null),
  34. idPrefix = 'id-', // prefix IDs so that values like '__proto__' don't break the work
  35. hasStyleOrScript = false;
  36. /**
  37. * Bananas!
  38. *
  39. * @param {Array} items input items
  40. * @return {Array} output items
  41. */
  42. function monkeys(items) {
  43. for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) {
  44. var item = items.content[i],
  45. match;
  46. // check if <style> of <script> presents
  47. if (item.isElem(styleOrScript)) {
  48. hasStyleOrScript = true;
  49. continue;
  50. }
  51. // …and don't remove any ID if yes
  52. if (item.isElem()) {
  53. item.eachAttr(function(attr) {
  54. var key;
  55. // save IDs
  56. if (attr.name === 'id') {
  57. key = idPrefix + attr.value;
  58. if (key in IDs) {
  59. item.removeAttr('id');
  60. } else {
  61. IDs[key] = item;
  62. }
  63. }
  64. // save IDs url() references
  65. else if (referencesProps.indexOf(attr.name) > -1) {
  66. match = attr.value.match(regReferencesUrl);
  67. if (match) {
  68. key = idPrefix + match[2];
  69. if (referencesIDs[key]) {
  70. referencesIDs[key].push(attr);
  71. } else {
  72. referencesIDs[key] = [attr];
  73. }
  74. }
  75. }
  76. // save IDs href references
  77. else if (
  78. attr.local === 'href' && (match = attr.value.match(regReferencesHref)) ||
  79. attr.name === 'begin' && (match = attr.value.match(regReferencesBegin))
  80. ) {
  81. key = idPrefix + match[1];
  82. if (referencesIDs[key]) {
  83. referencesIDs[key].push(attr);
  84. } else {
  85. referencesIDs[key] = [attr];
  86. }
  87. }
  88. });
  89. }
  90. // go deeper
  91. if (item.content) {
  92. monkeys(item);
  93. }
  94. }
  95. return items;
  96. }
  97. data = monkeys(data);
  98. if (hasStyleOrScript) {
  99. return data;
  100. }
  101. var idKey;
  102. for (var k in referencesIDs) {
  103. if (IDs[k]) {
  104. idKey = k;
  105. k = k.replace(idPrefix, '');
  106. // replace referenced IDs with the minified ones
  107. if (params.minify) {
  108. currentIDstring = getIDstring(currentID = generateID(currentID), params);
  109. IDs[idKey].attr('id').value = currentIDstring;
  110. referencesIDs[idKey].forEach(function(attr) {
  111. attr.value = attr.value
  112. .replace('#' + k, '#' + currentIDstring)
  113. .replace(k + '.', currentIDstring + '.');
  114. });
  115. idKey = idPrefix + k;
  116. }
  117. // don't remove referenced IDs
  118. delete IDs[idKey];
  119. }
  120. }
  121. // remove non-referenced IDs attributes from elements
  122. if (params.remove) {
  123. for(var ID in IDs) {
  124. IDs[ID].removeAttr('id');
  125. }
  126. }
  127. return data;
  128. };
  129. /**
  130. * Generate unique minimal ID.
  131. *
  132. * @param {Array} [currentID] current ID
  133. * @return {Array} generated ID array
  134. */
  135. function generateID(currentID) {
  136. if (!currentID) return [0];
  137. currentID[currentID.length - 1]++;
  138. for(var i = currentID.length - 1; i > 0; i--) {
  139. if (currentID[i] > maxIDindex) {
  140. currentID[i] = 0;
  141. if (currentID[i - 1] !== undefined) {
  142. currentID[i - 1]++;
  143. }
  144. }
  145. }
  146. if (currentID[0] > maxIDindex) {
  147. currentID[0] = 0;
  148. currentID.unshift(0);
  149. }
  150. return currentID;
  151. }
  152. /**
  153. * Get string from generated ID array.
  154. *
  155. * @param {Array} arr input ID array
  156. * @return {String} output ID string
  157. */
  158. function getIDstring(arr, params) {
  159. var str = params.prefix;
  160. arr.forEach(function(i) {
  161. str += generateIDchars[i];
  162. });
  163. return str;
  164. }