You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
4.7 KiB
JavaScript

var URL_PREFIX = 'url(';
var UPPERCASE_URL_PREFIX = 'URL(';
var URL_SUFFIX = ')';
var DATA_URI_PREFIX = 'data:';
var IMPORT_URL_PREFIX = '@import';
var UPPERCASE_IMPORT_URL_PREFIX = '@IMPORT';
var COMMENT_END_MARKER = /\*\//;
function byUrl(data, context, callback) {
var nextStart = 0;
var nextStartUpperCase = 0;
var nextEnd = 0;
var nextEndAhead = 0;
var isDataURI = false;
var cursor = 0;
var tempData = [];
var hasUppercaseUrl = data.indexOf(UPPERCASE_URL_PREFIX) > -1;
for (; nextEnd < data.length;) {
nextStart = data.indexOf(URL_PREFIX, nextEnd);
nextStartUpperCase = hasUppercaseUrl ? data.indexOf(UPPERCASE_URL_PREFIX, nextEnd) : -1;
if (nextStart == -1 && nextStartUpperCase == -1)
break;
if (nextStart == -1 && nextStartUpperCase > -1)
nextStart = nextStartUpperCase;
if (data[nextStart + URL_PREFIX.length] == '"') {
nextEnd = data.indexOf('"', nextStart + URL_PREFIX.length + 1);
} else if (data[nextStart + URL_PREFIX.length] == '\'') {
nextEnd = data.indexOf('\'', nextStart + URL_PREFIX.length + 1);
} else {
isDataURI = data.substring(nextStart + URL_PREFIX.length).trim().indexOf(DATA_URI_PREFIX) === 0;
nextEnd = data.indexOf(URL_SUFFIX, nextStart);
if (isDataURI) {
// this is a fuzzy matching logic for unqoted data URIs
while (true) {
nextEndAhead = data.indexOf(URL_SUFFIX, nextEnd + 1);
// if it has whitespace, curly braces, or semicolon then we should be out of URL,
// otherwise keep iterating if it has not but content is not escaped,
// it has to be quoted so it will be captured by either of two clauses above
if (nextEndAhead == -1 || /[\s\{\};]/.test(data.substring(nextEnd, nextEndAhead)))
break;
nextEnd = nextEndAhead;
}
}
}
// Following lines are a safety mechanism to ensure
// incorrectly terminated urls are processed correctly.
if (nextEnd == -1) {
nextEnd = data.indexOf('}', nextStart);
if (nextEnd == -1)
nextEnd = data.length;
else
nextEnd--;
context.warnings.push('Broken URL declaration: \'' + data.substring(nextStart, nextEnd + 1) + '\'.');
} else {
if (data[nextEnd] != URL_SUFFIX)
nextEnd = data.indexOf(URL_SUFFIX, nextEnd);
}
tempData.push(data.substring(cursor, nextStart));
var url = data.substring(nextStart, nextEnd + 1);
callback(url, tempData);
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
}
function byImport(data, context, callback) {
var nextImport = 0;
var nextImportUpperCase = 0;
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var tempData = [];
var nextSingleQuote = 0;
var nextDoubleQuote = 0;
var untilNextQuote;
var withQuote;
var SINGLE_QUOTE = '\'';
var DOUBLE_QUOTE = '"';
for (; nextEnd < data.length;) {
nextImport = data.indexOf(IMPORT_URL_PREFIX, nextEnd);
nextImportUpperCase = data.indexOf(UPPERCASE_IMPORT_URL_PREFIX, nextEnd);
if (nextImport == -1 && nextImportUpperCase == -1)
break;
if (nextImport > -1 && nextImportUpperCase > -1 && nextImportUpperCase < nextImport)
nextImport = nextImportUpperCase;
nextSingleQuote = data.indexOf(SINGLE_QUOTE, nextImport);
nextDoubleQuote = data.indexOf(DOUBLE_QUOTE, nextImport);
if (nextSingleQuote > -1 && nextDoubleQuote > -1 && nextSingleQuote < nextDoubleQuote) {
nextStart = nextSingleQuote;
withQuote = SINGLE_QUOTE;
} else if (nextSingleQuote > -1 && nextDoubleQuote > -1 && nextSingleQuote > nextDoubleQuote) {
nextStart = nextDoubleQuote;
withQuote = DOUBLE_QUOTE;
} else if (nextSingleQuote > -1) {
nextStart = nextSingleQuote;
withQuote = SINGLE_QUOTE;
} else if (nextDoubleQuote > -1) {
nextStart = nextDoubleQuote;
withQuote = DOUBLE_QUOTE;
} else {
break;
}
tempData.push(data.substring(cursor, nextStart));
nextEnd = data.indexOf(withQuote, nextStart + 1);
untilNextQuote = data.substring(nextImport, nextEnd);
if (nextEnd == -1 || /^@import\s+(url\(|__ESCAPED)/i.test(untilNextQuote) || COMMENT_END_MARKER.test(untilNextQuote)) {
cursor = nextStart;
break;
}
var url = data.substring(nextStart, nextEnd + 1);
callback(url, tempData);
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
}
function reduceAll(data, context, callback) {
data = byUrl(data, context, callback);
data = byImport(data, context, callback);
return data;
}
module.exports = reduceAll;