var split = require('../utils/split'); var URL_PREFIX = 'url('; var UPPERCASE_URL_PREFIX = 'URL('; var URL_SUFFIX = ')'; var SINGLE_QUOTE_URL_SUFFIX = '\')'; var DOUBLE_QUOTE_URL_SUFFIX = '")'; var DATA_URI_PREFIX_PATTERN = /^\s*['"]?\s*data:/; var DATA_URI_TRAILER_PATTERN = /[\s\};,\/!]/; 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 firstMatch; 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(DOUBLE_QUOTE_URL_SUFFIX, nextStart); } else if (data[nextStart + URL_PREFIX.length] == '\'') { nextEnd = data.indexOf(SINGLE_QUOTE_URL_SUFFIX, nextStart); } else { isDataURI = DATA_URI_PREFIX_PATTERN.test(data.substring(nextStart + URL_PREFIX.length)); if (isDataURI) { firstMatch = split(data.substring(nextStart), DATA_URI_TRAILER_PATTERN, false, '(', ')', true).pop(); if (firstMatch && firstMatch[firstMatch.length - 1] == URL_SUFFIX) { nextEnd = nextStart + firstMatch.length - URL_SUFFIX.length; } else { nextEnd = -1; } } else { nextEnd = data.indexOf(URL_SUFFIX, nextStart); } } // 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;