diff --git a/client.js b/client.js index f8e7aed..2a5e0bc 100644 --- a/client.js +++ b/client.js @@ -150,15 +150,19 @@ function inlineDocument($, css, options) { // go through the properties function addProps(style, selector) { for (var i = 0, l = style.length; i < l; i++) { - var name = style[i]; - var value = style[name] + (options.preserveImportant && style._importants[name] ? ' !important' : ''); - var prop = new utils.Property(name, value, selector, style._importants[name] ? 2 : 0); - var existing = el.styleProps[name]; - - // if property name is not in the excluded properties array - if (juiceClient.excludedProperties.indexOf(name) < 0) { - if (existing && existing.compare(prop) === prop || !existing) { - el.styleProps[name] = prop; + if (style[i].type == 'property') { + var name = style[i].name; + var value = style[i].value; + var important = style[i].value.match(/!important$/) !== null; + if (important && !options.preserveImportant) value = value.replace(/\s*!important$/, ''); + var prop = new utils.Property(name, value, selector, important ? 2 : 0); + var existing = el.styleProps[name]; + + // if property name is not in the excluded properties array + if (juiceClient.excludedProperties.indexOf(name) < 0) { + if (existing && existing.compare(prop) === prop || !existing) { + el.styleProps[name] = prop; + } } } } diff --git a/lib/utils.js b/lib/utils.js index 4f502c4..14eadc5 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,7 +4,7 @@ * Module dependencies. */ -var cssom = require('cssom'); +var mensch = require('mensch'); var cheerio = require('cheerio'); var own = {}.hasOwnProperty; var os = require('os'); @@ -61,16 +61,17 @@ exports.extract = function extract(selectorText) { */ exports.parseCSS = function(css) { - var rules = cssom.parse(css).cssRules || []; + var parsed = mensch.parse(css); + var rules = typeof parsed.stylesheet != 'undefined' && parsed.stylesheet.rules ? parsed.stylesheet.rules : []; var ret = []; for (var i = 0, l = rules.length; i < l; i++) { - if (rules[i].selectorText) { // media queries don't have selectorText + if (rules[i].type == 'rule') { var rule = rules[i]; - var selectors = exports.extract(rule.selectorText); + var selectors = rule.selectors; for (var ii = 0, ll = selectors.length; ii < ll; ii++) { - ret.push([selectors[ii], rule.style]); + ret.push([selectors[ii], rule.declarations]); } } } @@ -78,16 +79,18 @@ exports.parseCSS = function(css) { return ret; }; - -var getStringifiedStyles = function(rule) { - var styles = []; - for (var style = 0; style < rule.style.length; style++) { - var property = rule.style[style]; - var value = rule.style[property]; - var important = rule.style._importants[property] ? ' !important' : ''; - styles.push(' ' + property + ': ' + value + important + ';'); - } - return styles; +var removeStyle = function(style, startPos, endPos, skipRows, startOffset, endOffset, insert) { + var styleRows = style.split("\n"); + var start = startOffset; + var end = endOffset; + for (var r = 1 + skipRows; r < startPos.line; r++) start += styleRows[r - 1 - skipRows].length + 1; + start += startPos.col; + if (endPos !== null) { + for (var r2 = 1 + skipRows; r2 < endPos.line; r2++) end += styleRows[r2 - 1 - skipRows].length + 1; + end += endPos.col; + } else end += style.length + 1; + var newStyle = style.substr(0, start - 1) + insert + style.substr(end - 1); + return newStyle; }; /** @@ -99,52 +102,35 @@ var getStringifiedStyles = function(rule) { */ exports.getPreservedText = function(css, options) { - var rules = cssom.parse(css).cssRules || []; - var preserved = []; - - for (var i = 0, l = rules.length; i < l; i++) { - /* CSS types - STYLE: 1, - IMPORT: 3, - MEDIA: 4, - FONT_FACE: 5, - */ - - if (options.fontFaces && rules[i].type === cssom.CSSFontFaceRule.prototype.type) { - var fontFace = [ '' ]; - fontFace.push('@font-face {'); - fontFace = fontFace.concat(getStringifiedStyles(rules[i])); - fontFace.push('}'); - - if (fontFace.length) { - preserved.push(fontFace.length ? fontFace.join(os.EOL) + os.EOL : ''); - } - } - - if (options.mediaQueries && rules[i].type === cssom.CSSMediaRule.prototype.type) { - var query = rules[i]; - var queryString = []; - - queryString.push(os.EOL + '@media ' + query.media[0] + ' {'); - - for (var ii = 0, ll = query.cssRules.length; ii < ll; ii++) { - var rule = query.cssRules[ii]; - - if (rule.type === cssom.CSSStyleRule.prototype.type - || rule.type === cssom.CSSFontFaceRule.prototype.type) { - queryString.push(' ' - + (rule.type === cssom.CSSStyleRule.prototype.type ? rule.selectorText : '@font-face') + ' {'); - queryString = queryString.concat(getStringifiedStyles(rule)); - queryString.push(' }'); - } - } - - queryString.push('}'); - preserved.push(queryString.length ? queryString.join(os.EOL) + os.EOL : ''); + var parsed = mensch.parse(css, {position: true, comments: true}); + var rules = typeof parsed.stylesheet != 'undefined' && parsed.stylesheet.rules ? parsed.stylesheet.rules : []; + var preserved = css; + var preserved2 = []; + var lastStart = null; + + for (var i = rules.length - 1; i >= 0; i--) { + if (options.fontFaces && rules[i].type === 'font-face') { + // preserve + preserved2.push(mensch.stringify({ stylesheet: { rules: [ rules[i] ] }}, { comments: false, indentation: ' ' }).replace(/}/,os.EOL+'}')); + } else if (options.mediaQueries && rules[i].type === 'media') { + // preserve + preserved2.push(mensch.stringify({ stylesheet: { rules: [ rules[i] ] }}, { comments: false, indentation: ' ' })); + } else { + // remove + preserved = removeStyle(preserved, rules[i].position.start, lastStart, 0, 0, 0, ''); + // TODO +os.EOL? } + lastStart = rules[i].position.start; + } + // preserved works by removing unwanted stuff, preserved2 by generating a new style with "stuff to keep" + // We have to decide what is our strategy (the preserved2 produces the same output of the cssom based version <= 2.0.0) + if (false) { + if (preserved.trim().length === 0) return false; + return preserved; + } else { + if (preserved2.length === 0) return false; + return os.EOL+preserved2.join(os.EOL)+os.EOL; } - - return preserved.join(os.EOL); }; /** @@ -160,7 +146,8 @@ exports.cheerio = function(html, options) { }; exports.normalizeLineEndings = function(text) { - return text.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); + // NOTE this consider multiple newlines the same as a single newline + return text.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n').replace(/\r\n\r\n/g, '\r\n'); }; exports.encodeEJS = function(html) { diff --git a/package.json b/package.json index f9c0801..4b3a03e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "cheerio": "0.20.0", "commander": "2.9.0", "cross-spawn-async": "^2.1.8", - "cssom": "0.3.1", + "mensch": "brettstimmerman/mensch^0.3.2", "deep-extend": "^0.4.0", "slick": "1.12.2", "web-resource-inliner": "2.0.0" diff --git a/test/juice.test.js b/test/juice.test.js index 4ce1443..548131e 100644 --- a/test/juice.test.js +++ b/test/juice.test.js @@ -101,14 +101,13 @@ it('parse simple css into a object structure', function() { var parse = utils.parseCSS; var actual = parse('a, b { c: e; }'); + var a = actual[0]; var b = actual[1]; assert.equal(a[0],'a'); - assert.equal(a[1]['0'],'c'); + assert.deepEqual(a[1]['0'],{ type: 'property', name: 'c', value: 'e' }); assert.equal(a[1].length,1); - assert.deepEqual(a[1]._importants, { c: '' }); - assert.equal(a[1].c,'e'); assert.deepEqual(a[1],b[1]); });