reduce.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. var split = require('../utils/split');
  2. var URL_PREFIX = 'url(';
  3. var UPPERCASE_URL_PREFIX = 'URL(';
  4. var URL_SUFFIX = ')';
  5. var SINGLE_QUOTE_URL_SUFFIX = '\')';
  6. var DOUBLE_QUOTE_URL_SUFFIX = '")';
  7. var DATA_URI_PREFIX_PATTERN = /^\s*['"]?\s*data:/;
  8. var DATA_URI_TRAILER_PATTERN = /[\s\};,\/!]/;
  9. var IMPORT_URL_PREFIX = '@import';
  10. var UPPERCASE_IMPORT_URL_PREFIX = '@IMPORT';
  11. var COMMENT_END_MARKER = /\*\//;
  12. function byUrl(data, context, callback) {
  13. var nextStart = 0;
  14. var nextStartUpperCase = 0;
  15. var nextEnd = 0;
  16. var firstMatch;
  17. var isDataURI = false;
  18. var cursor = 0;
  19. var tempData = [];
  20. var hasUppercaseUrl = data.indexOf(UPPERCASE_URL_PREFIX) > -1;
  21. for (; nextEnd < data.length;) {
  22. nextStart = data.indexOf(URL_PREFIX, nextEnd);
  23. nextStartUpperCase = hasUppercaseUrl ? data.indexOf(UPPERCASE_URL_PREFIX, nextEnd) : -1;
  24. if (nextStart == -1 && nextStartUpperCase == -1)
  25. break;
  26. if (nextStart == -1 && nextStartUpperCase > -1)
  27. nextStart = nextStartUpperCase;
  28. if (data[nextStart + URL_PREFIX.length] == '"') {
  29. nextEnd = data.indexOf(DOUBLE_QUOTE_URL_SUFFIX, nextStart);
  30. } else if (data[nextStart + URL_PREFIX.length] == '\'') {
  31. nextEnd = data.indexOf(SINGLE_QUOTE_URL_SUFFIX, nextStart);
  32. } else {
  33. isDataURI = DATA_URI_PREFIX_PATTERN.test(data.substring(nextStart + URL_PREFIX.length));
  34. if (isDataURI) {
  35. firstMatch = split(data.substring(nextStart), DATA_URI_TRAILER_PATTERN, false, '(', ')', true).pop();
  36. if (firstMatch && firstMatch[firstMatch.length - 1] == URL_SUFFIX) {
  37. nextEnd = nextStart + firstMatch.length - URL_SUFFIX.length;
  38. } else {
  39. nextEnd = -1;
  40. }
  41. } else {
  42. nextEnd = data.indexOf(URL_SUFFIX, nextStart);
  43. }
  44. }
  45. // Following lines are a safety mechanism to ensure
  46. // incorrectly terminated urls are processed correctly.
  47. if (nextEnd == -1) {
  48. nextEnd = data.indexOf('}', nextStart);
  49. if (nextEnd == -1)
  50. nextEnd = data.length;
  51. else
  52. nextEnd--;
  53. context.warnings.push('Broken URL declaration: \'' + data.substring(nextStart, nextEnd + 1) + '\'.');
  54. } else {
  55. if (data[nextEnd] != URL_SUFFIX)
  56. nextEnd = data.indexOf(URL_SUFFIX, nextEnd);
  57. }
  58. tempData.push(data.substring(cursor, nextStart));
  59. var url = data.substring(nextStart, nextEnd + 1);
  60. callback(url, tempData);
  61. cursor = nextEnd + 1;
  62. }
  63. return tempData.length > 0 ?
  64. tempData.join('') + data.substring(cursor, data.length) :
  65. data;
  66. }
  67. function byImport(data, context, callback) {
  68. var nextImport = 0;
  69. var nextImportUpperCase = 0;
  70. var nextStart = 0;
  71. var nextEnd = 0;
  72. var cursor = 0;
  73. var tempData = [];
  74. var nextSingleQuote = 0;
  75. var nextDoubleQuote = 0;
  76. var untilNextQuote;
  77. var withQuote;
  78. var SINGLE_QUOTE = '\'';
  79. var DOUBLE_QUOTE = '"';
  80. for (; nextEnd < data.length;) {
  81. nextImport = data.indexOf(IMPORT_URL_PREFIX, nextEnd);
  82. nextImportUpperCase = data.indexOf(UPPERCASE_IMPORT_URL_PREFIX, nextEnd);
  83. if (nextImport == -1 && nextImportUpperCase == -1)
  84. break;
  85. if (nextImport > -1 && nextImportUpperCase > -1 && nextImportUpperCase < nextImport)
  86. nextImport = nextImportUpperCase;
  87. nextSingleQuote = data.indexOf(SINGLE_QUOTE, nextImport);
  88. nextDoubleQuote = data.indexOf(DOUBLE_QUOTE, nextImport);
  89. if (nextSingleQuote > -1 && nextDoubleQuote > -1 && nextSingleQuote < nextDoubleQuote) {
  90. nextStart = nextSingleQuote;
  91. withQuote = SINGLE_QUOTE;
  92. } else if (nextSingleQuote > -1 && nextDoubleQuote > -1 && nextSingleQuote > nextDoubleQuote) {
  93. nextStart = nextDoubleQuote;
  94. withQuote = DOUBLE_QUOTE;
  95. } else if (nextSingleQuote > -1) {
  96. nextStart = nextSingleQuote;
  97. withQuote = SINGLE_QUOTE;
  98. } else if (nextDoubleQuote > -1) {
  99. nextStart = nextDoubleQuote;
  100. withQuote = DOUBLE_QUOTE;
  101. } else {
  102. break;
  103. }
  104. tempData.push(data.substring(cursor, nextStart));
  105. nextEnd = data.indexOf(withQuote, nextStart + 1);
  106. untilNextQuote = data.substring(nextImport, nextEnd);
  107. if (nextEnd == -1 || /^@import\s+(url\(|__ESCAPED)/i.test(untilNextQuote) || COMMENT_END_MARKER.test(untilNextQuote)) {
  108. cursor = nextStart;
  109. break;
  110. }
  111. var url = data.substring(nextStart, nextEnd + 1);
  112. callback(url, tempData);
  113. cursor = nextEnd + 1;
  114. }
  115. return tempData.length > 0 ?
  116. tempData.join('') + data.substring(cursor, data.length) :
  117. data;
  118. }
  119. function reduceAll(data, context, callback) {
  120. data = byUrl(data, context, callback);
  121. data = byImport(data, context, callback);
  122. return data;
  123. }
  124. module.exports = reduceAll;