Socket
Socket
Sign inDemoInstall

csso

Package Overview
Dependencies
9
Maintainers
3
Versions
82
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.5.4 to 1.6.0

lib/compressor/ast/translateWithSourceMap.js

16

HISTORY.md

@@ -0,1 +1,17 @@

## 1.6.0 (February 27, 2016)
- **source maps support**
- parser remake:
- various parsing issues fixed
- fix unicode sequence processing in ident (fixes #191)
- support for flags in attribute selector (fixes #270)
- position (line and column) of parse error (fixes #109)
- 4x performance boost, less memory consumption
- compressor refactoring
- internal AST is using doubly linked lists (with safe transformation support during iteration) instead of arrays
- rename `restructuring` to `restructure` option for `minify()`/`compress()` (`restructuring` is alias for `restructure` now, with lower priority)
- unquote urls when possible (fixes #141, #60)
- setup code coverage and a number of related fixes
- add eslint to check unused things
## 1.5.4 (January 27, 2016)

@@ -2,0 +18,0 @@

262

lib/cli.js
var fs = require('fs');
var path = require('path');
var cli = require('clap');
var SourceMapConsumer = require('source-map').SourceMapConsumer;
var csso = require('./index.js');

@@ -18,11 +20,26 @@

function stat(filename, source, result, time, mem) {
function showStat(filename, source, result, inputMap, map, time, mem) {
function fmt(size) {
return String(size).replace(/\B\d{3}$/, ' $&');
return String(size).split('').reverse().reduce(function(size, digit, idx) {
if (idx && idx % 3 === 0) {
size = ' ' + size;
}
return digit + size;
}, '');
}
console.error('File: ', filename);
map = map || 0;
result -= map;
console.error('Source: ', filename === '<stdin>' ? filename : path.relative(process.cwd(), filename));
if (inputMap) {
console.error('Map source:', inputMap);
}
console.error('Original: ', fmt(source), 'bytes');
console.error('Compressed:', fmt(result), 'bytes', '(' + (100 * result / source).toFixed(2) + '%)');
console.error('Saving: ', fmt(source - result), 'bytes', '(' + (100 * (source - result) / source).toFixed(2) + '%)');
if (map) {
console.error('Source map:', fmt(map), 'bytes', '(' + (100 * map / (result + map)).toFixed(2) + '% of total)');
console.error('Total: ', fmt(map + result), 'bytes');
}
console.error('Time: ', time, 'ms');

@@ -32,2 +49,29 @@ console.error('Memory: ', (mem / (1024 * 1024)).toFixed(3), 'MB');

function showParseError(source, filename, details, message) {
function processLines(start, end) {
return lines.slice(start, end).map(function(line, idx) {
var num = String(start + idx + 1);
while (num.length < maxNumLength) {
num = ' ' + num;
}
return num + ' |' + line;
}).join('\n');
}
var lines = source.split(/\n|\r\n?|\f/);
var column = details.column;
var line = details.line;
var startLine = Math.max(1, line - 2);
var endLine = Math.min(line + 2, lines.length + 1);
var maxNumLength = Math.max(4, String(endLine).length) + 1;
console.error('\nParse error ' + filename + ': ' + message);
console.error(processLines(startLine - 1, line));
console.error(new Array(column + maxNumLength + 2).join('-') + '^');
console.error(processLines(line, endLine));
console.error();
}
function debugLevel(level) {

@@ -38,2 +82,97 @@ // level is undefined when no param -> 1

function resolveSourceMap(source, inputMap, map, inputFile, outputFile) {
var inputMapContent = null;
var inputMapFile = null;
var outputMapFile = null;
switch (map) {
case 'none':
// don't generate source map
map = false;
inputMap = 'none';
break;
case 'inline':
// nothing to do
break;
case 'file':
if (!outputFile) {
console.error('Output filename should be specified when `--map file` is used');
process.exit(2);
}
outputMapFile = outputFile + '.map';
break;
default:
// process filename
if (map) {
// check path is reachable
if (!fs.existsSync(path.dirname(map))) {
console.error('Directory for map file should exists:', path.dirname(path.resolve(map)));
process.exit(2);
}
// resolve to absolute path
outputMapFile = path.resolve(process.cwd(), map);
}
}
switch (inputMap) {
case 'none':
// nothing to do
break;
case 'auto':
if (map) {
// try fetch source map from source
var inputMapComment = source.match(/\/\*# sourceMappingURL=(\S+)\s*\*\/\s*$/);
if (inputFile === '<stdin>') {
inputFile = false;
}
if (inputMapComment) {
// if comment found – value is filename or base64-encoded source map
inputMapComment = inputMapComment[1];
if (inputMapComment.substr(0, 5) === 'data:') {
// decode source map content from comment
inputMapContent = new Buffer(inputMapComment.substr(inputMapComment.indexOf('base64,') + 7), 'base64').toString();
} else {
// value is filename – resolve it as absolute path
if (inputFile) {
inputMapFile = path.resolve(path.dirname(inputFile), inputMapComment);
}
}
} else {
// comment doesn't found - look up file with `.map` extension nearby input file
if (inputFile && fs.existsSync(inputFile + '.map')) {
inputMapFile = inputFile + '.map';
}
}
}
break;
default:
if (inputMap) {
inputMapFile = inputMap;
}
}
// source map placed in external file
if (inputMapFile) {
inputMapContent = fs.readFileSync(inputMapFile, 'utf8');
}
return {
input: inputMapContent,
inputFile: inputMapFile || (inputMapContent ? '<inline>' : false),
output: map,
outputFile: outputMapFile
};
}
var command = cli.create('csso', '[input] [output]')

@@ -43,2 +182,4 @@ .version(require('../package.json').version)

.option('-o, --output <filename>', 'Output file (result outputs to stdout if not set)')
.option('-m, --map <destination>', 'Generate source map. Possible values: none (default), inline, file or <filename>', 'none')
.option('--input-map <source>', 'Input source map. Possible values: none, auto (default) or <filename>', 'auto')
.option('--restructure-off', 'Turns structure minimization off')

@@ -48,4 +189,11 @@ .option('--stat', 'Output statistics in stderr')

.action(function(args) {
var inputFile = this.values.input || args[0];
var outputFile = this.values.output || args[1];
var options = this.values;
var inputFile = options.input || args[0];
var outputFile = options.output || args[1];
var map = options.map;
var inputMap = options.inputMap;
var structureOptimisationOff = options.restructureOff;
var debug = options.debug;
var statistics = options.stat;
var inputStream;

@@ -57,22 +205,96 @@ if (process.stdin.isTTY && !inputFile && !outputFile) {

var structureOptimisationOff = this.values.restructureOff;
var debug = this.values.debug;
var input = inputFile ? fs.createReadStream(inputFile) : process.stdin;
var statistics = this.values.stat;
if (!inputFile) {
inputFile = '<stdin>';
inputStream = process.stdin;
} else {
inputFile = path.resolve(process.cwd(), inputFile);
inputStream = fs.createReadStream(inputFile);
}
readFromStream(input, function(source) {
if (outputFile) {
outputFile = path.resolve(process.cwd(), outputFile);
}
readFromStream(inputStream, function(source) {
var time = process.hrtime();
var mem = process.memoryUsage().heapUsed;
var sourceMap = resolveSourceMap(source, inputMap, map, inputFile, outputFile);
var sourceMapAnnotation = '';
var result;
var result = csso.minify(source, {
restructuring: !structureOptimisationOff,
debug: debug
});
// main action
try {
result = csso.minify(source, {
filename: inputFile,
sourceMap: sourceMap.output,
restructure: !structureOptimisationOff,
debug: debug
});
// for backward capability minify returns a string
if (typeof result === 'string') {
result = {
css: result,
map: null
};
}
} catch (e) {
if (e.parseError) {
showParseError(source, inputFile, e.parseError, e.message);
if (!debug) {
process.exit(2);
}
}
throw e;
}
if (sourceMap.output && result.map) {
// post-processing of source map
if (sourceMap.input) {
// apply input map
result.map.applySourceMap(
new SourceMapConsumer(sourceMap.input),
inputFile
);
} else {
// otherwise add source content
result.map.setSourceContent(inputFile, source);
}
// add source map to result
if (sourceMap.outputFile) {
// write source map to file
fs.writeFileSync(sourceMap.outputFile, result.map.toString(), 'utf-8');
sourceMapAnnotation = '\n' +
'/*# sourceMappingURL=' +
path.relative(outputFile ? path.dirname(outputFile) : process.cwd(), sourceMap.outputFile) +
' */';
} else {
// inline source map
sourceMapAnnotation = '\n' +
'/*# sourceMappingURL=data:application/json;base64,' +
new Buffer(result.map.toString()).toString('base64') +
' */';
}
result.css += sourceMapAnnotation;
}
// output result
if (outputFile) {
fs.writeFileSync(outputFile, result.css, 'utf-8');
} else {
console.log(result.css);
}
// output statistics
if (statistics) {
var timeDiff = process.hrtime(time);
stat(
inputFile || '<stdin>',
showStat(
path.relative(process.cwd(), inputFile),
source.length,
result.length,
result.css.length,
sourceMap.inputFile,
sourceMapAnnotation.length,
parseInt(timeDiff[0] * 1e3 + timeDiff[1] / 1e6),

@@ -82,8 +304,2 @@ process.memoryUsage().heapUsed - mem

}
if (outputFile) {
fs.writeFileSync(outputFile, result, 'utf-8');
} else {
console.log(result);
}
});

@@ -90,0 +306,0 @@ });

351

lib/compressor/ast/gonzalesToInternal.js

@@ -1,14 +0,83 @@

var StyleSheet = function(token) {
var List = require('../../utils/list.js');
function StyleSheet(tokens) {
var rules = new List();
for (var i = 2; i < tokens.length; i++) {
var token = tokens[i];
var type = token[1];
if (type !== 's' &&
type !== 'comment' &&
type !== 'unknown') {
rules.insert(List.createItem(convertToInternal(token)));
}
}
return {
type: 'StyleSheet',
info: tokens[0],
avoidRulesMerge: false,
rules: rules
};
}
function Atrule(token, expression, block) {
if (expression instanceof List) {
expression = {
type: 'AtruleExpression',
info: expression.head ? expression.head.data.info : null,
sequence: expression,
id: null
};
}
return {
type: 'Atrule',
info: token[0],
rules: token.filter(function(item, idx) {
return idx >= 2 &&
item[1] !== 's' &&
item[1] !== 'comment' &&
item[1] !== 'unknown';
}).map(convertToInternal)
name: token[2][2][2],
expression: expression,
block: block
};
};
}
function Declaration(token) {
return {
type: 'Declaration',
info: token[0],
property: convertToInternal(token[2]),
value: convertToInternal(token[3]),
id: 0,
length: 0,
fingerprint: null
};
}
function Value(token) {
var important = false;
var end = token.length - 1;
for (; end >= 2; end--) {
var type = token[end][1];
if (type !== 's' && type !== 'comment') {
if (type === 'important' && !important) {
important = true;
} else {
break;
}
}
}
return {
type: 'Value',
info: token[0],
important: important,
sequence: trimSC(token, 2, end)
};
}
function firstNonSC(token) {
return convertToInternal(token[skipSC(token, 2)]);
}
function skipSC(token, offset) {

@@ -26,2 +95,4 @@ for (; offset < token.length; offset++) {

function trimSC(token, start, end) {
var list = new List();
start = skipSC(token, start);

@@ -35,14 +106,14 @@ for (; end >= start; end--) {

if (end < start) {
return [];
for (var i = start; i <= end; i++) {
var node = convertToInternal(token[i]);
if (node) {
list.insert(List.createItem(node));
}
}
return token
.slice(start, end + 1)
.map(convertToInternal)
.filter(Boolean);
return list;
}
function argumentList(token) {
var result = [];
var list = new List();
var args = token;

@@ -53,7 +124,7 @@ var start = 2;

if (args[i][1] === 'operator' && args[i][2] === ',') {
result.push({
list.insert(List.createItem({
type: 'Argument',
info: {},
sequence: trimSC(args, start, i - 1)
});
}));
start = i + 1;

@@ -64,11 +135,11 @@ }

var lastArg = trimSC(args, start, args.length - 1);
if (lastArg.length || result.length) {
result.push({
if (lastArg.head || list.head) {
list.insert(List.createItem({
type: 'Argument',
info: {},
sequence: lastArg
});
}));
}
return result;
return list;
}

@@ -79,22 +150,14 @@

atruleb: function(token) {
return {
type: 'Atrule',
info: token[0],
name: token[2][2][2],
expression: {
type: 'AtruleExpression',
info: {},
sequence: trimSC(token, 3, token.length - 2)
},
block: convertToInternal(token[token.length - 1])
};
return Atrule(
token,
trimSC(token, 3, token.length - 2),
convertToInternal(token[token.length - 1])
);
},
atruler: function(token) {
return {
type: 'Atrule',
info: token[0],
name: token[2][2][2],
expression: convertToInternal(token[3]),
block: convertToInternal(token[4])
};
return Atrule(
token,
convertToInternal(token[3]),
convertToInternal(token[4])
);
},

@@ -105,3 +168,4 @@ atrulerq: function(token) {

info: token[0],
sequence: trimSC(token, 2, token.length - 1)
sequence: trimSC(token, 2, token.length - 1),
id: null
};

@@ -111,31 +175,33 @@ },

atrules: function(token) {
return {
type: 'Atrule',
info: token[0],
name: token[2][2][2],
expression: {
type: 'AtruleExpression',
info: {},
sequence: trimSC(token, 3, token.length - 1)
},
block: null
};
return Atrule(
token,
trimSC(token, 3, token.length - 1),
null
);
},
attrib: function(token) {
var offset = 2;
var name;
var operator = null;
var value = null;
var flags = null;
offset = skipSC(token, 2);
var name = convertToInternal(token[offset]);
name = convertToInternal(token[offset]);
if (token[offset + 1] && token[offset + 1][1] === 'namespace') {
name.name += '|' + token[offset + 2][2];
offset += 2;
}
offset = skipSC(token, offset + 1);
var operator = token[offset] ? token[offset][2] : null;
if (offset < token.length) {
operator = token[offset][2];
offset = skipSC(token, offset + 1);
var value = convertToInternal(token[offset]);
offset = skipSC(token, offset + 1);
value = convertToInternal(token[offset]);
if (offset < token.length) {
offset = skipSC(token, offset + 1);
if (offset < token.length && token[offset][1] === 'attribFlags') {
flags = token[offset][2];
}
}
}
return {

@@ -146,3 +212,4 @@ type: 'Attribute',

operator: operator,
value: value
value: value,
flags: flags
};

@@ -152,8 +219,17 @@ },

block: function(token) {
var declarations = new List();
for (var i = 2; i < token.length; i++) {
var item = token[i];
var type = item[1];
if (type === 'declaration' || type === 'filter') {
declarations.insert(List.createItem(convertToInternal(item)));
}
}
return {
type: 'Block',
info: token[0],
declarations: token.filter(function(item, idx) {
return idx >= 2 && (item[1] === 'declaration' || item[1] === 'filter');
}).map(convertToInternal)
declarations: declarations
};

@@ -185,10 +261,3 @@ },

comment: false,
declaration: function(token) {
return {
type: 'Declaration',
info: token[0],
property: convertToInternal(token[2]),
value: convertToInternal(token[3])
};
},
declaration: Declaration,
decldelim: false, // redundant

@@ -204,17 +273,4 @@ delim: false, // redundant

},
filter: function(token) {
return {
type: 'Declaration',
info: token[0],
property: convertToInternal(token[2]),
value: convertToInternal(token[3])
};
},
filterv: function(token) {
return {
type: 'Value',
info: token[0],
sequence: trimSC(token, 2, token.length - 1)
};
},
filter: Declaration,
filterv: Value,
functionExpression: function(token) {

@@ -224,9 +280,9 @@ return {

name: 'expression',
arguments: [{
arguments: new List([{
type: 'Argument',
sequence: [{
sequence: new List([{
type: 'Raw',
value: token[2]
}]
}]
}])
}])
};

@@ -250,8 +306,2 @@ },

},
important: function(token) {
return {
type: 'Important',
info: token[0]
};
},
namespace: false,

@@ -269,8 +319,13 @@ nth: function(token) {

name: token[2][2],
arguments: [{
arguments: new List([{
type: 'Argument',
sequence: token.filter(function(item, idx) {
return idx >= 3 && item[1] !== 's' && item[1] !== 'comment';
}).map(convertToInternal)
}]
sequence: new List(
token
.slice(3)
.filter(function(item) {
return item[1] !== 's' && item[1] !== 'comment';
})
.map(convertToInternal)
)
}])
};

@@ -303,3 +358,3 @@ },

info: token[0],
value: trimSC(token, 2, token.length - 1)[0]
value: firstNonSC(token)
};

@@ -323,5 +378,5 @@ },

type: 'Negation',
sequence: [
sequence: new List([
types.simpleselector(value[3][2])
]
])
};

@@ -362,14 +417,8 @@ }

var selector = convertToInternal(token[2]);
var block;
var block = convertToInternal(token[3]);
if (token.length === 4) {
block = convertToInternal(token[3]);
} else {
block = selector;
selector = null;
}
return {
type: 'Ruleset',
info: token[0],
pseudoSignature: null,
selector: selector,

@@ -387,4 +436,6 @@ block: block

var last = 'delim';
var badSelector = false;
var selectors = token.filter(function(item, idx) {
var selectors = new List();
for (var i = 2; i < token.length; i++) {
var item = token[i];
var type = item[1];

@@ -394,3 +445,5 @@

if (last === type) {
badSelector = true;
// bad selector
selectors = new List();
break;
}

@@ -400,4 +453,6 @@ last = type;

return idx >= 2 && type === 'simpleselector';
}).map(convertToInternal);
if (type === 'simpleselector') {
selectors.insert(List.createItem(convertToInternal(item)));
}
}

@@ -407,8 +462,5 @@ // check selector is valid since gonzales parses selectors

// w/o this check broken selector will be repaired and broken ruleset apply;
// return null in this case so compressor could remove ruleset with no selector
if (badSelector ||
last === 'delim' ||
selectors.length === 0 ||
selectors[selectors.length - 1].sequence.length === 0) {
return null;
// make selector empty so compressor can remove ruleset with no selector
if (last === 'delim' || (!selectors.isEmpty() && selectors.last().sequence.isEmpty())) {
selectors = new List();
}

@@ -430,14 +482,12 @@

simpleselector: function(token) {
var sequence = [];
for (var i = skipSC(token, 2), needCombinator = false; i < token.length; i++) {
var sequence = new List();
var combinator = null;
for (var i = skipSC(token, 2); i < token.length; i++) {
var item = token[i];
switch (item[1]) {
case 'combinator':
needCombinator = false;
sequence.push(item);
break;
case 's':
if (sequence[sequence.length - 1][1] !== 'combinator') {
needCombinator = item;
if (!combinator) {
combinator = [item[0], 'combinator', ' '];
}

@@ -449,18 +499,13 @@ break;

case 'namespace':
// ident namespace ident -> ident '|' ident
sequence[sequence.length - 1] = [
{},
'ident',
sequence[sequence.length - 1][2] + '|' + token[i + 1][2]
];
i++;
case 'combinator':
combinator = item;
break;
default:
if (needCombinator) {
sequence.push([needCombinator[0], 'combinator', ' ']);
if (combinator !== null) {
sequence.insert(List.createItem(convertToInternal(combinator)));
}
needCombinator = false;
sequence.push(item);
combinator = null;
sequence.insert(List.createItem(convertToInternal(item)));
}

@@ -472,3 +517,5 @@ }

info: token[0],
sequence: sequence.map(convertToInternal)
sequence: sequence,
id: null,
compareMarker: null
};

@@ -496,12 +543,6 @@ },

info: token[0],
value: trimSC(token, 2, token.length - 1)[0]
value: firstNonSC(token)
};
},
value: function(token) {
return {
type: 'Value',
info: token[0],
sequence: trimSC(token, 2, token.length - 1)
};
},
value: Value,
vhash: function(token) {

@@ -516,3 +557,3 @@ return {

function convertToInternal(token, parent, stack) {
function convertToInternal(token) {
if (token) {

@@ -519,0 +560,0 @@ var type = token[1];

function eachDelim(node, type, itemsProperty, delimeter) {
var result = [node.info, type];
var items = node[itemsProperty];
var list = node[itemsProperty];
for (var i = 0; i < items.length; i++) {
result.push(toGonzales(items[i]));
list.each(function(data, item) {
result.push(toGonzales(data));
if (i !== items.length - 1) {
if (item.next) {
result.push(delimeter.slice());
}
}
});

@@ -17,8 +17,8 @@ return result;

function buildArguments(body, args) {
for (var i = 0; i < args.length; i++) {
body.push.apply(body, args[i].sequence.map(toGonzales));
if (i !== args.length - 1) {
args.each(function(data, item) {
body.push.apply(body, data.sequence.map(toGonzales));
if (item.next) {
body.push([{}, 'operator', ',']);
}
}
});
}

@@ -51,3 +51,3 @@

if (node.expression && node.expression.sequence.length) {
if (node.expression && !node.expression.sequence.isEmpty()) {
if (type === 'atruler') {

@@ -86,14 +86,8 @@ result.push([

case 'Ruleset':
return node.selector
? [
node.info,
'ruleset',
toGonzales(node.selector),
toGonzales(node.block)
]
: [
node.info,
'ruleset',
toGonzales(node.block)
];
return [
node.info,
'ruleset',
toGonzales(node.selector),
toGonzales(node.block)
];

@@ -109,13 +103,5 @@ case 'Selector':

node.sequence.forEach(function(item) {
item = toGonzales(item);
if (item[1] === 'ident' && /\|/.test(item[2])) {
result.push(
[{}, 'ident', item[2].split('|')[0]],
[{}, 'namespace'],
[{}, 'ident', item[2].split('|')[1]]
);
} else {
result.push(item);
}
node.sequence.each(function(data) {
var node = toGonzales(data);
result.push(node);
});

@@ -145,18 +131,19 @@

if (/\|/.test(node.name.name)) {
result = result.concat([
[{}, 'ident', node.name.name.split('|')[0]],
[{}, 'namespace'],
[{}, 'ident', node.name.name.split('|')[1]]
]);
} else {
result.push([{}, 'ident', node.name.name]);
}
result.push([{}, 'ident', node.name.name]);
if (node.operator) {
if (node.operator !== null) {
result.push([{}, 'attrselector', node.operator]);
if (node.value !== null) {
result.push(toGonzales(node.value));
if (node.flags !== null) {
if (node.value.type !== 'String') {
result.push([{}, 's', ' ']);
}
result.push([{}, 'attribFlags', node.flags]);
}
}
}
if (node.value) {
result.push(toGonzales(node.value));
}
return result;

@@ -214,5 +201,2 @@

case 'Argument':
return;
case 'Block':

@@ -224,4 +208,4 @@ return eachDelim(node, 'block', 'declarations', [{}, 'decldelim']);

node.info,
node.value.sequence.length &&
node.value.sequence[0].type === 'Progid' &&
!node.value.sequence.isEmpty() &&
node.value.sequence.first().type === 'Progid' &&
/(-[a-z]+-|[\*-_])?filter$/.test(node.property.name)

@@ -242,9 +226,7 @@ ? 'filter'

// case 'AtruleExpression':
case 'Value':
return [
var result = [
node.info,
node.sequence.length &&
node.sequence[0].type === 'Progid'
!node.sequence.isEmpty() &&
node.sequence.first().type === 'Progid'
? 'filterv'

@@ -254,2 +236,8 @@ : 'value'

if (node.important) {
result.push([{}, 'important']);
}
return result;
case 'Url':

@@ -314,5 +302,2 @@ return [node.info, 'uri', toGonzales(node.value)];

case 'Important':
return [node.info, 'important'];
case 'Percentage':

@@ -327,9 +312,10 @@ return [node.info, 'percentage', [{}, 'number', node.value]];

// nothing to do
// case 'Argument':
default:
console.warn('Unknown node type:', node);
throw new Error('Unknown node type: ' + node.type);
}
}
module.exports = function(node) {
return node ? toGonzales(node) : [];
};
module.exports = toGonzales;

@@ -1,178 +0,150 @@

function each(array, buffer) {
for (var i = 0; i < array.length; i++) {
translate(array[i], buffer, array, i);
function each(list) {
if (list.head && list.head === list.tail) {
return translate(list.head.data);
}
return list.map(translate).join('');
}
function eachDelim(array, buffer, delimeter) {
for (var i = 0; i < array.length; i++) {
translate(array[i], buffer, array, i);
function eachDelim(list, delimeter) {
if (list.head && list.head === list.tail) {
return translate(list.head.data);
}
if (i !== array.length - 1) {
buffer.push(delimeter);
}
}
return list.map(translate).join(delimeter);
}
function translate(node, buffer, array, i) {
function translate(node) {
switch (node.type) {
case 'StyleSheet':
return each(node.rules);
case 'Atrule':
buffer.push('@', node.name);
if (node.expression && node.expression.sequence.length) {
buffer.push(' ');
translate(node.expression, buffer);
var result = '@' + node.name;
if (node.expression && !node.expression.sequence.isEmpty()) {
result += ' ' + translate(node.expression);
}
if (node.block) {
buffer.push('{');
translate(node.block, buffer);
buffer.push('}');
return result + '{' + translate(node.block) + '}';
} else {
buffer.push(';');
return result + ';';
}
break;
case 'Ruleset':
return translate(node.selector) + '{' + translate(node.block) + '}';
case 'Selector':
return eachDelim(node.selectors, ',');
case 'SimpleSelector':
return each(node.sequence);
case 'Declaration':
translate(node.property, buffer);
buffer.push(':');
translate(node.value, buffer);
break;
return translate(node.property) + ':' + translate(node.value);
case 'Property':
return node.name;
case 'Value':
return node.important
? each(node.sequence) + '!important'
: each(node.sequence);
case 'Attribute':
buffer.push('[');
translate(node.name, buffer);
if (node.operator) {
buffer.push(node.operator);
var result = translate(node.name);
if (node.operator !== null) {
result += node.operator;
if (node.value !== null) {
result += translate(node.value);
if (node.flags !== null) {
result += (node.value.type !== 'String' ? ' ' : '') + node.flags;
}
}
}
if (node.value) {
translate(node.value, buffer);
}
buffer.push(']');
break;
return '[' + result + ']';
case 'FunctionalPseudo':
buffer.push(':', node.name, '(');
eachDelim(node.arguments, buffer, ',');
buffer.push(')');
break;
return ':' + node.name + '(' + eachDelim(node.arguments, ',') + ')';
case 'Function':
buffer.push(node.name, '(');
eachDelim(node.arguments, buffer, ',');
buffer.push(')');
break;
return node.name + '(' + eachDelim(node.arguments, ',') + ')';
case 'Block':
eachDelim(node.declarations, buffer, ';');
break;
return eachDelim(node.declarations, ';');
case 'Ruleset':
if (node.selector) {
translate(node.selector, buffer);
}
buffer.push('{');
translate(node.block, buffer);
buffer.push('}');
break;
case 'Selector':
eachDelim(node.selectors, buffer, ',');
break;
case 'Negation':
buffer.push(':not(');
eachDelim(node.sequence, buffer, ',');
buffer.push(')');
break;
return ':not(' + eachDelim(node.sequence, ',') + ')';
case 'Braces':
buffer.push(node.open);
each(node.sequence, buffer);
buffer.push(node.close);
break;
return node.open + each(node.sequence) + node.close;
case 'Argument':
case 'AtruleExpression':
case 'Value':
case 'SimpleSelector':
each(node.sequence, buffer);
break;
return each(node.sequence);
case 'StyleSheet':
each(node.rules, buffer);
break;
case 'Url':
buffer.push('url(');
translate(node.value, buffer);
buffer.push(')');
break;
return 'url(' + translate(node.value) + ')';
case 'Progid':
translate(node.value, buffer);
break;
return translate(node.value);
case 'Property':
case 'Combinator':
return node.name;
case 'Identifier':
buffer.push(node.name);
break;
return node.name;
case 'PseudoClass':
buffer.push(':', node.name);
break;
return ':' + node.name;
case 'PseudoElement':
buffer.push('::', node.name);
break;
return '::' + node.name;
case 'Class':
buffer.push('.', node.name);
break;
return '.' + node.name;
case 'Dimension':
buffer.push(node.value, node.unit);
break;
case 'Id':
return '#' + node.name;
case 'Id':
buffer.push('#', node.name);
break;
case 'Hash':
buffer.push('#', node.value);
break;
return '#' + node.value;
case 'Dimension':
return node.value + node.unit;
case 'Nth':
return node.value;
case 'Number':
return node.value;
case 'String':
return node.value;
case 'Operator':
return node.value;
case 'Raw':
buffer.push(node.value);
break;
return node.value;
case 'Important': // remove
buffer.push('!important');
break;
case 'Percentage':
buffer.push(node.value, '%');
break;
return node.value + '%';
case 'Space':
buffer.push(' ');
break;
return ' ';
case 'Comment':
buffer.push('/*', node.value, '*/');
break;
return '/*' + node.value + '*/';
default:
console.warn('Unknown node type:', node);
throw new Error('Unknown node type: ' + node.type);
}
}
module.exports = function(node) {
var buffer = [];
translate(node, buffer);
return buffer.join('');
};
module.exports = translate;

@@ -1,148 +0,163 @@

function each(array, walker, parent) {
for (var i = 0; i < array.length; i++) {
var item = array[i];
var result = walker.call(this, item, parent, array, i);
function walkRules(node, item, list) {
switch (node.type) {
case 'StyleSheet':
var oldStylesheet = this.stylesheet;
this.stylesheet = node;
if (result === null) {
array.splice(i, 1);
i--;
} else if (result && result !== item) {
array.splice(i, 1, result);
}
}
}
node.rules.each(walkRules, this);
function eachRight(array, walker, parent) {
for (var i = array.length - 1; i >= 0; i--) {
var item = array[i];
var result = walker.call(this, item, parent, array, i);
if (result === null) {
array.splice(i, 1);
} else if (result && result !== item) {
array.splice(i, 1, result);
}
}
}
function walkRules(node, parent, array, index) {
switch (node.type) {
case 'StyleSheet':
each.call(this, node.rules, walkRules, node);
this.stylesheet = oldStylesheet;
break;
case 'Atrule':
if (node.block) {
if (node.block !== null) {
walkRules.call(this, node.block);
}
return this.fn(node, parent, array, index);
this.fn(node, item, list);
break;
case 'Ruleset':
return this.fn(node, parent, array, index);
this.fn(node, item, list);
break;
}
}
function walkRulesRight(node, parent, array, index) {
function walkRulesRight(node, item, list) {
switch (node.type) {
case 'StyleSheet':
eachRight.call(this, node.rules, walkRulesRight, node);
var oldStylesheet = this.stylesheet;
this.stylesheet = node;
node.rules.eachRight(walkRulesRight, this);
this.stylesheet = oldStylesheet;
break;
case 'Atrule':
if (node.block) {
if (node.block !== null) {
walkRulesRight.call(this, node.block);
}
return this.fn(node, parent, array, index);
this.fn(node, item, list);
break;
case 'Ruleset':
return this.fn(node, parent, array, index);
this.fn(node, item, list);
break;
}
}
function walkAll(node, parent, array, index) {
this.stack.push(node);
function walkAll(node, item, list) {
switch (node.type) {
case 'StyleSheet':
var oldStylesheet = this.stylesheet;
this.stylesheet = node;
switch (node.type) {
node.rules.each(walkAll, this);
this.stylesheet = oldStylesheet;
break;
case 'Atrule':
if (node.expression) {
walkAll.call(this, node.expression, node);
if (node.expression !== null) {
walkAll.call(this, node.expression);
}
if (node.block) {
walkAll.call(this, node.block, node);
if (node.block !== null) {
walkAll.call(this, node.block);
}
break;
case 'Declaration':
walkAll.call(this, node.property, node);
walkAll.call(this, node.value, node);
break;
case 'Ruleset':
this.ruleset = node;
case 'Attribute':
walkAll.call(this, node.name, node);
if (node.value) {
walkAll.call(this, node.value, node);
if (node.selector !== null) {
walkAll.call(this, node.selector);
}
walkAll.call(this, node.block);
this.ruleset = null;
break;
case 'FunctionalPseudo':
case 'Function':
each.call(this, node.arguments, walkAll, node);
case 'Selector':
node.selectors.each(walkAll, this);
break;
case 'Block':
each.call(this, node.declarations, walkAll, node);
node.declarations.each(walkAll, this);
break;
case 'Ruleset':
if (node.selector) {
walkAll.call(this, node.selector, node);
case 'Declaration':
this.declaration = node;
walkAll.call(this, node.property);
walkAll.call(this, node.value);
this.declaration = null;
break;
case 'Attribute':
walkAll.call(this, node.name);
if (node.value !== null) {
walkAll.call(this, node.value);
}
walkAll.call(this, node.block, node);
break;
case 'Selector':
each.call(this, node.selectors, walkAll, node);
case 'FunctionalPseudo':
case 'Function':
this['function'] = node;
node.arguments.each(walkAll, this);
this['function'] = null;
break;
case 'Value':
case 'Argument':
case 'AtruleExpression':
case 'SimpleSelector':
case 'Braces':
case 'Negation':
case 'Value':
case 'SimpleSelector':
each.call(this, node.sequence, walkAll, node);
node.sequence.each(walkAll, this);
break;
case 'StyleSheet':
each.call(this, node.rules, walkAll, node);
break;
case 'Url':
case 'Progid':
walkAll.call(this, node.value, node);
walkAll.call(this, node.value);
break;
case 'Property':
case 'Combinator':
case 'Dimension':
case 'Hash':
case 'Identifier':
case 'Important': // remove
case 'Nth':
case 'Class':
case 'Id':
case 'Percentage':
case 'PseudoClass':
case 'PseudoElement':
case 'Space':
case 'Number':
case 'String':
case 'Operator':
case 'Raw':
break;
// nothig to do with
// case 'Property':
// case 'Combinator':
// case 'Dimension':
// case 'Hash':
// case 'Identifier':
// case 'Nth':
// case 'Class':
// case 'Id':
// case 'Percentage':
// case 'PseudoClass':
// case 'PseudoElement':
// case 'Space':
// case 'Number':
// case 'String':
// case 'Operator':
// case 'Raw':
}
this.stack.pop(node);
this.fn(node, item, list);
}
return this.fn(node, parent, array, index);
function createContext(root, fn) {
var context = {
fn: fn,
root: root,
stylesheet: null,
ruleset: null,
declaration: null,
function: null
};
return context;
}

@@ -152,22 +167,10 @@

all: function(root, fn) {
walkAll.call({
fn: fn,
root: root,
stack: []
}, root);
walkAll.call(createContext(root, fn), root);
},
rules: function(root, fn) {
walkRules.call({
fn: fn,
root: root,
stack: []
}, root);
walkRules.call(createContext(root, fn), root);
},
rulesRight: function(root, fn) {
walkRulesRight.call({
fn: fn,
root: root,
stack: []
}, root);
walkRulesRight.call(createContext(root, fn), root);
}
};

@@ -1,2 +0,2 @@

module.exports = function cleanAtrule(node, parent, array, i) {
module.exports = function cleanAtrule(node, item, list) {
if (node.block) {

@@ -6,8 +6,10 @@ // otherwise removed at-rule don't prevent @import for removal

if (node.block.type === 'Block' && !node.block.declarations.length) {
return null;
if (node.block.type === 'Block' && node.block.declarations.isEmpty()) {
list.remove(item);
return;
}
if (node.block.type === 'StyleSheet' && !node.block.rules.length) {
return null;
if (node.block.type === 'StyleSheet' && node.block.rules.isEmpty()) {
list.remove(item);
return;
}

@@ -18,9 +20,11 @@ }

case 'charset':
if (!node.expression.sequence.length) {
return null;
if (node.expression.sequence.isEmpty()) {
list.remove(item);
return;
}
// if there is any rule before @charset -> remove it
if (i) {
return null;
if (item.prev) {
list.remove(item);
return;
}

@@ -32,3 +36,4 @@

if (!this.root.firstAtrulesAllowed) {
return null;
list.remove(item);
return;
}

@@ -38,7 +43,6 @@

// remove it
for (i = i - 1; i >= 0; i--) {
var rule = array[i];
list.prevUntil(item.prev, function(rule) {
if (rule.type === 'Atrule') {
if (rule.name === 'import' || rule.name === 'charset') {
continue;
return;
}

@@ -48,4 +52,5 @@ }

this.root.firstAtrulesAllowed = false;
return null;
}
list.remove(item);
return true;
}, this);

@@ -52,0 +57,0 @@ break;

@@ -1,5 +0,5 @@

module.exports = function cleanDeclartion(node) {
if (!node.value.sequence.length) {
return null;
module.exports = function cleanDeclartion(node, item, list) {
if (node.value.sequence.isEmpty()) {
list.remove(item);
}
};

@@ -8,6 +8,6 @@ var handlers = {

module.exports = function(node, parent, array, index) {
module.exports = function(node, item, list) {
if (handlers.hasOwnProperty(node.type)) {
return handlers[node.type].call(this, node, parent, array, index);
handlers[node.type].call(this, node, item, list);
}
};

@@ -1,5 +0,6 @@

module.exports = function cleanRuleset(node) {
if (!node.selector || !node.block.declarations.length) {
return null;
module.exports = function cleanRuleset(node, item, list) {
if (node.selector.selectors.isEmpty() ||
node.block.declarations.isEmpty()) {
list.remove(item);
}
};
function canCleanWhitespace(node, left) {
switch (node.type) {
case 'Important':
case 'Nth':
return true;
case 'Operator':
return node.value !== '+' && node.value !== '-';
if (node.type === 'Operator') {
return node.value !== '+' && node.value !== '-';
}

@@ -21,5 +16,5 @@

module.exports = function cleanWhitespace(node, parent, array, index) {
var prev = array[index - 1];
var next = array[index + 1];
module.exports = function cleanWhitespace(node, item, list) {
var prev = item.prev && item.prev.data;
var next = item.next && item.next.data;
var prevType = prev.type;

@@ -48,11 +43,7 @@ var nextType = next.type;

if ((prevType === 'Identifier' && prev.name === '*') ||
(nextType === 'Identifier' && next.name === '*')) {
return null;
}
if (canCleanWhitespace(next, false) ||
canCleanWhitespace(prev, true)) {
return null;
list.remove(item);
return;
}
};
var compressKeyframes = require('./atrule/keyframes.js');
module.exports = function(node, parent, array, index) {
module.exports = function(node, item, list) {
// compress @keyframe selectors
if (/^(-[a-z\d]+-)?keyframes$/.test(node.name)) {
compressKeyframes(node, parent, array, index);
compressKeyframes(node, item, list);
}
};
module.exports = function(node) {
node.block.rules.forEach(function(ruleset) {
ruleset.selector.selectors.forEach(function(simpleselector) {
var array = simpleselector.sequence;
for (var i = 0; i < array.length; i++) {
var part = array[i];
if (part.type === 'Percentage' && part.value === '100') {
array[i] = {
node.block.rules.each(function(ruleset) {
ruleset.selector.selectors.each(function(simpleselector) {
simpleselector.sequence.each(function(data, item) {
if (data.type === 'Percentage' && data.value === '100') {
item.data = {
type: 'Identifier',
info: array[i].info,
info: data.info,
name: 'to'
};
} else if (part.type === 'Identifier' && part.name === 'from') {
array[i] = {
} else if (data.type === 'Identifier' && data.name === 'from') {
item.data = {
type: 'Percentage',
info: array[i].info,
info: data.info,
value: '0'
};
}
}
});
});
});
};

@@ -0,1 +1,2 @@

var List = require('../../utils/list.js');
var packNumber = require('./Number.js').pack;

@@ -245,12 +246,13 @@

function parseFunctionArgs(functionArgs, count, rgb) {
var argument = functionArgs.head;
var args = [];
for (var i = 0; i < functionArgs.length; i++) {
// each arguments should just one node
var items = functionArgs[i].sequence;
while (argument !== null) {
var argumentPart = argument.data.sequence.head;
var wasValue = false;
for (var j = 0; j < items.length; j++) {
var value = items[j];
while (argumentPart !== null) {
var value = argumentPart.data;
var type = value.type;
switch (type) {

@@ -269,2 +271,3 @@ case 'Number':

break;
case 'Operator':

@@ -280,3 +283,7 @@ if (wasValue || value.value !== '+') {

}
argumentPart = argumentPart.next;
}
argument = argument.next;
}

@@ -318,3 +325,3 @@

return args.map(function(arg, idx) {
return args.map(function(arg) {
var value = Math.max(0, arg.value);

@@ -352,3 +359,3 @@

function compressFunction(node, parent, array, index) {
function compressFunction(node, item, list) {
var functionName = node.name;

@@ -372,14 +379,14 @@ var args;

// replace argument values for normalized/interpolated
node.arguments.forEach(function(argument, idx) {
var value = argument.sequence[0];
node.arguments.each(function(argument) {
var item = argument.sequence.head;
if (value.type === 'Operator') {
value = argument.sequence[1];
if (item.data.type === 'Operator') {
item = item.next;
}
argument.sequence = [{
argument.sequence = new List([{
type: 'Number',
info: value.info,
value: packNumber(args[idx])
}];
info: item.data.info,
value: packNumber(args.shift())
}]);
});

@@ -416,10 +423,10 @@

// check if color is not at the end and not followed by space
var next = array && index < array.length - 1 ? array[index + 1] : null;
if (next && next.type !== 'Space') {
array.splice(index + 1, 0, {
var next = item.next;
if (next && next.data.type !== 'Space') {
list.insert(list.createItem({
type: 'Space'
});
}), next);
}
var color = {
item.data = {
type: 'Hash',

@@ -430,10 +437,8 @@ info: node.info,

return compressHex(color) || color;
compressHex(item.data, item);
}
}
function compressIdent(node, parent) {
var parentType = parent.type;
if (parentType !== 'Value' && parentType !== 'Function') {
function compressIdent(node, item) {
if (this.declaration === null) {
return;

@@ -443,8 +448,9 @@ }

var color = node.name.toLowerCase();
var hex = NAME_TO_HEX[color];
if (hex) {
if (NAME_TO_HEX.hasOwnProperty(color)) {
var hex = NAME_TO_HEX[color];
if (hex.length + 1 <= color.length) {
// replace for shorter hex value
return {
item.data = {
type: 'Hash',

@@ -466,3 +472,3 @@ info: node.info,

function compressHex(node) {
function compressHex(node, item) {
var color = node.value.toLowerCase();

@@ -479,3 +485,3 @@

if (HEX_TO_NAME[color]) {
return {
item.data = {
type: 'Identifier',

@@ -482,0 +488,0 @@ info: node.info,

@@ -16,3 +16,3 @@ var packNumber = require('./Number.js').pack;

module.exports = function compressDimension(node, parent) {
module.exports = function compressDimension(node, item) {
var value = packNumber(node.value);

@@ -23,5 +23,5 @@ var unit = node.unit;

if (value === '0' && !NON_LENGTH_UNIT[unit]) {
if (value === '0' && !NON_LENGTH_UNIT.hasOwnProperty(unit)) {
// issue #200: don't remove units in flex property as it could change value meaning
if (parent.type === 'Value' && this.stack[this.stack.length - 2].property.name === 'flex') {
if (this.declaration.property.name === 'flex') {
return;

@@ -31,13 +31,7 @@ }

// issue #222: don't remove units inside calc
for (var i = this.stack.length - 1; i >= 0; i--) {
var cursor = this.stack[i];
if (cursor.type === 'Function' && cursor.name === 'calc') {
return;
}
if (cursor.type !== 'Braces' && cursor.type !== 'Argument') {
break;
}
if (this['function'] && this['function'].name === 'calc') {
return;
}
return {
item.data = {
type: 'Number',

@@ -44,0 +38,0 @@ info: node.info,

@@ -9,2 +9,3 @@ var handlers = {

String: require('./String.js'),
Url: require('./Url.js'),
Hash: require('./color.js').compressHex,

@@ -15,6 +16,6 @@ Identifier: require('./color.js').compressIdent,

module.exports = function(node, parent, array, index) {
module.exports = function(node, item, list) {
if (handlers.hasOwnProperty(node.type)) {
return handlers[node.type].call(this, node, parent, array, index);
handlers[node.type].call(this, node, item, list);
}
};

@@ -12,3 +12,3 @@ function packNumber(value) {

if (value === '' || value === '-') {
if (value.length === 0 || value === '-') {
value = '0';

@@ -15,0 +15,0 @@ }

@@ -1,2 +0,4 @@

module.exports = function compressBackground(value) {
var List = require('../../../utils/list.js');
module.exports = function compressBackground(node) {
function lastType() {

@@ -13,4 +15,3 @@ if (buffer.length) {

if (!buffer.length ||
(buffer.length === 1 && buffer[0].type === 'Important')) {
if (!buffer.length) {
buffer.unshift(

@@ -39,3 +40,3 @@ {

value.sequence.forEach(function(node) {
node.sequence.each(function(node) {
if (node.type === 'Operator' && node.value === ',') {

@@ -66,3 +67,3 @@ flush();

flush();
value.sequence = newValue;
node.sequence = new List(newValue);
};
module.exports = function compressFontWeight(node) {
var value = node.sequence[0];
var value = node.sequence.head.data;

@@ -7,3 +7,3 @@ if (value.type === 'Identifier') {

case 'normal':
node.sequence[0] = {
node.sequence.head.data = {
type: 'Number',

@@ -15,3 +15,3 @@ info: value.info,

case 'bold':
node.sequence[0] = {
node.sequence.head.data = {
type: 'Number',

@@ -18,0 +18,0 @@ info: value.info,

@@ -1,50 +0,45 @@

module.exports = function compressFont(value) {
var array = value.sequence;
module.exports = function compressFont(node) {
var list = node.sequence;
for (var i = array.length - 1; i >= 0; i--) {
var node = array[i];
list.eachRight(function(node, item) {
if (node.type === 'Identifier') {
if (node.name === 'bold') {
array[i] = {
item.data = {
type: 'Number',
info: value.info,
info: node.info,
value: '700'
};
} else if (node.name === 'normal') {
var prev = i ? array[i - 1] : null;
var prev = item.prev;
if (prev && prev.type === 'Operator' && prev.value === '/') {
array.splice(--i, 2);
} else {
array.splice(i, 1);
if (prev && prev.data.type === 'Operator' && prev.data.value === '/') {
this.remove(prev);
}
this.remove(item);
} else if (node.name === 'medium') {
var next = i < array.length - 1 ? array[i + 1] : null;
var next = item.next;
if (!next || next.type !== 'Operator') {
array.splice(i, 1);
if (!next || next.data.type !== 'Operator') {
this.remove(item);
}
}
}
}
});
// remove redundant spaces
for (var i = 0; i < array.length; i++) {
if (array[i].type === 'Space') {
if (!i || i === array.length - 1 || array[i + 1].type === 'Space') {
array.splice(i, 1);
i--;
list.each(function(node, item) {
if (node.type === 'Space') {
if (!item.prev || !item.next || item.next.data.type === 'Space') {
this.remove(item);
}
}
}
});
if (!array.length) {
array.push({
if (list.isEmpty()) {
list.insert(list.createItem({
type: 'Identifier',
name: 'normal'
});
}));
}
value.sequence = array;
};

@@ -5,4 +5,4 @@ var compressFont = require('./property/font.js');

module.exports = function compressValue(node, parent) {
var property = parent.property.name;
module.exports = function compressValue(node) {
var property = this.declaration.property.name;

@@ -9,0 +9,0 @@ if (/background$/.test(property)) {

@@ -0,1 +1,2 @@

var List = require('../utils/list.js');
var convertToInternal = require('./ast/gonzalesToInternal.js');

@@ -80,8 +81,2 @@ var convertToGonzales = require('./ast/internalToGonzales.js');

function compressBlock(ast, restructuring, num, debug) {
function walk(name, fn) {
internalWalkAll(internalAst, fn);
debug(name, internalAst);
}
debug('Compress block #' + num, null, true);

@@ -93,5 +88,11 @@

internalAst.firstAtrulesAllowed = ast.firstAtrulesAllowed;
walk('clean', cleanFn);
walk('compress', compressFn);
// remove useless
internalWalkAll(internalAst, cleanFn);
debug('clean', internalAst);
// compress nodes
internalWalkAll(internalAst, compressFn);
debug('compress', internalAst);
// structure optimisations

@@ -110,4 +111,7 @@ if (restructuring) {

var debug = createLogger(options.debug);
var restructuring = options.restructuring || options.restructuring === undefined;
var result = [];
var restructuring =
'restructure' in options ? options.restructure :
'restructuring' in options ? options.restructuring :
true;
var result = new List();
var block = { offset: 2 };

@@ -128,27 +132,27 @@ var firstAtrulesAllowed = true;

// add \n before comment if there is another content in result
if (result.length) {
result.push({
if (!result.isEmpty()) {
result.insert(List.createItem({
type: 'Raw',
value: '\n'
});
}));
}
result.push({
result.insert(List.createItem({
type: 'Comment',
value: block.comment[2]
});
}));
// add \n after comment if block is not empty
if (block.stylesheet.rules.length) {
result.push({
if (!block.stylesheet.rules.isEmpty()) {
result.insert(List.createItem({
type: 'Raw',
value: '\n'
});
}));
}
}
result.push.apply(result, block.stylesheet.rules);
result.appendList(block.stylesheet.rules);
if (firstAtrulesAllowed && result.length) {
var lastRule = result[result.length - 1];
if (firstAtrulesAllowed && !result.isEmpty()) {
var lastRule = result.last();

@@ -155,0 +159,0 @@ if (lastRule.type !== 'Atrule' ||

@@ -1,27 +0,34 @@

var internalWalkAll = require('../ast/walk.js').all;
var internalWalkRules = require('../ast/walk.js').rules;
var internalWalkRulesRight = require('../ast/walk.js').rulesRight;
var translate = require('../ast/translate.js');
var prepare = require('./prepare/index.js');
var markShorthands = require('./markShorthands.js');
var processShorthands = require('./processShorthands.js');
var disjoin = require('./disjoinRuleset.js');
var rejoinRuleset = require('./rejoinRuleset.js');
var initialRejoinRuleset = require('./initialRejoinRuleset.js');
var rejoinAtrule = require('./rejoinAtrule.js');
var restructBlock = require('./restructBlock.js');
var restructRuleset = require('./restructRuleset.js');
var initialMergeRuleset = require('./1-initialMergeRuleset.js');
var mergeAtrule = require('./2-mergeAtrule.js');
var disjoinRuleset = require('./3-disjoinRuleset.js');
var restructShorthand = require('./4-restructShorthand.js');
var restructBlock = require('./6-restructBlock.js');
var mergeRuleset = require('./7-mergeRuleset.js');
var restructRuleset = require('./8-restructRuleset.js');
module.exports = function(ast, debug) {
function walk(name, fn) {
internalWalkAll(ast, fn);
function Index() {
this.seed = 0;
this.map = Object.create(null);
}
debug(name, ast);
Index.prototype.resolve = function(str) {
var index = this.map[str];
if (!index) {
index = ++this.seed;
this.map[str] = index;
}
return index;
};
module.exports = function(ast, debug) {
function walkRulesets(name, fn) {
// console.log(require('../ast/translate.js')(ast));
internalWalkRules(ast, function(node) {
internalWalkRules(ast, function(node, item, list) {
if (node.type === 'Ruleset') {
return fn.apply(this, arguments);
// console.log(require('../ast/translate.js')(ast));
fn.call(this, node, item, list);
}

@@ -34,5 +41,5 @@ });

function walkRulesetsRight(name, fn) {
internalWalkRulesRight(ast, function(node) {
internalWalkRulesRight(ast, function(node, item, list) {
if (node.type === 'Ruleset') {
return fn.apply(this, arguments);
fn.call(this, node, item, list);
}

@@ -45,5 +52,5 @@ });

function walkAtrules(name, fn) {
internalWalkRulesRight(ast, function(node) {
internalWalkRulesRight(ast, function(node, item, list) {
if (node.type === 'Atrule') {
return fn.apply(this, arguments);
fn.call(this, node, item, list);
}

@@ -55,36 +62,38 @@ });

// prepare ast for restructing
walk('prepare', prepare);
var declarationMarker = (function() {
var names = new Index();
var values = new Index();
// todo: remove initial rejoin
walkRulesetsRight('initialRejoinRuleset', initialRejoinRuleset);
walkAtrules('rejoinAtrule', rejoinAtrule);
walkRulesetsRight('disjoin', disjoin);
return function markDeclaration(node) {
// node.id = translate(node);
var shortDeclarations = [];
walkRulesetsRight('buildMaps', function(ruleset, stylesheet) {
var map = stylesheet.info.selectorsMap;
if (!map) {
map = stylesheet.info.selectorsMap = {};
stylesheet.info.shortDeclarations = shortDeclarations;
stylesheet.info.lastShortSelector = null;
}
var property = node.property.name;
var value = translate(node.value);
var selector = ruleset.selector.selectors[0].info.s;
if (selector in map === false) {
map[selector] = {
props: {},
shorts: {}
};
}
node.id = names.resolve(property) + (values.resolve(value) << 12);
node.length = property.length + 1 + value.length;
return node;
};
})();
// prepare ast for restructing
internalWalkRules(ast, function(node) {
prepare(node, declarationMarker);
});
debug('prepare', ast);
walkRulesetsRight('markShorthands', markShorthands);
processShorthands(shortDeclarations);
debug('processShorthand', ast);
// todo: remove initial merge
walkRulesetsRight('initialMergeRuleset', initialMergeRuleset);
walkAtrules('mergeAtrule', mergeAtrule);
walkRulesetsRight('disjoinRuleset', disjoinRuleset);
walkRulesetsRight('restructBlock', restructBlock);
// console.log(require('../ast/translate.js')(ast));
walkRulesets('rejoinRuleset', rejoinRuleset);
restructShorthand(ast, declarationMarker);
debug('restructShorthand', ast);
restructBlock(ast);
debug('restructBlock', ast);
walkRulesets('mergeRuleset', mergeRuleset);
walkRulesetsRight('restructRuleset', restructRuleset);
};
var translate = require('../../ast/translate.js');
var specificity = require('./specificity.js');
var freeze = require('./freeze.js');
var processSelector = require('./processSelector.js');
function translateNode(node) {
node.info.s = translate(node);
}
module.exports = function walk(node, markDeclaration) {
switch (node.type) {
case 'Ruleset':
node.block.declarations.each(markDeclaration);
processSelector(node);
break;
var handlers = {
Ruleset: freeze,
case 'Atrule':
if (node.expression) {
node.expression.id = translate(node.expression);
}
Atrule: function(node, root) {
var name = node.name;
// compare keyframe selectors by its values
// NOTE: still no clarification about problems with keyframes selector grouping (issue #197)
if (/^(-[a-z\d]+-)?keyframes$/.test(name)) {
node.block.info.isKeyframes = true;
node.block.rules.forEach(function(ruleset) {
ruleset.selector.selectors.forEach(function(simpleselector) {
simpleselector.info.compareMarker = simpleselector.info.s;
// compare keyframe selectors by its values
// NOTE: still no clarification about problems with keyframes selector grouping (issue #197)
if (/^(-[a-z\d]+-)?keyframes$/.test(node.name)) {
node.block.avoidRulesMerge = true; /* probably we don't need to prevent those merges for @keyframes
TODO: need to be checked */
node.block.rules.each(function(ruleset) {
ruleset.selector.selectors.each(function(simpleselector) {
simpleselector.compareMarker = simpleselector.id;
});
});
});
}
},
SimpleSelector: function(node) {
var info = node.info;
var array = node.sequence;
var tagName = '*';
var last;
for (var i = array.length - 1; i >= 0; i--) {
if (array[i].type === 'Combinator') {
break;
}
last = array[i];
}
if (last.type === 'Identifier') {
tagName = last.name;
}
info.compareMarker = specificity(node) + ',' + tagName;
info.s = translate(node);
},
AtruleExpression: translateNode,
Declaration: translateNode,
Property: translateNode,
Value: translateNode
};
module.exports = function(node, parent) {
if (handlers[node.type]) {
return handlers[node.type].call(this, node, parent);
break;
}
};

@@ -1,17 +0,15 @@

var A = 2;
var B = 1;
var C = 0;
module.exports = function specificity(simpleSelector) {
var specificity = [0, 0, 0];
var A = 0;
var B = 0;
var C = 0;
simpleSelector.sequence.forEach(function walk(item) {
switch (item.type) {
simpleSelector.sequence.each(function walk(data) {
switch (data.type) {
case 'SimpleSelector':
case 'Negation':
item.sequence.forEach(walk);
data.sequence.each(walk);
break;
case 'Id':
specificity[C]++;
A++;
break;

@@ -22,8 +20,8 @@

case 'FunctionalPseudo':
specificity[B]++;
B++;
break;
case 'Identifier':
if (item.name !== '*') {
specificity[A]++;
if (data.name !== '*') {
C++;
}

@@ -33,7 +31,7 @@ break;

case 'PseudoElement':
specificity[A]++;
C++;
break;
case 'PseudoClass':
var name = item.name.toLowerCase();
var name = data.name.toLowerCase();
if (name === 'before' ||

@@ -43,5 +41,5 @@ name === 'after' ||

name === 'first-letter') {
specificity[A]++;
C++;
} else {
specificity[B]++;
B++;
}

@@ -52,3 +50,3 @@ break;

return specificity;
return [A, B, C];
};

@@ -1,48 +0,26 @@

function copyObject(obj) {
var result = {};
function isEqualLists(a, b) {
var cursor1 = a.head;
var cursor2 = b.head;
for (var key in obj) {
result[key] = obj[key];
while (cursor1 && cursor2 && cursor1.data.id === cursor2.data.id) {
cursor1 = cursor1.next;
cursor2 = cursor2.next;
}
return result;
return cursor1 === null && cursor2 === null;
}
function equalHash(h0, h1) {
for (var key in h0) {
if (key in h1 === false) {
return false;
}
}
function isEqualDeclarations(a, b) {
var cursor1 = a.head;
var cursor2 = b.head;
for (var key in h1) {
if (key in h0 === false) {
return false;
}
while (cursor1 && cursor2 && cursor1.data.id === cursor2.data.id) {
cursor1 = cursor1.next;
cursor2 = cursor2.next;
}
return true;
return cursor1 === null && cursor2 === null;
}
function getHash(tokens) {
var hash = {};
for (var i = 0; i < tokens.length; i++) {
hash[tokens[i].info.s] = true;
}
return hash;
}
function hashInHash(hash1, hash2) {
for (var key in hash1) {
if (key in hash2 === false) {
return false;
}
}
return true;
}
function compareRulesets(ruleset1, ruleset2) {
function compareDeclarations(declarations1, declarations2) {
var result = {

@@ -55,33 +33,33 @@ eq: [],

var items1 = ruleset1.block.declarations; // token
var items2 = ruleset2.block.declarations; // prev
var hash1 = getHash(items1);
var hash2 = getHash(items2);
var fingerprints = {};
var fingerprints = Object.create(null);
var declarations2hash = Object.create(null);
for (var i = 0; i < items1.length; i++) {
if (items1[i].info.fingerprint) {
fingerprints[items1[i].info.fingerprint] = true;
}
for (var cursor = declarations2.head; cursor; cursor = cursor.next) {
declarations2hash[cursor.data.id] = true;
}
for (var i = 0; i < items1.length; i++) {
var item = items1[i];
for (var cursor = declarations1.head; cursor; cursor = cursor.next) {
var data = cursor.data;
if (item.info.s in hash2) {
result.eq.push(item);
if (data.fingerprint) {
fingerprints[data.fingerprint] = true;
}
if (declarations2hash[data.id]) {
declarations2hash[data.id] = false;
result.eq.push(data);
} else {
result.ne1.push(item);
result.ne1.push(data);
}
}
for (var i = 0; i < items2.length; i++) {
var item = items2[i];
for (var cursor = declarations2.head; cursor; cursor = cursor.next) {
var data = cursor.data;
if (item.info.s in hash1 === false) {
// if ruleset1 has overriding declaration, this is not a difference
if (item.info.fingerprint in fingerprints === false) {
result.ne2.push(item);
if (declarations2hash[data.id]) {
// if declarations1 has overriding declaration, this is not a difference
if (!fingerprints[data.fingerprint]) {
result.ne2.push(data);
} else {
result.ne2overrided.push(item);
result.ne2overrided.push(data);
}

@@ -95,54 +73,31 @@ }

function addToSelector(dest, source) {
ignore:
for (var i = 0; i < source.length; i++) {
var simpleSelectorStr = source[i].info.s;
for (var j = dest.length; j > 0; j--) {
var prevSimpleSelectorStr = dest[j - 1].info.s;
if (prevSimpleSelectorStr === simpleSelectorStr) {
continue ignore;
source.each(function(sourceData) {
var newStr = sourceData.id;
var cursor = dest.head;
while (cursor) {
var nextStr = cursor.data.id;
if (nextStr === newStr) {
return;
}
if (prevSimpleSelectorStr < simpleSelectorStr) {
if (nextStr > newStr) {
break;
}
cursor = cursor.next;
}
dest.splice(j, 0, source[i]);
}
dest.insert(dest.createItem(sourceData), cursor);
});
return dest;
}
function isCompatibleSignatures(token1, token2) {
var info1 = token1.info;
var info2 = token2.info;
// same frozen ruleset
if (info1.freezeID === info2.freezeID) {
return true;
}
// same pseudo-classes in selectors
if (info1.pseudoID === info2.pseudoID) {
return true;
}
// different frozen rulesets
if (info1.freeze && info2.freeze) {
var signature1 = info1.pseudoSignature;
var signature2 = info2.pseudoSignature;
return signature1 === signature2;
}
// is it frozen at all?
return !info1.freeze && !info2.freeze;
}
module.exports = {
copyObject: copyObject,
equalHash: equalHash,
getHash: getHash,
hashInHash: hashInHash,
isCompatibleSignatures: isCompatibleSignatures,
compareRulesets: compareRulesets,
isEqualLists: isEqualLists,
isEqualDeclarations: isEqualDeclarations,
compareDeclarations: compareDeclarations,
addToSelector: addToSelector
};
var parse = require('./parser');
var compress = require('./compressor');
var traslateInternal = require('./compressor/ast/translate');
var traslateInternalWithSourceMap = require('./compressor/ast/translateWithSourceMap');
var walk = require('./utils/walker');

@@ -14,3 +15,3 @@ var translate = require('./utils/translate');

var compressed = compress(ast, {
restructuring: !noStructureOptimizations,
restructure: !noStructureOptimizations,
outputAst: 'internal'

@@ -22,26 +23,51 @@ });

var minify = function(src, options) {
var minifyOptions = {
outputAst: 'internal'
};
if (options) {
for (var key in options) {
minifyOptions[key] = options[key];
}
function debugOutput(name, options, startTime, data) {
if (options.debug) {
console.error('## ' + name + ' done in %d ms\n', Date.now() - startTime);
}
var t = Date.now();
var ast = parse(src, 'stylesheet', true);
if (minifyOptions.debug) {
console.error('## parsing done in %d ms\n', Date.now() - t);
return data;
}
function compressOptions(options) {
var result = {};
for (var key in options) {
result[key] = options[key];
}
var t = Date.now();
var compressed = compress(ast, minifyOptions);
if (minifyOptions.debug) {
console.error('## compressing done in %d ms\n', Date.now() - t);
result.outputAst = 'internal';
return result;
}
var minify = function(source, options) {
options = options || {};
// parse
var ast = debugOutput('parsing', options, new Date(),
parse(source, 'stylesheet', {
filename: options.filename || '<unknown>',
needPositions: Boolean(options.sourceMap),
needInfo: true
})
);
// compress
var compressedAst = debugOutput('compress', options, new Date(),
compress(ast, compressOptions(options))
);
// translate
if (options.sourceMap) {
var result = debugOutput('translateWithSourceMap', options, new Date(),
traslateInternalWithSourceMap(compressedAst)
);
} else {
var result = debugOutput('translate', options, new Date(),
traslateInternal(compressedAst)
);
}
return traslateInternal(compressed);
return result;
};

@@ -48,0 +74,0 @@

@@ -1,11 +0,8 @@

module.exports = {
StringSQ: 'StringSQ',
StringDQ: 'StringDQ',
CommentML: 'CommentML',
CommentSL: 'CommentSL',
exports.TokenType = {
String: 'String',
Comment: 'Comment',
Unknown: 'Unknown',
Newline: 'Newline',
Space: 'Space',
Tab: 'Tab',
ExclamationMark: 'ExclamationMark', // !

@@ -42,5 +39,59 @@ QuotationMark: 'QuotationMark', // "

Tilde: 'Tilde', // ~
Identifier: 'Identifier',
DecimalNumber: 'DecimalNumber'
};
// var i = 1;
// for (var key in exports.TokenType) {
// exports.TokenType[key] = i++;
// }
exports.NodeType = {
AtkeywordType: 'atkeyword',
AtrulebType: 'atruleb',
AtrulerqType: 'atrulerq',
AtrulersType: 'atrulers',
AtrulerType: 'atruler',
AtrulesType: 'atrules',
AttribType: 'attrib',
AttrselectorType: 'attrselector',
BlockType: 'block',
BracesType: 'braces',
ClassType: 'clazz',
CombinatorType: 'combinator',
CommentType: 'comment',
DeclarationType: 'declaration',
DecldelimType: 'decldelim',
DelimType: 'delim',
DimensionType: 'dimension',
FilterType: 'filter',
FiltervType: 'filterv',
FunctionBodyType: 'functionBody',
FunctionExpressionType: 'functionExpression',
FunktionType: 'funktion',
IdentType: 'ident',
ImportantType: 'important',
NamespaceType: 'namespace',
NthselectorType: 'nthselector',
NthType: 'nth',
NumberType: 'number',
OperatorType: 'operator',
PercentageType: 'percentage',
ProgidType: 'progid',
PropertyType: 'property',
PseudocType: 'pseudoc',
PseudoeType: 'pseudoe',
RawType: 'raw',
RulesetType: 'ruleset',
SelectorType: 'selector',
ShashType: 'shash',
SimpleselectorType: 'simpleselector',
StringType: 'string',
StylesheetType: 'stylesheet',
SType: 's',
UnaryType: 'unary',
UnknownType: 'unknown',
UriType: 'uri',
ValueType: 'value',
VhashType: 'vhash'
};

@@ -1,2116 +0,1504 @@

var TokenType = require('./const.js');
'use strict';
var TokenType = require('./const.js').TokenType;
var NodeType = require('./const.js').NodeType;
var tokenize = require('./tokenize.js');
var cleanInfo = require('../utils/cleanInfo.js');
var needPositions;
var filename;
var tokens;
var needInfo;
var pos;
var failLN;
var currentBlockLN;
var NodeType = {
IdentType: 'ident',
AtkeywordType: 'atkeyword',
StringType: 'string',
ShashType: 'shash',
VhashType: 'vhash',
NumberType: 'number',
PercentageType: 'percentage',
DimensionType: 'dimension',
DecldelimType: 'decldelim',
SType: 's',
AttrselectorType: 'attrselector',
AttribType: 'attrib',
NthType: 'nth',
NthselectorType: 'nthselector',
NamespaceType: 'namespace',
ClazzType: 'clazz',
PseudoeType: 'pseudoe',
PseudocType: 'pseudoc',
DelimType: 'delim',
StylesheetType: 'stylesheet',
AtrulebType: 'atruleb',
AtrulesType: 'atrules',
AtrulerqType: 'atrulerq',
AtrulersType: 'atrulers',
AtrulerType: 'atruler',
BlockType: 'block',
RulesetType: 'ruleset',
CombinatorType: 'combinator',
SimpleselectorType: 'simpleselector',
SelectorType: 'selector',
DeclarationType: 'declaration',
PropertyType: 'property',
ImportantType: 'important',
UnaryType: 'unary',
OperatorType: 'operator',
BracesType: 'braces',
ValueType: 'value',
ProgidType: 'progid',
FiltervType: 'filterv',
FilterType: 'filter',
CommentType: 'comment',
UriType: 'uri',
RawType: 'raw',
FunctionBodyType: 'functionBody',
FunktionType: 'funktion',
FunctionExpressionType: 'functionExpression',
UnknownType: 'unknown'
var rules = {
'atkeyword': getAtkeyword,
'atruleb': getAtrule,
'atruler': getAtrule,
'atrules': getAtrule,
'attrib': getAttrib,
'attrselector': getAttrselector,
'block': getBlock,
'braces': getBraces,
'clazz': getClass,
'combinator': getCombinator,
'comment': getComment,
'declaration': getDeclaration,
'dimension': getDimension,
'filter': getDeclaration,
'functionExpression': getFunctionExpression,
'funktion': getFunction,
'ident': getIdentifier,
'important': getImportant,
'nth': getNth,
'nthselector': getNthSelector,
'number': getNumber,
'operator': getOperator,
'percentage': getPercentage,
'progid': getProgid,
'property': getProperty,
'pseudoc': getPseudoc,
'pseudoe': getPseudoe,
'ruleset': getRuleset,
'selector': getSelector,
'shash': getShash,
'simpleselector': getSimpleSelector,
'string': getString,
'stylesheet': getStylesheet,
'unary': getUnary,
'unknown': getUnknown,
'uri': getUri,
'value': getValue,
'vhash': getVhash
};
var CSSPRules = {
'ident': function() { if (checkIdent(pos)) return getIdent() },
'atkeyword': function() { if (checkAtkeyword(pos)) return getAtkeyword() },
'string': function() { if (checkString(pos)) return getString() },
'shash': function() { if (checkShash(pos)) return getShash() },
'vhash': function() { if (checkVhash(pos)) return getVhash() },
'number': function() { if (checkNumber(pos)) return getNumber() },
'percentage': function() { if (checkPercentage(pos)) return getPercentage() },
'dimension': function() { if (checkDimension(pos)) return getDimension() },
'decldelim': function() { if (checkDecldelim(pos)) return getDecldelim() },
's': function() { if (checkS(pos)) return getS() },
'attrselector': function() { if (checkAttrselector(pos)) return getAttrselector() },
'attrib': function() { if (checkAttrib(pos)) return getAttrib() },
'nth': function() { if (checkNth(pos)) return getNth() },
'nthselector': function() { if (checkNthselector(pos)) return getNthselector() },
'namespace': function() { if (checkNamespace(pos)) return getNamespace() },
'clazz': function() { if (checkClazz(pos)) return getClazz() },
'pseudoe': function() { if (checkPseudoe(pos)) return getPseudoe() },
'pseudoc': function() { if (checkPseudoc(pos)) return getPseudoc() },
'delim': function() { if (checkDelim(pos)) return getDelim() },
'stylesheet': function() { if (checkStylesheet(pos)) return getStylesheet() },
'atruleb': function() { if (checkAtruleb(pos)) return getAtruleb() },
'atrules': function() { if (checkAtrules(pos)) return getAtrules() },
'atrulerq': function() { if (checkAtrulerq(pos)) return getAtrulerq() },
'atrulers': function() { if (checkAtrulers(pos)) return getAtrulers() },
'atruler': function() { if (checkAtruler(pos)) return getAtruler() },
'block': function() { if (checkBlock(pos)) return getBlock() },
'ruleset': function() { if (checkRuleset(pos)) return getRuleset() },
'combinator': function() { if (checkCombinator(pos)) return getCombinator() },
'simpleselector': function() { if (checkSimpleselector(pos)) return getSimpleSelector() },
'selector': function() { if (checkSelector(pos)) return getSelector() },
'declaration': function() { if (checkDeclaration(pos)) return getDeclaration() },
'property': function() { if (checkProperty(pos)) return getProperty() },
'important': function() { if (checkImportant(pos)) return getImportant() },
'unary': function() { if (checkUnary(pos)) return getUnary() },
'operator': function() { if (checkOperator(pos)) return getOperator() },
'braces': function() { if (checkBraces(pos)) return getBraces() },
'value': function() { if (checkValue(pos)) return getValue() },
'progid': function() { if (checkProgid(pos)) return getProgid() },
'filterv': function() { if (checkFilterv(pos)) return getFilterv() },
'filter': function() { if (checkFilter(pos)) return getFilter() },
'comment': function() { if (checkComment(pos)) return getComment() },
'uri': function() { if (checkUri(pos)) return getUri() },
'funktion': function() { if (checkFunktion(pos)) return getFunktion() },
'functionExpression': function() { if (checkFunctionExpression(pos)) return getFunctionExpression() },
'unknown': function() { if (checkUnknown(pos)) return getUnknown() }
};
function parseError(message) {
var error = new Error(message);
var line = 1;
var column = 1;
var lines;
function fail(token) {
if (token && token.line > failLN) {
failLN = token.line;
if (tokens.length) {
if (pos < tokens.length) {
line = tokens[pos].line;
column = tokens[pos].column;
} else {
pos = tokens.length - 1;
lines = tokens[pos].value.trimRight().split(/\n|\r\n?|\f/);
line = tokens[pos].line + lines.length - 1;
column = lines.length > 1
? lines[lines.length - 1].length + 1
: tokens[pos].column + lines[lines.length - 1].length;
}
}
}
function throwError() {
throw new Error('Please check the validity of the CSS block starting from the line #' + currentBlockLN);
error.parseError = {
line: line,
column: column
};
throw error;
}
function getInfo(idx) {
var token = tokens[idx];
function eat(tokenType) {
if (pos < tokens.length && tokens[pos].type === tokenType) {
pos++;
return true;
}
return {
offset: token.offset,
line: token.line,
column: token.column
};
parseError(tokenType + ' is expected');
}
function createToken(type) {
var result;
function expectIdentifier(name, eat) {
if (pos < tokens.length) {
var token = tokens[pos];
if (token.type === TokenType.Identifier &&
token.value.toLowerCase() === name) {
if (eat) {
pos++;
}
if (needInfo) {
result = [getInfo(pos), type];
} else {
result = [type];
return true;
}
}
return result;
parseError('Identifier `' + name + '` is expected');
}
//any = braces | string | percentage | dimension | number | uri | functionExpression | funktion | ident | unary
function checkAny(_i) {
return checkBraces(_i) ||
checkString(_i) ||
checkPercentage(_i) ||
checkDimension(_i) ||
checkNumber(_i) ||
checkUri(_i) ||
checkFunctionExpression(_i) ||
checkFunktion(_i) ||
checkIdent(_i) ||
checkUnary(_i);
}
function expectAny(what) {
if (pos < tokens.length) {
for (var i = 1, type = tokens[pos].type; i < arguments.length; i++) {
if (type === arguments[i]) {
return true;
}
}
}
function getAny() {
if (checkBraces(pos)) return getBraces();
else if (checkString(pos)) return getString();
else if (checkPercentage(pos)) return getPercentage();
else if (checkDimension(pos)) return getDimension();
else if (checkNumber(pos)) return getNumber();
else if (checkUri(pos)) return getUri();
else if (checkFunctionExpression(pos)) return getFunctionExpression();
else if (checkFunktion(pos)) return getFunktion();
else if (checkIdent(pos)) return getIdent();
else if (checkUnary(pos)) return getUnary();
parseError(what + ' is expected');
}
//atkeyword = '@' ident:x -> [#atkeyword, x]
function checkAtkeyword(_i) {
var l;
function getInfo(idx) {
if (!needPositions) {
return null;
}
if (tokens[_i++].type !== TokenType.CommercialAt) return fail(tokens[_i - 1]);
var token = tokens[idx];
if (l = checkIdent(_i)) return l + 1;
return fail(tokens[_i]);
return {
source: filename,
offset: token.offset,
line: token.line,
column: token.column
};
}
function getAtkeyword() {
var startPos = pos;
function getStylesheet(nested) {
var stylesheet = [getInfo(pos), NodeType.StylesheetType];
pos++;
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.Space:
stylesheet.push(getS());
break;
return needInfo?
[getInfo(startPos), NodeType.AtkeywordType, getIdent()]:
[NodeType.AtkeywordType, getIdent()];
}
case TokenType.Comment:
stylesheet.push(getComment());
break;
//attrib = '[' sc*:s0 ident:x sc*:s1 attrselector:a sc*:s2 (ident | string):y sc*:s3 ']' -> this.concat([#attrib], s0, [x], s1, [a], s2, [y], s3)
// | '[' sc*:s0 ident:x sc*:s1 ']' -> this.concat([#attrib], s0, [x], s1),
function checkAttrib(_i) {
if (tokens[_i].type !== TokenType.LeftSquareBracket) return fail(tokens[_i]);
case TokenType.Unknown:
stylesheet.push(getUnknown());
break;
if (!tokens[_i].right) return fail(tokens[_i]);
case TokenType.CommercialAt:
stylesheet.push(getAtrule());
break;
return tokens[_i].right - _i + 1;
}
case TokenType.RightCurlyBracket:
if (!nested) {
parseError('Unexpected right curly brace');
}
function checkAttrib1(_i) {
var start = _i;
break scan;
_i++;
var l = checkSC(_i); // s0
if (l) _i += l;
if (l = checkIdent(_i, true)) _i += l; // x
else return fail(tokens[_i]);
if (tokens[_i].type === TokenType.VerticalLine &&
tokens[_i + 1].type !== TokenType.EqualsSign) {
_i++;
if (l = checkIdent(_i, true)) _i += l; // x
else return fail(tokens[_i]);
default:
stylesheet.push(getRuleset());
}
}
if (l = checkSC(_i)) _i += l; // s1
if (l = checkAttrselector(_i)) _i += l; // a
else return fail(tokens[_i]);
if (l = checkSC(_i)) _i += l; // s2
if ((l = checkIdent(_i)) || (l = checkString(_i))) _i += l; // y
else return fail(tokens[_i]);
if (l = checkSC(_i)) _i += l; // s3
if (tokens[_i].type === TokenType.RightSquareBracket) return _i - start;
return fail(tokens[_i]);
return stylesheet;
}
function getAttrib1() {
var startPos = pos;
function isBlockAtrule(i) {
for (i++; i < tokens.length; i++) {
var type = tokens[i].type;
pos++;
if (type === TokenType.RightCurlyBracket) {
return true;
}
var a = (needInfo? [getInfo(startPos), NodeType.AttribType] : [NodeType.AttribType]);
a = a.concat(
getSC(),
[getIdent()]
);
if (tokens[pos].type === TokenType.VerticalLine &&
tokens[pos + 1].type !== TokenType.EqualsSign) {
a.push(
getNamespace(),
getIdent()
);
if (type === TokenType.LeftCurlyBracket ||
type === TokenType.CommercialAt) {
return false;
}
}
a = a.concat(
getSC(),
[getAttrselector()],
getSC(),
[checkString(pos) ? getString() : getIdent()],
getSC()
);
pos++;
return a;
return true;
}
function checkAttrib2(_i) {
var start = _i;
function getAtkeyword() {
eat(TokenType.CommercialAt);
_i++;
return [getInfo(pos - 1), NodeType.AtkeywordType, getIdentifier()];
}
var l = checkSC(_i);
function getAtrule() {
var node = [getInfo(pos), NodeType.AtrulesType, getAtkeyword(pos)];
if (l) _i += l;
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.Semicolon:
pos++;
break scan;
if (l = checkIdent(_i, true)) _i += l;
case TokenType.LeftCurlyBracket:
if (isBlockAtrule(pos)) {
node[1] = NodeType.AtrulebType;
node.push(getBlock());
} else {
node[1] = NodeType.AtrulerType;
node.push([
{},
NodeType.AtrulerqType
].concat(node.splice(3)));
if (tokens[_i].type === TokenType.VerticalLine &&
tokens[_i + 1].type !== TokenType.EqualsSign) {
_i++;
if (l = checkIdent(_i, true)) _i += l; // x
else return fail(tokens[_i]);
}
pos++; // {
if (l = checkSC(_i)) _i += l;
var stylesheet = getStylesheet(true);
stylesheet[1] = NodeType.AtrulersType;
node.push(stylesheet);
if (tokens[_i].type === TokenType.RightSquareBracket) return _i - start;
pos++; // }
}
break scan;
return fail(tokens[_i]);
}
case TokenType.Space:
node.push(getS());
break;
function getAttrib2() {
var startPos = pos;
case TokenType.Comment:
node.push(getComment());
break;
pos++;
case TokenType.Comma:
node.push(getOperator());
break;
var a = (needInfo? [getInfo(startPos), NodeType.AttribType] : [NodeType.AttribType])
.concat(
getSC(),
[getIdent()]
);
case TokenType.Colon:
node.push(getPseudo());
break;
if (tokens[pos].type === TokenType.VerticalLine &&
tokens[pos + 1].type !== TokenType.EqualsSign) {
a.push(
getNamespace(),
getIdent()
);
}
case TokenType.LeftParenthesis:
node.push(getBraces());
break;
a = a.concat(
getSC()
);
pos++;
return a;
}
function getAttrib() {
if (checkAttrib1(pos)) return getAttrib1();
if (checkAttrib2(pos)) return getAttrib2();
}
//attrselector = (seq('=') | seq('~=') | seq('^=') | seq('$=') | seq('*=') | seq('|=')):x -> [#attrselector, x]
function checkAttrselector(_i) {
if (tokens[_i].type === TokenType.EqualsSign) return 1;
if (tokens[_i].type === TokenType.VerticalLine && (!tokens[_i + 1] || tokens[_i + 1].type !== TokenType.EqualsSign)) return 1;
if (!tokens[_i + 1] || tokens[_i + 1].type !== TokenType.EqualsSign) return fail(tokens[_i]);
switch(tokens[_i].type) {
case TokenType.Tilde:
case TokenType.CircumflexAccent:
case TokenType.DollarSign:
case TokenType.Asterisk:
case TokenType.VerticalLine:
return 2;
default:
node.push(getAny());
}
}
return fail(tokens[_i]);
return node;
}
function getAttrselector() {
var startPos = pos,
s = tokens[pos++].value;
if (tokens[pos] && tokens[pos].type === TokenType.EqualsSign) s += tokens[pos++].value;
return needInfo?
[getInfo(startPos), NodeType.AttrselectorType, s] :
[NodeType.AttrselectorType, s];
function getRuleset() {
return [
getInfo(pos),
NodeType.RulesetType,
getSelector(),
getBlock()
];
}
//atrule = atruler | atruleb | atrules
function checkAtrule(_i) {
var start = _i,
l;
function getSelector() {
var selector = [getInfo(pos), NodeType.SelectorType];
if (tokens[start].atrule_l !== undefined) return tokens[start].atrule_l;
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.LeftCurlyBracket:
break scan;
if (l = checkAtruler(_i)) tokens[_i].atrule_type = 1;
else if (l = checkAtruleb(_i)) tokens[_i].atrule_type = 2;
else if (l = checkAtrules(_i)) tokens[_i].atrule_type = 3;
else return fail(tokens[start]);
case TokenType.Comma:
selector.push([
getInfo(pos++),
NodeType.DelimType
]);
break;
tokens[start].atrule_l = l;
default:
selector.push(getSimpleSelector());
}
}
return l;
return selector;
}
function getAtrule() {
switch (tokens[pos].atrule_type) {
case 1: return getAtruler();
case 2: return getAtruleb();
case 3: return getAtrules();
}
}
function getSimpleSelector(nested) {
var node = [getInfo(pos), NodeType.SimpleselectorType];
//atruleb = atkeyword:ak tset*:ap block:b -> this.concat([#atruleb, ak], ap, [b])
function checkAtruleb(_i) {
var start = _i,
l;
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.Comma:
case TokenType.LeftCurlyBracket:
break scan;
if (l = checkAtkeyword(_i)) _i += l;
else return fail(tokens[_i]);
case TokenType.RightParenthesis:
if (!nested) {
parseError('Unexpected input');
}
if (l = checkTsets(_i)) _i += l;
break scan;
if (l = checkBlock(_i)) _i += l;
else return fail(tokens[_i]);
case TokenType.Space:
node.push(getS());
break;
return _i - start;
}
case TokenType.Comment:
node.push(getComment());
break;
function getAtruleb() {
return (needInfo?
[getInfo(pos), NodeType.AtrulebType, getAtkeyword()] :
[NodeType.AtrulebType, getAtkeyword()])
.concat(getTsets())
.concat([getBlock()]);
}
case TokenType.PlusSign:
case TokenType.GreaterThanSign:
case TokenType.Tilde:
case TokenType.Solidus:
node.push(getCombinator());
break;
//atruler = atkeyword:ak atrulerq:x '{' atrulers:y '}' -> [#atruler, ak, x, y]
function checkAtruler(_i) {
var start = _i,
l;
case TokenType.FullStop:
node.push(getClass());
break;
if (l = checkAtkeyword(_i)) _i += l;
else return fail(tokens[_i]);
case TokenType.LeftSquareBracket:
node.push(getAttrib());
break;
if (l = checkAtrulerq(_i)) _i += l;
case TokenType.NumberSign:
node.push(getShash());
break;
if (_i < tokens.length && tokens[_i].type === TokenType.LeftCurlyBracket) _i++;
else return fail(tokens[_i]);
case TokenType.Colon:
node.push(getPseudo());
break;
if (l = checkAtrulers(_i)) _i += l;
case TokenType.HyphenMinus:
case TokenType.LowLine:
case TokenType.Identifier:
case TokenType.Asterisk:
case TokenType.DecimalNumber:
node.push(
tryGetPercentage() ||
getNamespacedIdentifier(false)
);
break;
if (_i < tokens.length && tokens[_i].type === TokenType.RightCurlyBracket) _i++;
else return fail(tokens[_i]);
default:
parseError('Unexpected input');
}
}
return _i - start;
return node;
}
function getAtruler() {
var atruler = needInfo?
[getInfo(pos), NodeType.AtrulerType, getAtkeyword(), getAtrulerq()] :
[NodeType.AtrulerType, getAtkeyword(), getAtrulerq()];
function getBlock() {
var node = [getInfo(pos), NodeType.BlockType];
pos++;
eat(TokenType.LeftCurlyBracket);
atruler.push(getAtrulers());
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.RightCurlyBracket:
break scan;
pos++;
case TokenType.Space:
node.push(getS());
break;
return atruler;
}
case TokenType.Comment:
node.push(getComment());
break;
//atrulerq = tset*:ap -> [#atrulerq].concat(ap)
function checkAtrulerq(_i) {
return checkTsets(_i);
}
case TokenType.Semicolon: // ;
node.push([
getInfo(pos++),
NodeType.DecldelimType
]);
break;
function getAtrulerq() {
return createToken(NodeType.AtrulerqType).concat(getTsets());
}
//atrulers = sc*:s0 ruleset*:r sc*:s1 -> this.concat([#atrulers], s0, r, s1)
function checkAtrulers(_i) {
var start = _i,
l;
if (l = checkSC(_i)) _i += l;
while ((l = checkRuleset(_i)) || (l = checkAtrule(_i)) || (l = checkSC(_i))) {
_i += l;
default:
node.push(getDeclaration());
}
}
tokens[_i].atrulers_end = 1;
eat(TokenType.RightCurlyBracket);
if (l = checkSC(_i)) _i += l;
return _i - start;
return node;
}
function getAtrulers() {
var atrulers = createToken(NodeType.AtrulersType).concat(getSC());
function getDeclaration() {
var startPos = pos;
var info = getInfo(pos);
var property = getProperty();
while (!tokens[pos].atrulers_end) {
if (checkSC(pos)) {
atrulers = atrulers.concat(getSC());
} else if (checkRuleset(pos)) {
atrulers.push(getRuleset());
} else {
atrulers.push(getAtrule());
eat(TokenType.Colon);
// check it's a filter
for (var j = startPos; j < pos; j++) {
if (tokens[j].value === 'filter') {
if (checkProgid(pos)) {
return [
info,
NodeType.FilterType,
property,
getFilterv()
];
}
break;
}
}
return atrulers.concat(getSC());
return [
info,
NodeType.DeclarationType,
property,
getValue()
];
}
//atrules = atkeyword:ak tset*:ap ';' -> this.concat([#atrules, ak], ap)
function checkAtrules(_i) {
var start = _i,
l;
function getProperty() {
var info = getInfo(pos);
var name = '';
if (l = checkAtkeyword(_i)) _i += l;
else return fail(tokens[_i]);
while (pos < tokens.length) {
var type = tokens[pos].type;
if (l = checkTsets(_i)) _i += l;
if (type !== TokenType.Solidus &&
type !== TokenType.Asterisk &&
type !== TokenType.DollarSign) {
break;
}
if (_i >= tokens.length) return _i - start;
name += tokens[pos++].value;
}
if (tokens[_i].type === TokenType.Semicolon) _i++;
else return fail(tokens[_i]);
return _i - start;
return readSC([
info,
NodeType.PropertyType,
[
info,
NodeType.IdentType,
name + readIdent()
]
]);
}
function getAtrules() {
var atrules = (needInfo? [getInfo(pos), NodeType.AtrulesType, getAtkeyword()] : [NodeType.AtrulesType, getAtkeyword()]).concat(getTsets());
function getValue() {
var node = [getInfo(pos), NodeType.ValueType];
pos++;
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.RightCurlyBracket:
case TokenType.Semicolon:
break scan;
return atrules;
}
case TokenType.Space:
node.push(getS());
break;
//block = '{' blockdecl*:x '}' -> this.concatContent([#block], x)
function checkBlock(_i) {
if (_i < tokens.length && tokens[_i].type === TokenType.LeftCurlyBracket) return tokens[_i].right - _i + 1;
case TokenType.Comment:
node.push(getComment());
break;
return fail(tokens[_i]);
}
case TokenType.NumberSign:
node.push(getVhash());
break;
function getBlock() {
var block = createToken(NodeType.BlockType);
var end = tokens[pos].right;
case TokenType.Solidus:
case TokenType.Comma:
node.push(getOperator());
break;
pos++;
case TokenType.LeftParenthesis:
case TokenType.LeftSquareBracket:
node.push(getBraces());
break;
while (pos < end) {
if (checkBlockdecl(pos)) block = block.concat(getBlockdecl());
else throwError();
}
case TokenType.ExclamationMark:
node.push(getImportant());
break;
pos = end + 1;
default:
// check for unicode range: U+0F00, U+0F00-0FFF, u+0F00??
if (tokens[pos].type === TokenType.Identifier) {
var prefix = tokens[pos].value;
if ((prefix === 'U' || prefix === 'u') &&
pos + 1 < tokens.length &&
tokens[pos + 1].type === TokenType.PlusSign) {
pos += 2;
return block;
}
node.push([
getInfo(pos),
NodeType.IdentType,
prefix + '+' + getUnicodeRange(true)
]);
break;
}
}
//blockdecl = sc*:s0 (filter | declaration):x decldelim:y sc*:s1 -> this.concat(s0, [x], [y], s1)
// | sc*:s0 (filter | declaration):x sc*:s1 -> this.concat(s0, [x], s1)
// | sc*:s0 decldelim:x sc*:s1 -> this.concat(s0, [x], s1)
// | sc+:s0 -> s0
function checkBlockdecl(_i) {
var l;
if (l = _checkBlockdecl0(_i)) tokens[_i].bd_type = 1;
else if (l = _checkBlockdecl1(_i)) tokens[_i].bd_type = 2;
else if (l = _checkBlockdecl2(_i)) tokens[_i].bd_type = 3;
else if (l = _checkBlockdecl3(_i)) tokens[_i].bd_type = 4;
else return fail(tokens[_i]);
return l;
}
function getBlockdecl() {
switch (tokens[pos].bd_type) {
case 1: return _getBlockdecl0();
case 2: return _getBlockdecl1();
case 3: return _getBlockdecl2();
case 4: return _getBlockdecl3();
node.push(getAny());
}
}
}
//sc*:s0 (filter | declaration):x decldelim:y sc*:s1 -> this.concat(s0, [x], [y], s1)
function _checkBlockdecl0(_i) {
var start = _i,
l;
if (l = checkSC(_i)) _i += l;
if (l = checkFilter(_i)) {
tokens[_i].bd_filter = 1;
_i += l;
} else if (l = checkDeclaration(_i)) {
tokens[_i].bd_decl = 1;
_i += l;
} else return fail(tokens[_i]);
if (_i < tokens.length && (l = checkDecldelim(_i))) _i += l;
else return fail(tokens[_i]);
if (l = checkSC(_i)) _i += l;
return _i - start;
return node;
}
function _getBlockdecl0() {
return getSC()
.concat([tokens[pos].bd_filter? getFilter() : getDeclaration()])
.concat([getDecldelim()])
.concat(getSC());
}
// any = string | percentage | dimension | number | uri | functionExpression | funktion | unary | operator | ident
function getAny() {
var startPos = pos;
//sc*:s0 (filter | declaration):x sc*:s1 -> this.concat(s0, [x], s1)
function _checkBlockdecl1(_i) {
var start = _i,
l;
switch (tokens[pos].type) {
case TokenType.String:
return getString();
if (l = checkSC(_i)) _i += l;
case TokenType.FullStop:
case TokenType.DecimalNumber:
case TokenType.HyphenMinus:
case TokenType.PlusSign:
var number = tryGetNumber();
if (l = checkFilter(_i)) {
tokens[_i].bd_filter = 1;
_i += l;
} else if (l = checkDeclaration(_i)) {
tokens[_i].bd_decl = 1;
_i += l;
} else return fail(tokens[_i]);
if (number !== null) {
if (pos < tokens.length) {
if (tokens[pos].type === TokenType.PercentSign) {
return getPercentage(startPos, number);
} else if (tokens[pos].type === TokenType.Identifier) {
return getDimension(startPos, number);
}
}
if (l = checkSC(_i)) _i += l;
return number;
}
return _i - start;
}
if (tokens[pos].type === TokenType.HyphenMinus &&
pos < tokens.length &&
(tokens[pos + 1].type === TokenType.Identifier || tokens[pos + 1].type === TokenType.HyphenMinus)) {
break;
}
function _getBlockdecl1() {
return getSC()
.concat([tokens[pos].bd_filter? getFilter() : getDeclaration()])
.concat(getSC());
}
if (tokens[pos].type === TokenType.HyphenMinus ||
tokens[pos].type === TokenType.PlusSign) {
return getUnary();
}
//sc*:s0 decldelim:x sc*:s1 -> this.concat(s0, [x], s1)
function _checkBlockdecl2(_i) {
var start = _i,
l;
parseError('Unexpected input');
break;
if (l = checkSC(_i)) _i += l;
case TokenType.HyphenMinus:
case TokenType.LowLine:
case TokenType.Identifier:
break;
if (l = checkDecldelim(_i)) _i += l;
else return fail(tokens[_i]);
default:
parseError('Unexpected input');
}
if (l = checkSC(_i)) _i += l;
var ident = getIdentifier();
return _i - start;
}
if (pos < tokens.length && tokens[pos].type === TokenType.LeftParenthesis) {
switch (ident[2]) {
case 'url':
return getUri(startPos, ident);
function _getBlockdecl2() {
return getSC()
.concat([getDecldelim()])
.concat(getSC());
}
case 'expression':
return getFunctionExpression(startPos, ident);
//sc+:s0 -> s0
function _checkBlockdecl3(_i) {
return checkSC(_i);
}
default:
return getFunction(startPos, ident);
}
}
function _getBlockdecl3() {
return getSC();
return ident;
}
//braces = '(' tset*:x ')' -> this.concat([#braces, '(', ')'], x)
// | '[' tset*:x ']' -> this.concat([#braces, '[', ']'], x)
function checkBraces(_i) {
if (_i >= tokens.length ||
(tokens[_i].type !== TokenType.LeftParenthesis &&
tokens[_i].type !== TokenType.LeftSquareBracket)
) return fail(tokens[_i]);
// '[' S* attrib_name ']'
// '[' S* attrib_name attrib_match [ IDENT | STRING ] S* attrib_flags? ']'
function getAttrib() {
var node = [getInfo(pos), NodeType.AttribType];
return tokens[_i].right - _i + 1;
}
eat(TokenType.LeftSquareBracket);
function getBraces() {
var startPos = pos,
left = pos,
right = tokens[pos].right;
readSC(node);
pos++;
node.push(getNamespacedIdentifier(true));
var tsets = getTsets();
readSC(node);
pos++;
if (pos < tokens.length && tokens[pos].type !== TokenType.RightSquareBracket) {
node.push(getAttrselector());
readSC(node);
return needInfo?
[getInfo(startPos), NodeType.BracesType, tokens[left].value, tokens[right].value].concat(tsets) :
[NodeType.BracesType, tokens[left].value, tokens[right].value].concat(tsets);
}
if (pos < tokens.length && tokens[pos].type === TokenType.String) {
node.push(getString());
} else {
node.push(getIdentifier());
}
// node: Clazz
function checkClazz(_i) {
var token = tokens[_i];
var l;
readSC(node);
if (token.clazz_l) return token.clazz_l;
if (token.type === TokenType.FullStop) {
// otherwise it's converts to dimension and some part of selector lost (issue 99)
if (tokens[_i + 1].type === 'DecimalNumber' &&
!/\D/.test(tokens[_i + 1].value)) {
_i++;
// attribute flags
if (pos < tokens.length && tokens[pos].type === TokenType.Identifier) {
node.push([
getInfo(pos),
'attribFlags',
tokens[pos++].value
]);
readSC(node);
}
if (l = checkIdent(_i + 1)) {
token.clazz_l = l + 1;
return l + 1;
}
}
return fail(token);
}
eat(TokenType.RightSquareBracket);
function getClazz() {
var startPos = pos;
var clazz_l = pos + tokens[pos].clazz_l;
pos++;
var ident = createToken(NodeType.IdentType).concat(joinValues(pos, clazz_l - 1));
pos = clazz_l;
return needInfo?
[getInfo(startPos), NodeType.ClazzType, ident] :
[NodeType.ClazzType, ident];
return node;
}
// node: Combinator
function checkCombinator(_i) {
if (tokens[_i].type === TokenType.PlusSign ||
tokens[_i].type === TokenType.GreaterThanSign ||
tokens[_i].type === TokenType.Tilde) {
return 1;
}
function getAttrselector() {
expectAny('Attribute selector (=, ~=, ^=, $=, *=, |=)',
TokenType.EqualsSign, // =
TokenType.Tilde, // ~=
TokenType.CircumflexAccent, // ^=
TokenType.DollarSign, // $=
TokenType.Asterisk, // *=
TokenType.VerticalLine // |=
);
if (tokens[_i + 0].type === TokenType.Solidus &&
tokens[_i + 1].type === TokenType.Identifier && tokens[_i + 1].value === 'deep' &&
tokens[_i + 2].type === TokenType.Solidus) {
return 3;
}
var startPos = pos;
var name;
return fail(tokens[_i]);
}
function getCombinator() {
var combinator = tokens[pos].value;
if (tokens[pos].type === TokenType.Solidus) {
combinator = '/deep/';
pos += 3;
if (tokens[pos].type === TokenType.EqualsSign) {
name = '=';
pos++;
} else {
pos += 1;
name = tokens[pos].value + '=';
pos++;
eat(TokenType.EqualsSign);
}
return needInfo?
[getInfo(pos), NodeType.CombinatorType, combinator] :
[NodeType.CombinatorType, combinator];
return [getInfo(startPos), NodeType.AttrselectorType, name];
}
// node: Comment
function checkComment(_i) {
if (tokens[_i].type === TokenType.CommentML) return 1;
function getBraces() {
expectAny('Parenthesis or square bracket',
TokenType.LeftParenthesis,
TokenType.LeftSquareBracket
);
return fail(tokens[_i]);
}
var close;
function getComment() {
var startPos = pos,
s = tokens[pos].value.substring(2),
l = s.length;
if (tokens[pos].type === TokenType.LeftParenthesis) {
close = TokenType.RightParenthesis;
} else {
close = TokenType.RightSquareBracket;
}
if (s.charAt(l - 2) === '*' && s.charAt(l - 1) === '/') s = s.substring(0, l - 2);
var node = [
getInfo(pos),
NodeType.BracesType,
tokens[pos].value,
null
];
// left brace
pos++;
return needInfo?
[getInfo(startPos), NodeType.CommentType, s] :
[NodeType.CommentType, s];
}
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case close:
node[3] = tokens[pos].value;
break scan;
// declaration = property:x ':' value:y -> [#declaration, x, y]
function checkDeclaration(_i) {
var start = _i,
l;
case TokenType.Space:
node.push(getS());
break;
if (l = checkProperty(_i)) _i += l;
else return fail(tokens[_i]);
case TokenType.Comment:
node.push(getComment());
break;
if (_i < tokens.length && tokens[_i].type === TokenType.Colon) _i++;
else return fail(tokens[_i]);
case TokenType.NumberSign: // ??
node.push(getVhash());
break;
if (l = checkValue(_i)) _i += l;
else return fail(tokens[_i]);
case TokenType.LeftParenthesis:
case TokenType.LeftSquareBracket:
node.push(getBraces());
break;
return _i - start;
}
case TokenType.Solidus:
case TokenType.Asterisk:
case TokenType.Comma:
case TokenType.Colon:
node.push(getOperator());
break;
function getDeclaration() {
var declaration = needInfo?
[getInfo(pos), NodeType.DeclarationType, getProperty()] :
[NodeType.DeclarationType, getProperty()];
default:
node.push(getAny());
}
}
pos++;
// right brace
eat(close);
declaration.push(getValue());
return declaration;
return node;
}
// node: Decldelim
function checkDecldelim(_i) {
if (_i < tokens.length && tokens[_i].type === TokenType.Semicolon) return 1;
return fail(tokens[_i]);
}
function getDecldelim() {
// '.' ident
function getClass() {
var startPos = pos;
pos++;
eat(TokenType.FullStop);
return needInfo?
[getInfo(startPos), NodeType.DecldelimType] :
[NodeType.DecldelimType];
return [
getInfo(startPos),
NodeType.ClassType,
getIdentifier()
];
}
// node: Delim
function checkDelim(_i) {
if (_i < tokens.length && tokens[_i].type === TokenType.Comma) return 1;
if (_i >= tokens.length) return fail(tokens[tokens.length - 1]);
return fail(tokens[_i]);
}
function getDelim() {
// '#' ident
// FIXME: shash node should has structure like other ident's (['shash', ['ident', ident]])
function getShash() {
var startPos = pos;
pos++;
eat(TokenType.NumberSign);
return needInfo?
[getInfo(startPos), NodeType.DelimType] :
[NodeType.DelimType];
return [
getInfo(startPos),
NodeType.ShashType,
readIdent()
];
}
// node: Dimension
function checkDimension(_i) {
var ln = checkNumber(_i),
li;
// + | > | ~ | /deep/
function getCombinator() {
var info = getInfo(pos);
var combinator;
if (!ln || (ln && _i + ln >= tokens.length)) return fail(tokens[_i]);
switch (tokens[pos].type) {
case TokenType.PlusSign:
case TokenType.GreaterThanSign:
case TokenType.Tilde:
combinator = tokens[pos].value;
pos++;
break;
if (li = checkNmName2(_i + ln)) return ln + li;
case TokenType.Solidus:
combinator = '/deep/';
pos++;
return fail(tokens[_i]);
}
expectIdentifier('deep', true);
function getDimension() {
var startPos = pos,
n = getNumber(),
dimension = needInfo ?
[getInfo(pos), NodeType.IdentType, getNmName2()] :
[NodeType.IdentType, getNmName2()];
eat(TokenType.Solidus);
break;
return needInfo?
[getInfo(startPos), NodeType.DimensionType, n, dimension] :
[NodeType.DimensionType, n, dimension];
}
default:
parseError('Combinator (+, >, ~, /deep/) is expected');
}
//filter = filterp:x ':' filterv:y -> [#filter, x, y]
function checkFilter(_i) {
var start = _i,
l;
if (l = checkFilterp(_i)) _i += l;
else return fail(tokens[_i]);
if (tokens[_i].type === TokenType.Colon) _i++;
else return fail(tokens[_i]);
if (l = checkFilterv(_i)) _i += l;
else return fail(tokens[_i]);
return _i - start;
return [info, NodeType.CombinatorType, combinator];
}
function getFilter() {
var filter = needInfo?
[getInfo(pos), NodeType.FilterType, getFilterp()] :
[NodeType.FilterType, getFilterp()];
// '/*' .* '*/'
function getComment() {
var value = tokens[pos].value;
var len = value.length;
pos++;
filter.push(getFilterv());
return filter;
}
//filterp = (seq('-filter') | seq('_filter') | seq('*filter') | seq('-ms-filter') | seq('filter')):t sc*:s0 -> this.concat([#property, [#ident, t]], s0)
function checkFilterp(_i) {
var start = _i,
l,
x;
if (_i < tokens.length) {
if (tokens[_i].value === 'filter') l = 1;
else {
x = joinValues2(_i, 2);
if (x === '-filter' || x === '_filter' || x === '*filter') l = 2;
else {
x = joinValues2(_i, 4);
if (x === '-ms-filter') l = 4;
else return fail(tokens[_i]);
}
}
tokens[start].filterp_l = l;
_i += l;
if (checkSC(_i)) _i += l;
return _i - start;
if (len > 4 && value.charAt(len - 2) === '*' && value.charAt(len - 1) === '/') {
len -= 2;
}
return fail(tokens[_i]);
return [getInfo(pos++), NodeType.CommentType, value.substring(2, len)];
}
function getFilterp() {
var startPos = pos,
x = joinValues2(pos, tokens[pos].filterp_l),
ident = needInfo? [getInfo(startPos), NodeType.IdentType, x] : [NodeType.IdentType, x];
pos += tokens[pos].filterp_l;
return (needInfo? [getInfo(startPos), NodeType.PropertyType, ident] : [NodeType.PropertyType, ident])
.concat(getSC());
// number ident
function getDimension(startPos, number) {
return [
getInfo(startPos || pos),
NodeType.DimensionType,
number || getNumber(),
getIdentifier()
];
}
//filterv = progid+:x -> [#filterv].concat(x)
function checkFilterv(_i) {
var start = _i,
l;
// expression '(' raw ')'
function getFunctionExpression(startPos, ident) {
var raw = '';
var balance = 0;
if (l = checkProgid(_i)) _i += l;
else return fail(tokens[_i]);
if (!startPos) {
startPos = pos;
}
while (l = checkProgid(_i)) {
_i += l;
if (!ident) {
ident = getIdentifier();
}
tokens[start].last_progid = _i;
if (ident[2] !== 'expression') {
parseError('`expression` is expected');
}
if (_i < tokens.length && (l = checkSC(_i))) _i += l;
eat(TokenType.LeftParenthesis);
if (_i < tokens.length && (l = checkImportant(_i))) _i += l;
while (pos < tokens.length) {
if (tokens[pos].type === TokenType.RightParenthesis) {
if (balance === 0) {
break;
}
return _i - start;
}
balance--;
} else if (tokens[pos].type === TokenType.LeftParenthesis) {
balance++;
}
function getFilterv() {
var filterv = createToken(NodeType.FiltervType);
var last_progid = tokens[pos].last_progid;
while (pos < last_progid) {
filterv.push(getProgid());
raw += tokens[pos++].value;
}
filterv = filterv.concat(checkSC(pos) ? getSC() : []);
eat(TokenType.RightParenthesis);
if (pos < tokens.length && checkImportant(pos)) filterv.push(getImportant());
return filterv;
return [
getInfo(startPos),
NodeType.FunctionExpressionType,
raw
];
}
//functionExpression = ``expression('' functionExpressionBody*:x ')' -> [#functionExpression, x.join('')],
function checkFunctionExpression(_i) {
var start = _i;
// ident '(' functionBody ')' |
// not '(' <simpleSelector>* ')'
function getFunction(startPos, ident) {
if (!startPos) {
startPos = pos;
}
if (!tokens[_i] || tokens[_i++].value !== 'expression') return fail(tokens[_i - 1]);
if (!ident) {
ident = getIdentifier();
}
if (!tokens[_i] || tokens[_i].type !== TokenType.LeftParenthesis) return fail(tokens[_i]);
eat(TokenType.LeftParenthesis);
return tokens[_i].right - start + 1;
}
var body = ident[2] !== 'not'
? getFunctionBody()
: getNotFunctionBody(); // ok, here we have CSS3 initial draft: http://dev.w3.org/csswg/selectors3/#negation
function getFunctionExpression() {
var startPos = pos;
pos++;
var e = joinValues(pos + 1, tokens[pos].right - 1);
pos = tokens[pos].right + 1;
return needInfo?
[getInfo(startPos), NodeType.FunctionExpressionType, e] :
[NodeType.FunctionExpressionType, e];
return [getInfo(startPos), NodeType.FunktionType, ident, body];
}
//funktion = ident:x '(' functionBody:y ')' -> [#funktion, x, y]
function checkFunktion(_i) {
var start = _i,
l = checkIdent(_i);
function getFunctionBody() {
var node = [getInfo(pos), NodeType.FunctionBodyType];
if (!l) return fail(tokens[_i]);
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.RightParenthesis:
break scan;
_i += l;
case TokenType.Space:
node.push(getS());
break;
if (_i >= tokens.length || tokens[_i].type !== TokenType.LeftParenthesis) return fail(tokens[_i - 1]);
case TokenType.Comment:
node.push(getComment());
break;
return tokens[_i].right - start + 1;
}
case TokenType.NumberSign: // TODO: not sure it should be here
node.push(getVhash());
break;
function getFunktion() {
var startPos = pos,
ident = getIdent();
case TokenType.LeftParenthesis:
case TokenType.LeftSquareBracket:
node.push(getBraces());
break;
pos++;
case TokenType.Solidus:
case TokenType.Asterisk:
case TokenType.Comma:
case TokenType.Colon:
case TokenType.EqualsSign:
node.push(getOperator());
break;
var body = ident[needInfo? 2 : 1] !== 'not'?
getFunctionBody() :
getNotFunctionBody(); // ok, here we have CSS3 initial draft: http://dev.w3.org/csswg/selectors3/#negation
return needInfo?
[getInfo(startPos), NodeType.FunktionType, ident, body] :
[NodeType.FunktionType, ident, body];
}
function getFunctionBody() {
var startPos = pos,
body = [],
x;
while (tokens[pos].type !== TokenType.RightParenthesis) {
if (checkTset(pos)) {
x = getTset();
if ((needInfo && typeof x[1] === 'string') || typeof x[0] === 'string') body.push(x);
else body = body.concat(x);
} else if (checkClazz(pos)) {
body.push(getClazz());
} else {
throwError();
default:
node.push(getAny());
}
}
pos++;
eat(TokenType.RightParenthesis);
return (needInfo?
[getInfo(startPos), NodeType.FunctionBodyType] :
[NodeType.FunctionBodyType]
).concat(body);
return node;
}
function getNotFunctionBody() {
var startPos = pos,
body = [];
var node = [getInfo(pos), NodeType.FunctionBodyType];
while (tokens[pos].type !== TokenType.RightParenthesis) {
if (checkSimpleselector(pos)) {
body.push(getSimpleSelector());
} else {
throwError();
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.RightParenthesis:
pos++;
return node;
default:
node.push(getSimpleSelector(true));
}
}
pos++;
return (needInfo?
[getInfo(startPos), NodeType.FunctionBodyType] :
[NodeType.FunctionBodyType]
).concat(body);
}
function getUnicodeRange(i, tryNext) {
function getUnicodeRange(tryNext) {
var hex = '';
for (;i < tokens.length; i++) {
if (tokens[i].type !== TokenType.DecimalNumber &&
tokens[i].type !== TokenType.Identifier) {
for (; pos < tokens.length; pos++) {
if (tokens[pos].type !== TokenType.DecimalNumber &&
tokens[pos].type !== TokenType.Identifier) {
break;
}
hex += tokens[i].value
hex += tokens[pos].value;
}
if (/^[0-9a-f]{1,6}$/i.test(hex)) {
// U+abc???
if (tryNext) {
for (;hex.length < 6 && i < tokens.length; i++) {
if (tokens[i].type !== TokenType.QuestionMark) {
break;
}
if (!/^[0-9a-f]{1,6}$/i.test(hex)) {
parseError('Unexpected input');
}
hex += tokens[i].value
tryNext = false;
// U+abc???
if (tryNext) {
for (; hex.length < 6 && pos < tokens.length; pos++) {
if (tokens[pos].type !== TokenType.QuestionMark) {
break;
}
hex += tokens[pos].value;
tryNext = false;
}
}
// U+aaa-bbb
if (tryNext) {
if (tokens[i] && tokens[i].type === TokenType.HyphenMinus) {
var next = getUnicodeRange(i + 1);
if (next) {
return next;
}
// U+aaa-bbb
if (tryNext) {
if (pos < tokens.length && tokens[pos].type === TokenType.HyphenMinus) {
pos++;
var next = getUnicodeRange(false);
if (!next) {
parseError('Unexpected input');
}
hex += '-' + next;
}
}
return i;
}
return hex;
}
// node: Ident
function checkIdent(_i, attribute) {
if (_i >= tokens.length) return fail(tokens[_i]);
function readIdent() {
var name = '';
var start = _i,
wasIdent = false;
// unicode-range-token
if (tokens[_i].type === TokenType.Identifier &&
(tokens[_i].value === 'U' || tokens[_i].value === 'u') &&
tokens[_i + 1].type === TokenType.PlusSign) {
var unicodeRange = getUnicodeRange(_i + 2, true);
if (unicodeRange) {
tokens[start].ident_last = unicodeRange - 1;
return unicodeRange - start;
}
// optional first -
if (pos < tokens.length && tokens[pos].type === TokenType.HyphenMinus) {
name = '-';
pos++;
}
if (tokens[_i].type === TokenType.LowLine) return checkIdentLowLine(_i, attribute);
expectAny('Identifier',
TokenType.LowLine,
TokenType.Identifier
);
// start char / word
if (tokens[_i].type === TokenType.HyphenMinus ||
tokens[_i].type === TokenType.Identifier ||
tokens[_i].type === TokenType.DollarSign ||
tokens[_i].type === TokenType.Asterisk) _i++;
else return fail(tokens[_i]);
if (pos < tokens.length) {
name += tokens[pos].value;
pos++;
wasIdent = tokens[_i - 1].type === TokenType.Identifier;
for (; pos < tokens.length; pos++) {
var type = tokens[pos].type;
if (type !== TokenType.LowLine &&
type !== TokenType.Identifier &&
type !== TokenType.DecimalNumber &&
type !== TokenType.HyphenMinus) {
break;
}
for (; _i < tokens.length; _i++) {
if (tokens[_i].type !== TokenType.HyphenMinus &&
tokens[_i].type !== TokenType.LowLine) {
if (tokens[_i].type !== TokenType.Identifier &&
(!attribute || tokens[_i].type !== TokenType.Colon) &&
(!wasIdent || tokens[_i].type !== TokenType.DecimalNumber)
) break;
else wasIdent = true;
name += tokens[pos].value;
}
}
if (!wasIdent && tokens[start].type !== TokenType.Asterisk) return fail(tokens[_i]);
return name;
}
tokens[start].ident_last = _i - 1;
function getNamespacedIdentifier(checkColon) {
if (pos >= tokens.length) {
parseError('Unexpected end of input');
}
return _i - start;
}
var info = getInfo(pos);
var name;
function checkIdentLowLine(_i, attribute) {
var start = _i;
if (tokens[pos].type === TokenType.Asterisk) {
checkColon = false;
name = '*';
pos++;
} else {
name = readIdent();
}
_i++;
if (pos < tokens.length) {
if (tokens[pos].type === TokenType.VerticalLine &&
pos + 1 < tokens.length &&
tokens[pos + 1].type !== TokenType.EqualsSign) {
name += '|';
pos++;
for (; _i < tokens.length; _i++) {
if (tokens[_i].type !== TokenType.HyphenMinus &&
tokens[_i].type !== TokenType.DecimalNumber &&
tokens[_i].type !== TokenType.LowLine &&
tokens[_i].type !== TokenType.Identifier &&
(!attribute || tokens[_i].type !== TokenType.Colon)) break;
if (pos < tokens.length) {
if (tokens[pos].type === TokenType.HyphenMinus ||
tokens[pos].type === TokenType.Identifier ||
tokens[pos].type === TokenType.LowLine) {
name += readIdent();
} else if (tokens[pos].type === TokenType.Asterisk) {
checkColon = false;
name += '*';
pos++;
}
}
}
}
tokens[start].ident_last = _i - 1;
if (checkColon && pos < tokens.length && tokens[pos].type === TokenType.Colon) {
pos++;
name += ':' + readIdent();
}
return _i - start;
return [
info,
NodeType.IdentType,
name
];
}
function getIdent() {
var startPos = pos,
s = joinValues(pos, tokens[pos].ident_last);
pos = tokens[pos].ident_last + 1;
return needInfo?
[getInfo(startPos), NodeType.IdentType, s] :
[NodeType.IdentType, s];
function getIdentifier() {
return [getInfo(pos), NodeType.IdentType, readIdent()];
}
//important = '!' sc*:s0 seq('important') -> [#important].concat(s0)
function checkImportant(_i) {
var start = _i,
l;
// ! ws* important
function getImportant() {
eat(TokenType.ExclamationMark);
if (tokens[_i++].type !== TokenType.ExclamationMark) return fail(tokens[_i - 1]);
var node = readSC([getInfo(pos - 1), NodeType.ImportantType]);
if (l = checkSC(_i)) _i += l;
expectIdentifier('important', true);
if (tokens[_i].value.toLowerCase() !== 'important') return fail(tokens[_i]);
return _i - start + 1;
return node;
}
function getImportant() {
// odd | even | number? n
function getNth() {
expectAny('Number, odd or even',
TokenType.Identifier,
TokenType.DecimalNumber
);
var startPos = pos;
var value = tokens[pos].value;
pos++;
if (tokens[pos].type === TokenType.DecimalNumber) {
if (pos + 1 < tokens.length &&
tokens[pos + 1].type === TokenType.Identifier &&
tokens[pos + 1].value === 'n') {
value += 'n';
pos++;
}
} else {
if (value !== 'odd' && value !== 'even' && value !== 'n') {
parseError('Unexpected identifier');
}
}
var sc = getSC();
pos++;
return (needInfo? [getInfo(startPos), NodeType.ImportantType] : [NodeType.ImportantType]).concat(sc);
return [
getInfo(startPos),
NodeType.NthType,
value
];
}
// node: Namespace
function checkNamespace(_i) {
if (tokens[_i].type === TokenType.VerticalLine) return 1;
function getNthSelector() {
eat(TokenType.Colon);
expectIdentifier('nth', false);
return fail(tokens[_i]);
}
var node = [getInfo(pos - 1), NodeType.NthselectorType, getIdentifier()];
function getNamespace() {
var startPos = pos;
eat(TokenType.LeftParenthesis);
pos++;
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.RightParenthesis:
break scan;
return needInfo?
[getInfo(startPos), NodeType.NamespaceType] :
[NodeType.NamespaceType];
}
case TokenType.Space:
node.push(getS());
break;
//nth = (digit | 'n')+:x -> [#nth, x.join('')]
// | (seq('even') | seq('odd')):x -> [#nth, x]
function checkNth(_i) {
return checkNth1(_i) || checkNth2(_i);
}
case TokenType.Comment:
node.push(getComment());
break;
function checkNth1(_i) {
var start = _i;
case TokenType.HyphenMinus:
case TokenType.PlusSign:
node.push(getUnary());
break;
for (; _i < tokens.length; _i++) {
if (tokens[_i].type !== TokenType.DecimalNumber && tokens[_i].value !== 'n') break;
default:
node.push(getNth());
}
}
if (_i !== start) {
tokens[start].nth_last = _i - 1;
return _i - start;
}
eat(TokenType.RightParenthesis);
return fail(tokens[_i]);
return node;
}
function getNth() {
function tryGetNumber() {
var startPos = pos;
var wasDigits = false;
var number = '';
var i = pos;
if (tokens[pos].nth_last) {
var n = needInfo?
[getInfo(startPos), NodeType.NthType, joinValues(pos, tokens[pos].nth_last)] :
[NodeType.NthType, joinValues(pos, tokens[pos].nth_last)];
if (i < tokens.length && tokens[i].type === TokenType.HyphenMinus) {
number = '-';
i++;
}
pos = tokens[pos].nth_last + 1;
return n;
if (i < tokens.length && tokens[i].type === TokenType.DecimalNumber) {
wasDigits = true;
number += tokens[i].value;
i++;
}
return needInfo?
[getInfo(startPos), NodeType.NthType, tokens[pos++].value] :
[NodeType.NthType, tokens[pos++].value];
}
function checkNth2(_i) {
if (tokens[_i].value === 'even' || tokens[_i].value === 'odd') return 1;
return fail(tokens[_i]);
}
//nthf = ':' seq('nth-'):x (seq('child') | seq('last-child') | seq('of-type') | seq('last-of-type')):y -> (x + y)
function checkNthf(_i) {
var start = _i,
l = 0;
if (tokens[_i++].type !== TokenType.Colon) return fail(tokens[_i - 1]); l++;
if (tokens[_i++].value !== 'nth' || tokens[_i++].value !== '-') return fail(tokens[_i - 1]); l += 2;
if ('child' === tokens[_i].value) {
l += 1;
} else if ('last-child' === tokens[_i].value +
tokens[_i + 1].value +
tokens[_i + 2].value) {
l += 3;
} else if ('of-type' === tokens[_i].value +
tokens[_i + 1].value +
tokens[_i + 2].value) {
l += 3;
} else if ('last-of-type' === tokens[_i].value +
tokens[_i + 1].value +
tokens[_i + 2].value +
tokens[_i + 3].value +
tokens[_i + 4].value) {
l += 5;
} else return fail(tokens[_i]);
tokens[start + 1].nthf_last = start + l - 1;
return l;
}
function getNthf() {
pos++;
var s = joinValues(pos, tokens[pos].nthf_last);
pos = tokens[pos].nthf_last + 1;
return s;
}
//nthselector = nthf:x '(' (sc | unary | nth)*:y ')' -> [#nthselector, [#ident, x]].concat(y)
function checkNthselector(_i) {
var start = _i,
l;
if (l = checkNthf(_i)) _i += l;
else return fail(tokens[_i]);
if (tokens[_i].type !== TokenType.LeftParenthesis || !tokens[_i].right) return fail(tokens[_i]);
l++;
var rp = tokens[_i++].right;
while (_i < rp) {
if (l = checkSC(_i)) _i += l;
else if (l = checkUnary(_i)) _i += l;
else if (l = checkNth(_i)) _i += l;
else return fail(tokens[_i]);
if (i < tokens.length && tokens[i].type === TokenType.FullStop) {
number += '.';
i++;
}
return rp - start + 1;
}
function getNthselector() {
var nthf = needInfo?
[getInfo(pos), NodeType.IdentType, getNthf()] :
[NodeType.IdentType, getNthf()],
ns = needInfo?
[getInfo(pos), NodeType.NthselectorType, nthf] :
[NodeType.NthselectorType, nthf];
pos++;
while (tokens[pos].type !== TokenType.RightParenthesis) {
if (checkSC(pos)) ns = ns.concat(getSC());
else if (checkUnary(pos)) ns.push(getUnary());
else if (checkNth(pos)) ns.push(getNth());
if (i < tokens.length && tokens[i].type === TokenType.DecimalNumber) {
wasDigits = true;
number += tokens[i].value;
i++;
}
pos++;
return ns;
}
// node: Number
function checkNumber(_i, sign) {
if (_i < tokens.length && tokens[_i].number_l) return tokens[_i].number_l;
if (!sign && _i < tokens.length && tokens[_i].type === TokenType.HyphenMinus) {
var x = checkNumber(_i + 1, true);
if (x) {
tokens[_i].number_l = x + 1;
return tokens[_i].number_l;
} else {
fail(tokens[_i])
}
if (wasDigits) {
pos = i;
return [getInfo(startPos), NodeType.NumberType, number];
}
if (_i < tokens.length && tokens[_i].type === TokenType.DecimalNumber &&
(!tokens[_i + 1] ||
(tokens[_i + 1] && tokens[_i + 1].type !== TokenType.FullStop))
) return (tokens[_i].number_l = 1, tokens[_i].number_l); // 10
if (_i < tokens.length &&
tokens[_i].type === TokenType.DecimalNumber &&
tokens[_i + 1] && tokens[_i + 1].type === TokenType.FullStop &&
(!tokens[_i + 2] || (tokens[_i + 2].type !== TokenType.DecimalNumber))
) return (tokens[_i].number_l = 2, tokens[_i].number_l); // 10.
if (_i < tokens.length &&
tokens[_i].type === TokenType.FullStop &&
tokens[_i + 1].type === TokenType.DecimalNumber
) return (tokens[_i].number_l = 2, tokens[_i].number_l); // .10
if (_i < tokens.length &&
tokens[_i].type === TokenType.DecimalNumber &&
tokens[_i + 1] && tokens[_i + 1].type === TokenType.FullStop &&
tokens[_i + 2] && tokens[_i + 2].type === TokenType.DecimalNumber
) return (tokens[_i].number_l = 3, tokens[_i].number_l); // 10.10
return fail(tokens[_i]);
return null;
}
function getNumber() {
var s = '',
startPos = pos,
l = tokens[pos].number_l;
var number = tryGetNumber();
for (var i = 0; i < l; i++) {
s += tokens[pos + i].value;
if (!number) {
parseError('Wrong number');
}
pos += l;
return needInfo?
[getInfo(startPos), NodeType.NumberType, s] :
[NodeType.NumberType, s];
return number;
}
// node: Operator
function checkOperator(_i) {
if (_i < tokens.length &&
(tokens[_i].type === TokenType.Solidus ||
tokens[_i].type === TokenType.Comma ||
tokens[_i].type === TokenType.Colon ||
tokens[_i].type === TokenType.EqualsSign)) return 1;
// '/' | '*' | ',' | ':' | '='
// TODO: remove '=' since it's wrong operator, but theat as operator
// to make old things like `filter: alpha(opacity=0)` works
function getOperator() {
expectAny('Operator',
TokenType.Solidus,
TokenType.Asterisk,
TokenType.Comma,
TokenType.Colon,
TokenType.EqualsSign
);
return fail(tokens[_i]);
return [getInfo(pos), NodeType.OperatorType, tokens[pos++].value];
}
function getOperator() {
return needInfo?
[getInfo(pos), NodeType.OperatorType, tokens[pos++].value] :
[NodeType.OperatorType, tokens[pos++].value];
}
// node: Percentage
function checkPercentage(_i) {
var x = checkNumber(_i);
function tryGetPercentage() {
var startPos = pos;
var number = tryGetNumber();
if (!x || (x && _i + x >= tokens.length)) return fail(tokens[_i]);
if (!number) {
return null;
}
if (tokens[_i + x].type === TokenType.PercentSign) return x + 1;
if (pos >= tokens.length || tokens[pos].type !== TokenType.PercentSign) {
return null;
}
return fail(tokens[_i]);
return getPercentage(startPos, number);
}
function getPercentage() {
var startPos = pos,
n = getNumber();
function getPercentage(startPos, number) {
if (!startPos) {
startPos = pos;
}
pos++;
if (!number) {
number = getNumber();
}
return needInfo?
[getInfo(startPos), NodeType.PercentageType, n] :
[NodeType.PercentageType, n];
}
eat(TokenType.PercentSign);
//progid = sc*:s0 seq('progid:DXImageTransform.Microsoft.'):x letter+:y '(' (m_string | m_comment | ~')' char)+:z ')' sc*:s1
// -> this.concat([#progid], s0, [[#raw, x + y.join('') + '(' + z.join('') + ')']], s1),
function checkProgid(_i) {
var start = _i,
l,
x;
if (l = checkSC(_i)) _i += l;
if (_i < tokens.length - 1 && tokens[_i].value === 'progid' && tokens[_i + 1].type === TokenType.Colon) {
_i += 2;
} else return fail(tokens[_i]);
if (l = checkSC(_i)) _i += l;
if ((x = joinValues2(_i, 4)) === 'DXImageTransform.Microsoft.') {
_i += 4;
} else return fail(tokens[_i - 1]);
if (l = checkIdent(_i)) _i += l;
else return fail(tokens[_i]);
if (l = checkSC(_i)) _i += l;
if (tokens[_i].type === TokenType.LeftParenthesis) {
tokens[start].progid_end = tokens[_i].right;
_i = tokens[_i].right + 1;
} else return fail(tokens[_i]);
if (l = checkSC(_i)) _i += l;
return _i - start;
return [getInfo(startPos), NodeType.PercentageType, number];
}
function getProgid() {
var startPos = pos,
progid_end = tokens[pos].progid_end;
function getFilterv() {
var node = [getInfo(pos), NodeType.FiltervType];
return (needInfo? [getInfo(startPos), NodeType.ProgidType] : [NodeType.ProgidType])
.concat(getSC())
.concat([_getProgid(progid_end)])
.concat(getSC());
}
while (checkProgid(pos)) {
node.push(getProgid());
}
function _getProgid(progid_end) {
var startPos = pos,
x = joinValues(pos, progid_end);
readSC(node);
pos = progid_end + 1;
if (pos < tokens.length && tokens[pos].type === TokenType.ExclamationMark) {
node.push(getImportant());
}
return needInfo?
[getInfo(startPos), NodeType.RawType, x] :
[NodeType.RawType, x];
return node;
}
//property = ident:x sc*:s0 -> this.concat([#property, x], s0)
function checkProperty(_i) {
var start = _i,
l;
// 'progid:' ws* 'DXImageTransform.Microsoft.' ident ws* '(' .* ')'
function checkSC(i) {
var start = i;
if (l = checkIdent(_i)) _i += l;
else return fail(tokens[_i]);
while (i < tokens.length) {
if (tokens[i].type === TokenType.Space ||
tokens[i].type === TokenType.Comment) {
i++;
} else {
break;
}
}
if (l = checkSC(_i)) _i += l;
return _i - start;
return i - start;
}
function getProperty() {
var startPos = pos;
function checkProgid(i) {
var start = i;
return (needInfo?
[getInfo(startPos), NodeType.PropertyType, getIdent()] :
[NodeType.PropertyType, getIdent()])
.concat(getSC());
}
i += checkSC(i);
function checkPseudo(_i) {
return checkPseudoe(_i) ||
checkPseudoc(_i);
}
if (i + 1 >= tokens.length ||
tokens[i + 0].value !== 'progid' ||
tokens[i + 1].type !== TokenType.Colon) {
return false; // fail
}
function getPseudo() {
if (checkPseudoe(pos)) return getPseudoe();
if (checkPseudoc(pos)) return getPseudoc();
}
i += 2;
i += checkSC(i);
function checkPseudoe(_i) {
var l;
if (i + 6 >= tokens.length ||
tokens[i + 0].value !== 'DXImageTransform' ||
tokens[i + 1].type !== TokenType.FullStop ||
tokens[i + 2].value !== 'Microsoft' ||
tokens[i + 3].type !== TokenType.FullStop ||
tokens[i + 4].type !== TokenType.Identifier) {
return false; // fail
}
if (tokens[_i++].type !== TokenType.Colon) return fail(tokens[_i - 1]);
i += 5;
i += checkSC(i);
if (tokens[_i++].type !== TokenType.Colon) return fail(tokens[_i - 1]);
if (i >= tokens.length ||
tokens[i].type !== TokenType.LeftParenthesis) {
return false; // fail
}
if (l = checkIdent(_i)) return l + 2;
return fail(tokens[_i]);
}
function getPseudoe() {
var startPos = pos;
pos += 2;
return needInfo?
[getInfo(startPos), NodeType.PseudoeType, getIdent()] :
[NodeType.PseudoeType, getIdent()];
}
//pseudoc = ':' (funktion | ident):x -> [#pseudoc, x]
function checkPseudoc(_i) {
var l;
if (tokens[_i++].type !== TokenType.Colon) return fail(tokens[_i - 1]);
if ((l = checkFunktion(_i)) || (l = checkIdent(_i))) return l + 1;
return fail(tokens[_i]);
}
function getPseudoc() {
var startPos = pos;
pos++;
return needInfo?
[getInfo(startPos), NodeType.PseudocType, checkFunktion(pos)? getFunktion() : getIdent()] :
[NodeType.PseudocType, checkFunktion(pos)? getFunktion() : getIdent()];
}
//ruleset = selector*:x block:y -> this.concat([#ruleset], x, [y])
function checkRuleset(_i) {
var start = _i,
l;
if (tokens[start].ruleset_l !== undefined) return tokens[start].ruleset_l;
while (l = checkSelector(_i)) {
_i += l;
while (i < tokens.length) {
if (tokens[i++].type === TokenType.RightParenthesis) {
break;
}
}
if (l = checkBlock(_i)) _i += l;
else return fail(tokens[_i]);
tokens[start].progidEnd = i;
tokens[start].ruleset_l = _i - start;
return _i - start;
return true;
}
function getRuleset() {
var ruleset = createToken(NodeType.RulesetType);
function getProgid() {
var node = [getInfo(pos), NodeType.ProgidType];
var progidEnd = tokens[pos].progidEnd;
var value = '';
while (!checkBlock(pos)) {
ruleset.push(getSelector());
if (!progidEnd && !checkProgid(pos)) {
parseError('progid is expected');
}
ruleset.push(getBlock());
readSC(node);
return ruleset;
}
// node: S
function checkS(_i) {
if (tokens[_i].type === TokenType.Space) {
return 1;
var rawStart = pos;
for (; pos < progidEnd; pos++) {
value += tokens[pos].value;
}
return fail(tokens[_i]);
}
node.push([
getInfo(rawStart),
NodeType.RawType,
value
]);
function getS() {
var startPos = pos,
s = tokens[pos].value;
readSC(node);
pos++;
return needInfo? [getInfo(startPos), NodeType.SType, s] : [NodeType.SType, s];
return node;
}
function checkSC(_i) {
var l,
lsc = 0;
while (_i < tokens.length) {
if (!(l = checkS(_i)) && !(l = checkComment(_i))) break;
_i += l;
lsc += l;
// <pseudo-element> | <nth-selector> | <pseudo-class>
function getPseudo() {
if (pos >= tokens.length || tokens[pos].type !== TokenType.Colon) {
parseError('Colon is expected');
}
if (lsc) return lsc;
if (_i >= tokens.length) return fail(tokens[tokens.length - 1]);
return fail(tokens[_i]);
}
function getSC() {
var sc = [];
while (pos < tokens.length) {
if (checkS(pos)) sc.push(getS());
else if (checkComment(pos)) sc.push(getComment());
else break;
if (pos + 1 >= tokens.length) {
parseError('Unexpected end of input');
}
return sc;
}
var next = tokens[pos + 1];
//selector = (simpleselector | delim)+:x -> this.concat([#selector], x)
function checkSelector(_i) {
var start = _i,
l;
if (_i < tokens.length) {
while (l = checkSimpleselector(_i) || checkDelim(_i)) {
_i += l;
}
tokens[start].selector_end = _i - 1;
return _i - start;
if (next.type === TokenType.Colon) {
return getPseudoe();
}
}
function getSelector() {
var selector = createToken(NodeType.SelectorType);
var selector_end = tokens[pos].selector_end;
while (pos <= selector_end) {
selector.push(checkDelim(pos) ? getDelim() : getSimpleSelector());
if (next.type === TokenType.Identifier &&
next.value === 'nth') {
return getNthSelector();
}
return selector;
return getPseudoc();
}
// node: Shash
function checkShash(_i) {
if (tokens[_i].type !== TokenType.NumberSign) return fail(tokens[_i]);
// :: ident
function getPseudoe() {
eat(TokenType.Colon);
eat(TokenType.Colon);
var l = checkNmName(_i + 1);
if (l) return l + 1;
return fail(tokens[_i]);
return [getInfo(pos - 2), NodeType.PseudoeType, getIdentifier()];
}
function getShash() {
// : ( ident | function )
function getPseudoc() {
var startPos = pos;
var value = eat(TokenType.Colon) && getIdentifier();
pos++;
return needInfo?
[getInfo(startPos), NodeType.ShashType, getNmName()] :
[NodeType.ShashType, getNmName()];
}
//simpleselector = (nthselector | combinator | attrib | pseudo | clazz | shash | any | sc | namespace)+:x -> this.concatContent([#simpleselector], [x])
function checkSimpleselector(_i) {
var start = _i,
l;
while (_i < tokens.length) {
if (l = _checkSimpleSelector(_i)) _i += l;
else break;
if (pos < tokens.length && tokens[pos].type === TokenType.LeftParenthesis) {
value = getFunction(startPos, value);
}
if (_i - start) return _i - start;
if (_i >= tokens.length) return fail(tokens[tokens.length - 1]);
return fail(tokens[_i]);
return [
getInfo(startPos),
NodeType.PseudocType,
value
];
}
function _checkSimpleSelector(_i) {
return checkNthselector(_i) ||
checkCombinator(_i) ||
checkAttrib(_i) ||
checkPseudo(_i) ||
checkClazz(_i) ||
checkShash(_i) ||
checkAny(_i) ||
checkSC(_i) ||
checkNamespace(_i);
// ws
function getS() {
return [getInfo(pos), NodeType.SType, tokens[pos++].value];
}
function getSimpleSelector() {
var ss = createToken(NodeType.SimpleselectorType);
var t;
function readSC(node) {
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
case TokenType.Space:
node.push(getS());
break;
while (pos < tokens.length && _checkSimpleSelector(pos)) {
t = _getSimpleSelector();
case TokenType.Comment:
node.push(getComment());
break;
if (!t) {
throwError();
default:
break scan;
}
if ((needInfo && typeof t[1] === 'string') || typeof t[0] === 'string') ss.push(t);
else ss = ss.concat(t);
}
return ss;
return node;
}
function _getSimpleSelector() {
if (checkNthselector(pos)) return getNthselector();
else if (checkCombinator(pos)) return getCombinator();
else if (checkAttrib(pos)) return getAttrib();
else if (checkPseudo(pos)) return getPseudo();
else if (checkClazz(pos)) return getClazz();
else if (checkShash(pos)) return getShash();
else if (checkAny(pos)) return getAny();
else if (checkSC(pos)) return getSC();
else if (checkNamespace(pos)) return getNamespace();
}
// node: String
function checkString(_i) {
if (_i < tokens.length &&
(tokens[_i].type === TokenType.StringSQ || tokens[_i].type === TokenType.StringDQ)
) return 1;
return fail(tokens[_i]);
}
function getString() {
var startPos = pos;
return needInfo?
[getInfo(startPos), NodeType.StringType, tokens[pos++].value] :
[NodeType.StringType, tokens[pos++].value];
return [getInfo(pos), NodeType.StringType, tokens[pos++].value];
}
//stylesheet = (cdo | cdc | sc | statement)*:x -> this.concat([#stylesheet], x)
function checkStylesheet(_i) {
var start = _i,
l;
// '+' | '-'
function getUnary() {
expectAny('Unary operator',
TokenType.HyphenMinus,
TokenType.PlusSign
);
while (_i < tokens.length) {
if (l = checkSC(_i)) _i += l;
else {
currentBlockLN = tokens[_i].line;
if (l = checkAtrule(_i)) _i += l;
else if (l = checkRuleset(_i)) _i += l;
else if (l = checkUnknown(_i)) _i += l;
else throwError();
}
}
return _i - start;
return [getInfo(pos), NodeType.UnaryType, tokens[pos++].value];
}
function getStylesheet() {
var stylesheet = createToken(NodeType.StylesheetType);
// '//' ...
// TODO: remove it as wrong thing
function getUnknown() {
eat(TokenType.Unknown);
while (pos < tokens.length) {
if (checkSC(pos)) stylesheet = stylesheet.concat(getSC());
else {
currentBlockLN = tokens[pos].line;
if (checkRuleset(pos)) stylesheet.push(getRuleset());
else if (checkAtrule(pos)) stylesheet.push(getAtrule());
else if (checkUnknown(pos)) stylesheet.push(getUnknown());
else throwError();
}
}
return stylesheet;
return [getInfo(pos - 1), NodeType.UnknownType, tokens[pos - 1].value];
}
//tset = vhash | any | sc | operator
function checkTset(_i) {
return checkVhash(_i) ||
checkAny(_i) ||
checkSC(_i) ||
checkOperator(_i);
}
// url '(' ws* (string | raw) ws* ')'
function getUri(startPos, ident) {
var node = [getInfo(startPos || pos), NodeType.UriType];
function getTset() {
if (checkVhash(pos)) return getVhash();
else if (checkAny(pos)) return getAny();
else if (checkSC(pos)) return getSC();
else if (checkOperator(pos)) return getOperator();
}
function checkTsets(_i) {
var start = _i,
l;
while (l = checkTset(_i)) {
_i += l;
if (!ident) {
ident = getIdentifier();
}
return _i - start;
}
function getTsets() {
var tsets = [],
x;
while (x = getTset()) {
if ((needInfo && typeof x[1] === 'string') || typeof x[0] === 'string') tsets.push(x);
else tsets = tsets.concat(x);
if (ident[2] !== 'url') {
parseError('`url` is expected');
}
return tsets;
}
eat(TokenType.LeftParenthesis); // (
// node: Unary
function checkUnary(_i) {
if (_i < tokens.length &&
(tokens[_i].type === TokenType.HyphenMinus ||
tokens[_i].type === TokenType.PlusSign)
) return 1;
readSC(node);
return fail(tokens[_i]);
}
function getUnary() {
var startPos = pos;
return needInfo?
[getInfo(startPos), NodeType.UnaryType, tokens[pos++].value] :
[NodeType.UnaryType, tokens[pos++].value];
}
// node: Unknown
function checkUnknown(_i) {
if (_i < tokens.length && tokens[_i].type === TokenType.CommentSL) return 1;
return fail(tokens[_i]);
}
function getUnknown() {
var startPos = pos;
return needInfo?
[getInfo(startPos), NodeType.UnknownType, tokens[pos++].value] :
[NodeType.UnknownType, tokens[pos++].value];
}
// uri = seq('url(') sc*:s0 string:x sc*:s1 ')' -> this.concat([#uri], s0, [x], s1)
// | seq('url(') sc*:s0 (~')' ~m_w char)*:x sc*:s1 ')' -> this.concat([#uri], s0, [[#raw, x.join('')]], s1),
function checkUri(_i) {
var start = _i;
if (_i < tokens.length && tokens[_i++].value !== 'url') return fail(tokens[_i - 1]);
if (!tokens[_i] || tokens[_i].type !== TokenType.LeftParenthesis) return fail(tokens[_i]);
return tokens[_i].right - start + 1;
}
function getUri() {
var startPos = pos;
pos += 2;
if (checkUri1(pos)) {
var uri = (needInfo? [getInfo(startPos), NodeType.UriType] : [NodeType.UriType])
.concat(getSC())
.concat([getString()])
.concat(getSC());
pos++;
return uri;
if (tokens[pos].type === TokenType.String) {
node.push(getString());
readSC(node);
} else {
var uri = (needInfo? [getInfo(startPos), NodeType.UriType] : [NodeType.UriType])
.concat(getSC()),
l = checkExcluding(pos),
raw = needInfo?
[getInfo(pos), NodeType.RawType, joinValues(pos, pos + l)] :
[NodeType.RawType, joinValues(pos, pos + l)];
var rawStart = pos;
var raw = '';
uri.push(raw);
while (pos < tokens.length) {
var type = tokens[pos].type;
pos += l + 1;
if (type === TokenType.Space ||
type === TokenType.LeftParenthesis ||
type === TokenType.RightParenthesis) {
break;
}
uri = uri.concat(getSC());
raw += tokens[pos++].value;
}
pos++;
node.push([
getInfo(rawStart),
NodeType.RawType,
raw
]);
return uri;
readSC(node);
}
}
function checkUri1(_i) {
var start = _i,
l = checkSC(_i);
eat(TokenType.RightParenthesis); // )
if (l) _i += l;
if (tokens[_i].type !== TokenType.StringDQ && tokens[_i].type !== TokenType.StringSQ) return fail(tokens[_i]);
_i++;
if (l = checkSC(_i)) _i += l;
return _i - start;
return node;
}
// value = (sc | vhash | any | block | atkeyword | operator | important)+:x -> this.concat([#value], x)
function checkValue(_i) {
var start = _i,
l;
while (_i < tokens.length) {
if (l = _checkValue(_i)) _i += l;
else break;
}
if (_i - start) return _i - start;
return fail(tokens[_i]);
}
function _checkValue(_i) {
return checkSC(_i) ||
checkVhash(_i) ||
checkAny(_i) ||
checkBlock(_i) ||
checkAtkeyword(_i) ||
checkOperator(_i) ||
checkImportant(_i);
}
function getValue() {
var ss = createToken(NodeType.ValueType);
var t;
while (pos < tokens.length && _checkValue(pos)) {
t = _getValue();
if ((needInfo && typeof t[1] === 'string') || typeof t[0] === 'string') ss.push(t);
else ss = ss.concat(t);
}
return ss;
}
function _getValue() {
if (checkSC(pos)) return getSC();
else if (checkVhash(pos)) return getVhash();
else if (checkAny(pos)) return getAny();
else if (checkBlock(pos)) return getBlock();
else if (checkAtkeyword(pos)) return getAtkeyword();
else if (checkOperator(pos)) return getOperator();
else if (checkImportant(pos)) return getImportant();
}
// node: Vhash
function checkVhash(_i) {
if (_i >= tokens.length || tokens[_i].type !== TokenType.NumberSign) return fail(tokens[_i]);
var l = checkNmName2(_i + 1);
if (l) return l + 1;
return fail(tokens[_i]);
}
// # ident
function getVhash() {
var startPos = pos;
eat(TokenType.NumberSign);
pos++;
var name = tokens[pos].value;
return needInfo?
[getInfo(startPos), NodeType.VhashType, getNmName2()] :
[NodeType.VhashType, getNmName2()];
}
function checkNmName(_i) {
var start = _i;
// start char / word
if (tokens[_i].type === TokenType.HyphenMinus ||
tokens[_i].type === TokenType.LowLine ||
tokens[_i].type === TokenType.Identifier ||
tokens[_i].type === TokenType.DecimalNumber) _i++;
else return fail(tokens[_i]);
for (; _i < tokens.length; _i++) {
if (tokens[_i].type !== TokenType.HyphenMinus &&
tokens[_i].type !== TokenType.LowLine &&
tokens[_i].type !== TokenType.Identifier &&
tokens[_i].type !== TokenType.DecimalNumber) break;
if (tokens[pos++].type === TokenType.DecimalNumber) {
if (pos < tokens.length && tokens[pos].type === TokenType.Identifier) {
name += tokens[pos++].value;
}
}
tokens[start].nm_name_last = _i - 1;
return _i - start;
return [getInfo(pos - 1), NodeType.VhashType, name];
}
function getNmName() {
var s = joinValues(pos, tokens[pos].nm_name_last);
module.exports = function parse(source, rule, options) {
var ast;
pos = tokens[pos].nm_name_last + 1;
options = options || {};
return s;
}
function checkNmName2(_i) {
if (tokens[_i].type === TokenType.Identifier) return 1;
else if (tokens[_i].type !== TokenType.DecimalNumber) return fail(tokens[_i]);
_i++;
if (!tokens[_i] || tokens[_i].type !== TokenType.Identifier) return 1;
return 2;
}
function getNmName2() {
var s = tokens[pos].value;
if (tokens[pos++].type === TokenType.DecimalNumber &&
pos < tokens.length &&
tokens[pos].type === TokenType.Identifier
) s += tokens[pos++].value;
return s;
}
function checkExcluding( _i) {
var start = _i;
while(_i < tokens.length) {
var type = tokens[_i++].type;
if (type === TokenType.Space ||
type === TokenType.LeftParenthesis ||
type === TokenType.RightParenthesis) {
break;
}
if (options === true) {
options = {
needPositions: true,
needInfo: true
};
}
return _i - start - 2;
}
needPositions = options.needPositions || false;
filename = options.filename || '<unknown>';
rule = rule || 'stylesheet';
pos = 0;
function joinValues(start, finish) {
var s = '';
tokens = tokenize(source);
for (var i = start; i <= finish; i++) {
s += tokens[i].value;
if (tokens.length) {
ast = rules[rule]();
}
return s;
}
tokens = null; // drop tokens
function joinValues2(start, num) {
if (start + num - 1 >= tokens.length) {
return;
if (!ast && rule === 'stylesheet') {
ast = [{}, rule];
}
var s = '';
for (var i = 0; i < num; i++) {
s += tokens[start + i].value;
if (ast && !options.needInfo) {
ast = cleanInfo(ast);
}
return s;
}
module.exports = function parse(source, rule, _needInfo) {
tokens = tokenize(source);
rule = rule || 'stylesheet';
needInfo = _needInfo;
pos = 0;
failLN = 0;
var ast = CSSPRules[rule]();
if (!ast && rule === 'stylesheet') {
return needInfo ? [{}, rule] : [rule];
}
//console.log(require('../utils/stringify.js')(require('../utils/cleanInfo.js')(ast), true));
// console.log(require('../utils/stringify.js')(require('../utils/cleanInfo.js')(ast), true));
return ast;
};

@@ -1,49 +0,97 @@

var TokenType = require('./const.js');
'use strict';
var TokenType = require('./const.js').TokenType;
var lineStartPos;
var line;
var pos;
var lineStartPos;
var ln;
var Punctuation = {
' ': TokenType.Space,
'\n': TokenType.Newline,
'\r': TokenType.Newline,
'\t': TokenType.Tab,
'!': TokenType.ExclamationMark,
'"': TokenType.QuotationMark,
'#': TokenType.NumberSign,
'$': TokenType.DollarSign,
'%': TokenType.PercentSign,
'&': TokenType.Ampersand,
'\'': TokenType.Apostrophe,
'(': TokenType.LeftParenthesis,
')': TokenType.RightParenthesis,
'*': TokenType.Asterisk,
'+': TokenType.PlusSign,
',': TokenType.Comma,
'-': TokenType.HyphenMinus,
'.': TokenType.FullStop,
'/': TokenType.Solidus,
':': TokenType.Colon,
';': TokenType.Semicolon,
'<': TokenType.LessThanSign,
'=': TokenType.EqualsSign,
'>': TokenType.GreaterThanSign,
'?': TokenType.QuestionMark,
'@': TokenType.CommercialAt,
'[': TokenType.LeftSquareBracket,
']': TokenType.RightSquareBracket,
'^': TokenType.CircumflexAccent,
'_': TokenType.LowLine,
'{': TokenType.LeftCurlyBracket,
'|': TokenType.VerticalLine,
'}': TokenType.RightCurlyBracket,
'~': TokenType.Tilde
var TAB = 9;
var N = 10;
var F = 12;
var R = 13;
var SPACE = 32;
var DOUBLE_QUOTE = 34;
var QUOTE = 39;
var RIGHT_PARENTHESIS = 41;
var STAR = 42;
var SLASH = 47;
var BACK_SLASH = 92;
var UNDERSCORE = 95;
var LEFT_CURLY_BRACE = 123;
var RIGHT_CURLY_BRACE = 125;
var WHITESPACE = 1;
var PUNCTUATOR = 2;
var DIGIT = 3;
var STRING_SQ = 4;
var STRING_DQ = 5;
var PUNCTUATION = {
9: TokenType.Tab, // '\t'
10: TokenType.Newline, // '\n'
13: TokenType.Newline, // '\r'
32: TokenType.Space, // ' '
33: TokenType.ExclamationMark, // '!'
34: TokenType.QuotationMark, // '"'
35: TokenType.NumberSign, // '#'
36: TokenType.DollarSign, // '$'
37: TokenType.PercentSign, // '%'
38: TokenType.Ampersand, // '&'
39: TokenType.Apostrophe, // '\''
40: TokenType.LeftParenthesis, // '('
41: TokenType.RightParenthesis, // ')'
42: TokenType.Asterisk, // '*'
43: TokenType.PlusSign, // '+'
44: TokenType.Comma, // ','
45: TokenType.HyphenMinus, // '-'
46: TokenType.FullStop, // '.'
47: TokenType.Solidus, // '/'
58: TokenType.Colon, // ':'
59: TokenType.Semicolon, // ';'
60: TokenType.LessThanSign, // '<'
61: TokenType.EqualsSign, // '='
62: TokenType.GreaterThanSign, // '>'
63: TokenType.QuestionMark, // '?'
64: TokenType.CommercialAt, // '@'
91: TokenType.LeftSquareBracket, // '['
93: TokenType.RightSquareBracket, // ']'
94: TokenType.CircumflexAccent, // '^'
95: TokenType.LowLine, // '_'
123: TokenType.LeftCurlyBracket, // '{'
124: TokenType.VerticalLine, // '|'
125: TokenType.RightCurlyBracket, // '}'
126: TokenType.Tilde // '~'
};
var SYMBOL_CATEGORY_LENGTH = Math.max.apply(null, Object.keys(PUNCTUATION)) + 1;
var SYMBOL_CATEGORY = new Uint32Array(SYMBOL_CATEGORY_LENGTH);
var IS_PUNCTUATOR = new Uint32Array(SYMBOL_CATEGORY_LENGTH);
function isDecimalDigit(c) {
return '0123456789'.indexOf(c) !== -1;
// fill categories
Object.keys(PUNCTUATION).forEach(function(key) {
SYMBOL_CATEGORY[Number(key)] = PUNCTUATOR;
IS_PUNCTUATOR[Number(key)] = PUNCTUATOR;
}, SYMBOL_CATEGORY);
// don't treat as punctuator
IS_PUNCTUATOR[UNDERSCORE] = 0;
for (var i = 48; i <= 57; i++) {
SYMBOL_CATEGORY[i] = DIGIT;
}
function tokenize(s) {
function pushToken(type, ln, column, value) {
SYMBOL_CATEGORY[SPACE] = WHITESPACE;
SYMBOL_CATEGORY[TAB] = WHITESPACE;
SYMBOL_CATEGORY[N] = WHITESPACE;
SYMBOL_CATEGORY[R] = WHITESPACE;
SYMBOL_CATEGORY[F] = WHITESPACE;
SYMBOL_CATEGORY[QUOTE] = STRING_SQ;
SYMBOL_CATEGORY[DOUBLE_QUOTE] = STRING_DQ;
//
// main part
//
function tokenize(source) {
function pushToken(type, line, column, value) {
tokens.push({

@@ -54,3 +102,3 @@ type: type,

offset: lastPos,
line: ln,
line: line,
column: column

@@ -62,3 +110,3 @@ });

if (!s) {
if (!source) {
return [];

@@ -69,55 +117,90 @@ }

var urlMode = false;
var lastPos = 0;
var blockMode = 0;
var code;
var next;
var ident;
// ignore first char if it is byte order marker (UTF-8 BOM)
pos = s.charCodeAt(0) === 0xFEFF ? 1 : 0;
var lastPos = pos;
ln = 1;
pos = source.charCodeAt(0) === 0xFEFF ? 1 : 0;
lastPos = pos;
line = 1;
lineStartPos = -1;
var blockMode = 0;
var c;
var cn;
var ident;
for (; pos < source.length; pos++) {
code = source.charCodeAt(pos);
for (; pos < s.length; pos++) {
c = s.charAt(pos);
cn = s.charAt(pos + 1);
switch (code < SYMBOL_CATEGORY_LENGTH ? SYMBOL_CATEGORY[code] : 0) {
case DIGIT:
pushToken(TokenType.DecimalNumber, line, pos - lineStartPos, parseDecimalNumber(source));
break;
if (c === '/' && cn === '*') {
pushToken(TokenType.CommentML, ln, pos - lineStartPos, parseMLComment(s));
} else if (!urlMode && c === '/' && cn === '/') {
if (blockMode > 0) {
pushToken(TokenType.Identifier, ln, pos - lineStartPos, ident = parseIdentifier(s));
case STRING_SQ:
case STRING_DQ:
pushToken(TokenType.String, line, pos - lineStartPos, parseString(source, code));
break;
case WHITESPACE:
pushToken(TokenType.Space, line, pos - lineStartPos, parseSpaces(source));
break;
case PUNCTUATOR:
if (code === SLASH) {
next = source.charCodeAt(pos + 1);
if (next === STAR) { // /*
pushToken(TokenType.Comment, line, pos - lineStartPos, parseComment(source));
continue;
} else if (next === SLASH && !urlMode) { // //
if (blockMode > 0) {
var skip = 2;
while (source.charCodeAt(pos + skip) === SLASH) {
skip++;
}
pushToken(TokenType.Identifier, line, pos - lineStartPos, ident = parseIdentifier(source, skip));
urlMode = urlMode || ident === 'url';
} else {
pushToken(TokenType.Unknown, line, pos - lineStartPos, parseUnknown(source));
}
continue;
}
}
pushToken(PUNCTUATION[code], line, pos - lineStartPos, String.fromCharCode(code));
if (code === RIGHT_PARENTHESIS) {
urlMode = false;
} else if (code === LEFT_CURLY_BRACE) {
blockMode++;
} else if (code === RIGHT_CURLY_BRACE) {
blockMode--;
}
break;
default:
pushToken(TokenType.Identifier, line, pos - lineStartPos, ident = parseIdentifier(source, 0));
urlMode = urlMode || ident === 'url';
} else {
pushToken(TokenType.CommentSL, ln, pos - lineStartPos, parseSLComment(s));
}
} else if (c === '"' || c === "'") {
pushToken(c === '"' ? TokenType.StringDQ : TokenType.StringSQ, ln, pos - lineStartPos, parseString(s, c));
} else if (c === ' ' || c === '\n' || c === '\r' || c === '\t' || c === '\f') {
pushToken(TokenType.Space, ln, pos - lineStartPos, parseSpaces(s));
} else if (c in Punctuation) {
pushToken(Punctuation[c], ln, pos - lineStartPos, c);
if (c === ')') {
urlMode = false;
}
if (c === '{') {
blockMode++;
}
if (c === '}') {
blockMode--;
}
} else if (isDecimalDigit(c)) {
pushToken(TokenType.DecimalNumber, ln, pos - lineStartPos, parseDecimalNumber(s));
} else {
pushToken(TokenType.Identifier, ln, pos - lineStartPos, ident = parseIdentifier(s));
urlMode = urlMode || ident === 'url';
}
}
mark(tokens);
return tokens;
}
function checkNewline(code, s) {
if (code === N || code === F || code === R) {
if (code === R && pos + 1 < s.length && s.charCodeAt(pos + 1) === N) {
pos++;
}
line++;
lineStartPos = pos;
return true;
}
return false;
}
function parseSpaces(s) {

@@ -127,15 +210,5 @@ var start = pos;

for (; pos < s.length; pos++) {
var c = s.charAt(pos);
// \n or \f
if (c === '\n' || c === '\f') {
ln++;
lineStartPos = pos;
// \r + optional \n
} else if (c === '\r') {
ln++;
if (s.charAt(pos + 1) === '\n') {
pos++;
}
lineStartPos = pos;
} else if (c !== ' ' && c !== '\t') {
var code = s.charCodeAt(pos);
if (!checkNewline(code, s) && code !== SPACE && code !== TAB) {
break;

@@ -149,16 +222,16 @@ }

function parseMLComment(s) {
function parseComment(s) {
var start = pos;
for (pos = pos + 2; pos < s.length; pos++) {
if (s.charAt(pos) === '*') {
if (s.charAt(pos + 1) === '/') {
for (pos += 2; pos < s.length; pos++) {
var code = s.charCodeAt(pos);
if (code === STAR) { // */
if (s.charCodeAt(pos + 1) === SLASH) {
pos++;
break;
}
} else {
checkNewline(code, s);
}
if (s.charAt(pos) === '\n') {
ln++;
lineStartPos = pos;
}
}

@@ -169,7 +242,7 @@

function parseSLComment(s) {
function parseUnknown(s) {
var start = pos;
for (pos = pos + 2; pos < s.length; pos++) {
if (s.charAt(pos) === '\n' || s.charAt(pos) === '\r') {
for (pos += 2; pos < s.length; pos++) {
if (checkNewline(s.charCodeAt(pos), s)) {
break;

@@ -182,26 +255,17 @@ }

function parseString(s, q) {
function parseString(s, quote) {
var start = pos;
var res = '';
for (pos = pos + 1; pos < s.length; pos++) {
if (s.charAt(pos) === '\\') {
var next = s.charAt(pos + 1);
// \n or \f
if (next === '\n' || next === '\f') {
res += s.substring(start, pos);
start = pos + 2;
pos++;
// \r + optional \n
} else if (next === '\r') {
res += s.substring(start, pos);
if (s.charAt(pos + 2) === '\n') {
pos++;
}
start = pos + 2;
pos++;
} else {
pos++;
for (pos++; pos < s.length; pos++) {
var code = s.charCodeAt(pos);
if (code === BACK_SLASH) {
var end = pos++;
if (checkNewline(s.charCodeAt(pos), s)) {
res += s.substring(start, end);
start = pos + 1;
}
} else if (s.charAt(pos) === q) {
} else if (code === quote) {
break;

@@ -216,5 +280,8 @@ }

var start = pos;
var code;
for (; pos < s.length; pos++) {
if (!isDecimalDigit(s.charAt(pos))) {
for (pos++; pos < s.length; pos++) {
code = s.charCodeAt(pos);
if (code < 48 || code > 57) { // 0 .. 9
break;

@@ -228,63 +295,43 @@ }

function parseIdentifier(s) {
function parseIdentifier(s, skip) {
var start = pos;
while (s.charAt(pos) === '/') {
pos++;
}
for (pos += skip; pos < s.length; pos++) {
var code = s.charCodeAt(pos);
for (; pos < s.length; pos++) {
var c = s.charAt(pos);
if (c === '\\') {
if (code === BACK_SLASH) {
pos++;
} else if (c in Punctuation && c !== '_') {
break;
}
}
pos--;
// skip escaped unicode sequence that can ends with space
// [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
for (var i = 0; i < 7 && pos + i < s.length; i++) {
code = s.charCodeAt(pos + i);
return s.substring(start, pos + 1);
}
if (i !== 6) {
if ((code >= 48 && code <= 57) || // 0 .. 9
(code >= 65 && code <= 70) || // A .. F
(code >= 97 && code <= 102)) { // a .. f
continue;
}
}
// ====================================
// second run
// ====================================
if (i > 0) {
pos += i - 1;
if (code === SPACE || code === TAB || checkNewline(code, s)) {
pos++;
}
}
function mark(tokens) {
var ps = []; // Parenthesis
var sbs = []; // SquareBracket
var cbs = []; // CurlyBracket
for (var i = 0, t; i < tokens.length; i++) {
t = tokens[i];
switch (t.type) {
case TokenType.LeftParenthesis:
ps.push(i);
break;
case TokenType.RightParenthesis:
if (ps.length) {
tokens[ps.pop()].right = i;
}
break;
case TokenType.LeftSquareBracket:
sbs.push(i);
break;
case TokenType.RightSquareBracket:
if (sbs.length) {
tokens[sbs.pop()].right = i;
}
break;
case TokenType.LeftCurlyBracket:
cbs.push(i);
break;
case TokenType.RightCurlyBracket:
if (cbs.length) {
tokens[cbs.pop()].right = i;
}
break;
}
} else if (code < SYMBOL_CATEGORY_LENGTH &&
IS_PUNCTUATOR[code] === PUNCTUATOR) {
break;
}
}
pos--;
return s.substring(start, pos + 1);
}
module.exports = tokenize;

@@ -15,2 +15,3 @@ var useInfo;

unknown: simple,
attribFlags: simple,

@@ -51,14 +52,5 @@ simpleselector: composite,

cdo: function() {
buffer.push('cdo');
},
cdc: function() {
buffer.push('cdc');
},
decldelim: function() {
buffer.push(';');
},
namespace: function() {
buffer.push('|');
},
delim: function() {

@@ -65,0 +57,0 @@ buffer.push(',');

{
"name": "csso",
"description": "CSSO — CSS optimizer",
"version": "1.5.4",
"version": "1.6.0",
"homepage": "https://github.com/css/csso",

@@ -23,4 +23,19 @@ "author": "Sergey Kryzhanovsky <skryzhanovsky@ya.ru> (https://github.com/afelix)",

"main": "./lib/index",
"eslintConfig": {
"env": {
"node": true,
"mocha": true,
"es6": true
},
"rules": {
"no-undef": 1,
"no-unused-vars": [1, {"vars": "all", "args": "after-used"}]
}
},
"scripts": {
"test": "jscs . && mocha --reporter dot",
"test": "jscs lib && eslint lib test && mocha --reporter dot",
"hydrogen": "node --trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces --redirect-code-traces-to=code.asm --trace_hydrogen_file=code.cfg --print-opt-code bin/csso --stat -o /dev/null",
"coverage": "istanbul cover _mocha -- -R dot",
"coveralls": "istanbul cover _mocha --report lcovonly -- -R dot && cat ./coverage/lcov.info | coveralls",
"travis": "npm run test && npm run coveralls",
"browserify": "browserify --standalone csso lib/index.js | uglifyjs --compress --mangle -o dist/csso-browser.js",

@@ -31,7 +46,11 @@ "gh-pages": "git clone -b gh-pages https://github.com/css/csso.git .gh-pages && npm run browserify && cp dist/csso-browser.js .gh-pages/ && cd .gh-pages && git commit -am \"update\" && git push && cd .. && rm -rf .gh-pages",

"dependencies": {
"clap": "^1.0.9"
"clap": "^1.0.9",
"source-map": "^0.5.3"
},
"devDependencies": {
"browserify": "^13.0.0",
"jscs": "~2.9.0",
"coveralls": "^2.11.6",
"eslint": "^2.2.0",
"istanbul": "^0.4.2",
"jscs": "~2.10.0",
"mocha": "~2.4.2",

@@ -38,0 +57,0 @@ "uglify-js": "^2.6.1"

[![NPM version](https://img.shields.io/npm/v/csso.svg)](https://www.npmjs.com/package/csso)
[![Build Status](https://travis-ci.org/css/csso.svg?branch=master)](https://travis-ci.org/css/csso)
[![Coverage Status](https://coveralls.io/repos/github/css/csso/badge.svg?branch=master)](https://coveralls.io/github/css/csso?branch=master)
[![Dependency Status](https://img.shields.io/david/css/csso.svg)](https://david-dm.org/css/csso)
[![devDependency Status](https://img.shields.io/david/dev/css/csso.svg?style=flat)](https://david-dm.org/css/csso#info=devDependencies)
[![devDependency Status](https://img.shields.io/david/dev/css/csso.svg)](https://david-dm.org/css/csso#info=devDependencies)

@@ -16,2 +17,8 @@ CSSO (CSS Optimizer) is a CSS minimizer unlike others. In addition to usual minification techniques it can perform structural optimization of CSS files, resulting in smaller file size compared to other minifiers.

### Runners
- Gulp: [gulp-csso](https://github.com/ben-eb/gulp-csso)
- Grunt: [grunt-csso](https://github.com/t32k/grunt-csso)
- Broccoli: [broccoli-csso](https://github.com/sindresorhus/broccoli-csso)
### Command line

@@ -24,9 +31,11 @@

--debug [level] Output intermediate state of CSS during compression
-h, --help Output usage information
-i, --input <filename> Input file
-o, --output <filename> Output file (result outputs to stdout if not set)
--restructure-off Turns structure minimization off
--stat Output statistics in stderr
-v, --version Output version
--debug [level] Output intermediate state of CSS during compression
-h, --help Output usage information
-i, --input <filename> Input file
--input-map <source> Input source map. Possible values: none, auto (default) or <filename>
-m, --map <destination> Generate source map. Possible values: none (default), inline, file or <filename>
-o, --output <filename> Output file (result outputs to stdout if not set)
--restructure-off Turns structure minimization off
--stat Output statistics in stderr
-v, --version Output version
```

@@ -48,4 +57,2 @@

Debug and statistics:
```

@@ -61,3 +68,88 @@ > echo '.test { color: #ff0000 }' | node bin/csso --stat >/dev/null

### Source maps
Source map doesn't generate by default. To generate map use `--map` CLI option, that can be:
- `none` (default) – don't generate source map
- `inline` – generate map add it into result content (via `/*# sourceMappingURL=application/json;base64,...base64 encoded map... */`)
- `file` – generate map and write it into file with same name as output file, but with `.map` extension; in this case `--output` option is required
- any other values treat as filename for generated source map
Examples:
```
> csso my.css --map inline
> csso my.css --map file --output my.min.css
> csso my.css -o my.min.css -m maps/my.min.map
```
Input can has a source map. Use `--input-map` option to specify input source if needed. Possible values for option:
- `auto` (auto) - attempt to fetch input source map by follow steps:
- try to fetch inline map from source
- try to fetch map filename from source and read its content
- (when `--input` is specified) check for file with same name as input but with `.map` extension exists and read its content
- `none` - don't use input source map; actually it's using to disable `auto`-fetching
- any other values as filename for input source map
> NOTE: Input source map is using only if source map is generating.
### API
```js
var csso = require('csso');
var compressedCss = csso.minify('.test { color: #ff0000; }');
console.log(compressedCss);
// .test{color:red}
// there are some options you can pass
var compressedWithOptions = csso.minify('.test { color: #ff0000; }', {
restructuring: false, // don't change css structure, i.e. don't merge declarations, rulesets etc
debug: true // show additional debug information:
// true or number from 1 to 3 (greater number - more details)
});
// you may do it step by step
var ast = csso.parse('.test { color: #ff0000; }');
var compressedAst = csso.compress(ast);
var compressedCss = csso.translate(compressedAst, true);
console.log(compressedCss);
// .test{color:red}
```
Working with source maps:
```js
var css = fs.readFileSync('path/to/my.css', 'utf8');
var result = csso.minify(css, {
filename: 'path/to/my.css', // will be added to source map as reference to file
sourceMap: true // generate source map
});
console.log(result);
// { css: '...minified...', map: SourceMapGenerator {} }
console.log(result.map.toString());
// '{ .. source map content .. }'
// apply input source map
var SourceMapConsumer = require('source-map').SourceMapConsumer;
var inputSourceMap = fs.readFileSync('path/to/my.map.css', 'utf8');
result.map.applySourceMap(
new SourceMapConsumer(inputSourceMap),
'path/to/my.css' // should be the same as passed to csso.minify()
);
// if no input source map you may add source content
result.map.setContent('path/to/my.css', setSourceContent);
```
### Debugging
```
> echo '.test { color: green; color: #ff0000 } .foo { color: red }' | node bin/csso --debug

@@ -85,2 +177,4 @@ ## parsing done in 10 ms

More details are provided when `--debug` flag has a number greater than `1`:
```

@@ -116,33 +210,4 @@ > echo '.test { color: green; color: #ff0000 } .foo { color: red }' | node bin/csso --debug 2

### API
## License
```js
var csso = require('csso');
var compressed = csso.minify('.test { color: #ff0000; }');
console.log(compressed);
// .test{color:red}
// there are some options you can pass
var compressedWithOptions = csso.minify('.test { color: #ff0000; }', {
restructuring: false, // don't change css structure, i.e. don't merge declarations, rulesets etc
debug: true // show additional debug information:
// true or number from 1 to 3 (greater number - more details)
});
// you may do it step by step
var ast = csso.parse('.test { color: #ff0000; }');
ast = csso.compress(ast);
var compressed = csso.translate(ast, true);
console.log(compressed);
// .test{color:red}
```
## Documentation
> May be outdated
- [English](https://github.com/css/csso/blob/master/docs/index/index.en.md)
- [Русский](https://github.com/css/csso/blob/master/docs/index/index.ru.md)
- [日本語](https://github.com/css/csso/blob/master/docs/index/index.ja.md)
- [한국어](https://github.com/css/csso/blob/master/docs/index/index.ko.md)
MIT

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc