Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
rauchg committed Oct 8, 2011
0 parents commit 9033070
Show file tree
Hide file tree
Showing 36 changed files with 790 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules
3 changes: 3 additions & 0 deletions .npmignore
@@ -0,0 +1,3 @@
test/
support/
README.md
8 changes: 8 additions & 0 deletions Makefile
@@ -0,0 +1,8 @@

test:
@./node_modules/.bin/expresso \
-t 3000 \
--serial \
test/juice.test.js

.PHONY: test
12 changes: 12 additions & 0 deletions README.md
@@ -0,0 +1,12 @@

# Juice

Given HTML and CSS, juice will inline your properties into the `style`
attribute.

## How to use

```js
juice('<p>Test</p>', 'p { color: red; }')
// '<p style="color: red;">Test</p>'
```
2 changes: 2 additions & 0 deletions index.js
@@ -0,0 +1,2 @@

module.exports = require('./lib/juice');
140 changes: 140 additions & 0 deletions lib/juice.js
@@ -0,0 +1,140 @@

/**
* juice
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/

module.exports = exports = juice;

/**
* Module dependencies.
*/

var soupselect = require('soupselect')
, utils = require('./utils')
, Selector = require('./selector')
, Property = require('./property')

/**
* Export Selector.
*/

exports.Selector = Selector;

/**
* Export Property.
*/

exports.Property = Property;

/**
* Export utils.
*/

exports.utils = require('./utils');

/**
* Inlines the CSS specified by `css` into the `html`
*
* @param {String} html
* @param {String} css
* @api public
*/

function juice (html, css, options) {
var rules = utils.parseCSS(css)
, dom = utils.parseHTML(html)
, editedElements = []
, topmost = new Selector('<style attribute>', [1, 0, 0, 0])

function select (sel) {
return soupselect.select(dom, sel);
}

rules.forEach(function (rule) {
var sel = rule[0]
, style = rule[1]
, matches = select(sel)
, selector

console.log('\ngot selector "%s" - matches: %s ', sel, matches.length);
matches.forEach(function (el) {
// we initialize the Selector lazily to avoid needless parsing
if (!selector) {
selector = new Selector(sel)
console.log(
'selector "%s" has specificity "%s"'
, sel
, JSON.stringify(selector.specificity())
);
}

if (!el.styleProps) {
el.styleProps = {}

// if the element has inline styles, fake selector with topmost specificity
if (el.attribs && el.attribs.style) {
console.log('element has inline style - caching properties');
addProps(
utils.parseCSS(el.attribs.style)
, topmost
);
}

// store reference to an element we need to compile style="" attr for
editedElements.push(el);
}

// go through the properties
function addProps (style, selector) {
for (var i = 0, l = style.length; i < l; i++) {
var name = style[i]
, value = style[name]
, sel = style._importants[name]
? new Selector('!important', [2,0,0,0])
: selector
, prop = new Property(name, value, sel)
, existing = el.styleProps[name]

if (existing) {
var winner = existing.compare(prop)
, loser = prop == winner ? existing : prop

console.log(
' - already existing from selector %s, selector %s beats %s'
, name
, existing.selector.text
, winner.text
, loser.text
);

if (winner == prop) {
console.log(' + new value "%s"', value);
}
} else {
el.styleProps[name] = prop;
console.log(' - property "%s" added with value "%s"', name, value);
}
}
}

addProps(style, selector);
});
});

console.log('elements affected "%s"', editedElements.length);
editedElements.forEach(function (el) {
var style = '';

for (var i in el.styleProps) {
style += el.styleProps[i].toString();
}

if (!el.attribs) el.attribs = {};

el.attribs.style = style;
});

return utils.domToHTML(dom);
}
54 changes: 54 additions & 0 deletions lib/property.js
@@ -0,0 +1,54 @@

/**
* juice
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/

module.exports = exports = Property;

/**
* Module dependencies.
*/

var compare = require('./utils').compare

/**
* CSS property constructor.
*
* @param {String} property
* @param {String} value
* @param {Selector} selector the property originates from
* @api public
*/

function Property (prop, value, selector) {
this.prop = prop;
this.value = value;
this.selector = selector
}

/**
* Compares with another Property based on Selector#specificity.
*
* @api public
*/

Property.prototype.compare = function (property) {
var a = this.selector.specificity()
, b = property.selector.specificity()
, winner = compare(a, b)

if (winner == a) return this;
return property;
};

/**
* Returns CSS property
*
* @api public
*/

Property.prototype.toString = function () {
return this.prop + ': ' + this.value + ';';
};
59 changes: 59 additions & 0 deletions lib/selector.js
@@ -0,0 +1,59 @@

/**
* Module dependencies.
*/

var parse = require('mootools-slick-parser').Slick.parse

/**
* Module exports.
*/

module.exports = exports = Selector;

/**
* CSS selector constructor.
*
* @param {String} selector text
* @param {Array} optionally, precalculated specificity
* @api public
*/

function Selector (text, spec) {
this.text = text;
this.spec = spec;
}

/**
* Lazy specificity getter
*
* @api public
*/

Selector.prototype.specificity = function () {

This comment has been minimized.

Copy link
@arian

arian Oct 9, 2011

Contributor

Hey, for MooTools/Slick I made a bit more complete implementation (also using Slick.parse): https://github.com/arian/DOM/blob/matcher-specificity/Source/specificity.js#L8-33

This comment has been minimized.

Copy link
@rauchg

rauchg Oct 9, 2011

Author Contributor

Nice! I was looking for it but couldn't find it, Thomas told me you had one =]

This comment has been minimized.

Copy link
@rauchg

rauchg Oct 9, 2011

Author Contributor

Pull requests are welcome if you're down

if (this.spec) return this.spec;

this.parsed = parse(this.text).expressions[0];
this.spec = [0, 0, 0, 0];

for (var i = 0, l = this.parsed.length; i < l; i++) {
var token = this.parsed[i];

// id awards a point in the second column
if (undefined != token.id) this.spec[1]++;

// classes award a point each in the third column
if (token.classes) this.spec[2] += token.classes.length;

// attributes award a point each in the third column
if (token.attributes) this.spec[2] += token.attributes.length;

// pseudos award a point each in the third column
if (token.pseudos) this.spec[2] += token.pseudos.length;

// tag awards a point in the fourth column
if ('*' != token.tag) this.spec[3]++;
}

return this.spec;
}

0 comments on commit 9033070

Please sign in to comment.