Comparing version 1.5.4 to 1.6.0
@@ -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 @@ }); |
@@ -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" |
147
README.md
[![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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
295618
52
6073
208
2
7
1
+ Addedsource-map@^0.5.3
+ Addedsource-map@0.5.7(transitive)