123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- module.exports = function Tokenizer(data, minifyContext) {
- var chunker = new Chunker(data, 128);
- var chunk = chunker.next();
- var flatBlock = /(^@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport)|\\@.+?)/;
-
- var whatsNext = function(context) {
- var cursor = context.cursor;
- var mode = context.mode;
- var closest;
-
- if (chunk.length == context.cursor) {
- if (chunker.isEmpty())
- return null;
-
- chunk = chunker.next();
- context.cursor = 0;
- }
-
- if (mode == 'body') {
- closest = chunk.indexOf('}', cursor);
- return closest > -1 ?
- [closest, 'bodyEnd'] :
- null;
- }
-
- var nextSpecial = chunk.indexOf('@', context.cursor);
- var nextEscape = mode == 'top' ? chunk.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', context.cursor) : -1;
- var nextBodyStart = chunk.indexOf('{', context.cursor);
- var nextBodyEnd = chunk.indexOf('}', context.cursor);
-
- closest = nextSpecial;
- if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
- closest = nextEscape;
- if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
- closest = nextBodyStart;
- if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
- closest = nextBodyEnd;
-
- if (closest == -1)
- return;
- if (nextEscape === closest)
- return [closest, 'escape'];
- if (nextBodyStart === closest)
- return [closest, 'bodyStart'];
- if (nextBodyEnd === closest)
- return [closest, 'bodyEnd'];
- if (nextSpecial === closest)
- return [closest, 'special'];
- };
-
- var tokenize = function(context) {
- var tokenized = [];
-
- context = context || { cursor: 0, mode: 'top' };
-
- while (true) {
- var next = whatsNext(context);
- if (!next) {
- var whatsLeft = chunk.substring(context.cursor);
- if (whatsLeft.length > 0) {
- tokenized.push(whatsLeft);
- context.cursor += whatsLeft.length;
- }
- break;
- }
-
- var nextSpecial = next[0];
- var what = next[1];
- var nextEnd;
- var oldMode;
-
- if (what == 'special') {
- var firstOpenBraceAt = chunk.indexOf('{', nextSpecial);
- var firstSemicolonAt = chunk.indexOf(';', nextSpecial);
- var isSingle = firstSemicolonAt > -1 && (firstOpenBraceAt == -1 || firstSemicolonAt < firstOpenBraceAt);
- var isBroken = firstOpenBraceAt == -1 && firstSemicolonAt == -1;
- if (isBroken) {
- minifyContext.warnings.push('Broken declaration: \'' + chunk.substring(context.cursor) + '\'.');
- context.cursor = chunk.length;
- } else if (isSingle) {
- nextEnd = chunk.indexOf(';', nextSpecial + 1);
- tokenized.push(chunk.substring(context.cursor, nextEnd + 1));
-
- context.cursor = nextEnd + 1;
- } else {
- nextEnd = chunk.indexOf('{', nextSpecial + 1);
- var block = chunk.substring(context.cursor, nextEnd).trim();
-
- var isFlat = flatBlock.test(block);
- oldMode = context.mode;
- context.cursor = nextEnd + 1;
- context.mode = isFlat ? 'body' : 'block';
- var specialBody = tokenize(context);
- context.mode = oldMode;
-
- tokenized.push({ block: block, body: specialBody });
- }
- } else if (what == 'escape') {
- nextEnd = chunk.indexOf('__', nextSpecial + 1);
- var escaped = chunk.substring(context.cursor, nextEnd + 2);
- tokenized.push(escaped);
-
- context.cursor = nextEnd + 2;
- } else if (what == 'bodyStart') {
- var selector = chunk.substring(context.cursor, nextSpecial).trim();
-
- oldMode = context.mode;
- context.cursor = nextSpecial + 1;
- context.mode = 'body';
- var body = tokenize(context);
- context.mode = oldMode;
-
- tokenized.push({ selector: selector, body: body });
- } else if (what == 'bodyEnd') {
- // extra closing brace at the top level can be safely ignored
- if (context.mode == 'top') {
- var at = context.cursor;
- var warning = chunk[context.cursor] == '}' ?
- 'Unexpected \'}\' in \'' + chunk.substring(at - 20, at + 20) + '\'. Ignoring.' :
- 'Unexpected content: \'' + chunk.substring(at, nextSpecial + 1) + '\'. Ignoring.';
-
- minifyContext.warnings.push(warning);
- context.cursor = nextSpecial + 1;
- continue;
- }
-
- if (context.mode != 'block')
- tokenized = chunk.substring(context.cursor, nextSpecial);
-
- context.cursor = nextSpecial + 1;
-
- break;
- }
- }
-
- return tokenized;
- };
-
- return {
- process: function() {
- return tokenize();
- }
- };
- };
-
- // Divides `data` into chunks of `chunkSize` for faster processing
- var Chunker = function(data, chunkSize) {
- var chunks = [];
- for (var cursor = 0, dataSize = data.length; cursor < dataSize;) {
- var nextCursor = cursor + chunkSize > dataSize ?
- dataSize - 1 :
- cursor + chunkSize;
-
- if (data[nextCursor] != '}')
- nextCursor = data.indexOf('}', nextCursor);
- if (nextCursor == -1)
- nextCursor = data.length - 1;
-
- chunks.push(data.substring(cursor, nextCursor + 1));
- cursor = nextCursor + 1;
- }
-
- return {
- isEmpty: function() {
- return chunks.length === 0;
- },
-
- next: function() {
- return chunks.shift() || '';
- }
- };
- };
|