Skip to content

Commit 9033070

Browse files
committed
Initial import
0 parents  commit 9033070

36 files changed

+790
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

.npmignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
test/
2+
support/
3+
README.md

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
test:
3+
@./node_modules/.bin/expresso \
4+
-t 3000 \
5+
--serial \
6+
test/juice.test.js
7+
8+
.PHONY: test

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
# Juice
3+
4+
Given HTML and CSS, juice will inline your properties into the `style`
5+
attribute.
6+
7+
## How to use
8+
9+
```js
10+
juice('<p>Test</p>', 'p { color: red; }')
11+
// '<p style="color: red;">Test</p>'
12+
```

index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
module.exports = require('./lib/juice');

lib/juice.js

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
2+
/**
3+
* juice
4+
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
5+
* MIT Licensed
6+
*/
7+
8+
module.exports = exports = juice;
9+
10+
/**
11+
* Module dependencies.
12+
*/
13+
14+
var soupselect = require('soupselect')
15+
, utils = require('./utils')
16+
, Selector = require('./selector')
17+
, Property = require('./property')
18+
19+
/**
20+
* Export Selector.
21+
*/
22+
23+
exports.Selector = Selector;
24+
25+
/**
26+
* Export Property.
27+
*/
28+
29+
exports.Property = Property;
30+
31+
/**
32+
* Export utils.
33+
*/
34+
35+
exports.utils = require('./utils');
36+
37+
/**
38+
* Inlines the CSS specified by `css` into the `html`
39+
*
40+
* @param {String} html
41+
* @param {String} css
42+
* @api public
43+
*/
44+
45+
function juice (html, css, options) {
46+
var rules = utils.parseCSS(css)
47+
, dom = utils.parseHTML(html)
48+
, editedElements = []
49+
, topmost = new Selector('<style attribute>', [1, 0, 0, 0])
50+
51+
function select (sel) {
52+
return soupselect.select(dom, sel);
53+
}
54+
55+
rules.forEach(function (rule) {
56+
var sel = rule[0]
57+
, style = rule[1]
58+
, matches = select(sel)
59+
, selector
60+
61+
console.log('\ngot selector "%s" - matches: %s ', sel, matches.length);
62+
matches.forEach(function (el) {
63+
// we initialize the Selector lazily to avoid needless parsing
64+
if (!selector) {
65+
selector = new Selector(sel)
66+
console.log(
67+
'selector "%s" has specificity "%s"'
68+
, sel
69+
, JSON.stringify(selector.specificity())
70+
);
71+
}
72+
73+
if (!el.styleProps) {
74+
el.styleProps = {}
75+
76+
// if the element has inline styles, fake selector with topmost specificity
77+
if (el.attribs && el.attribs.style) {
78+
console.log('element has inline style - caching properties');
79+
addProps(
80+
utils.parseCSS(el.attribs.style)
81+
, topmost
82+
);
83+
}
84+
85+
// store reference to an element we need to compile style="" attr for
86+
editedElements.push(el);
87+
}
88+
89+
// go through the properties
90+
function addProps (style, selector) {
91+
for (var i = 0, l = style.length; i < l; i++) {
92+
var name = style[i]
93+
, value = style[name]
94+
, sel = style._importants[name]
95+
? new Selector('!important', [2,0,0,0])
96+
: selector
97+
, prop = new Property(name, value, sel)
98+
, existing = el.styleProps[name]
99+
100+
if (existing) {
101+
var winner = existing.compare(prop)
102+
, loser = prop == winner ? existing : prop
103+
104+
console.log(
105+
' - already existing from selector %s, selector %s beats %s'
106+
, name
107+
, existing.selector.text
108+
, winner.text
109+
, loser.text
110+
);
111+
112+
if (winner == prop) {
113+
console.log(' + new value "%s"', value);
114+
}
115+
} else {
116+
el.styleProps[name] = prop;
117+
console.log(' - property "%s" added with value "%s"', name, value);
118+
}
119+
}
120+
}
121+
122+
addProps(style, selector);
123+
});
124+
});
125+
126+
console.log('elements affected "%s"', editedElements.length);
127+
editedElements.forEach(function (el) {
128+
var style = '';
129+
130+
for (var i in el.styleProps) {
131+
style += el.styleProps[i].toString();
132+
}
133+
134+
if (!el.attribs) el.attribs = {};
135+
136+
el.attribs.style = style;
137+
});
138+
139+
return utils.domToHTML(dom);
140+
}

lib/property.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
/**
3+
* juice
4+
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
5+
* MIT Licensed
6+
*/
7+
8+
module.exports = exports = Property;
9+
10+
/**
11+
* Module dependencies.
12+
*/
13+
14+
var compare = require('./utils').compare
15+
16+
/**
17+
* CSS property constructor.
18+
*
19+
* @param {String} property
20+
* @param {String} value
21+
* @param {Selector} selector the property originates from
22+
* @api public
23+
*/
24+
25+
function Property (prop, value, selector) {
26+
this.prop = prop;
27+
this.value = value;
28+
this.selector = selector
29+
}
30+
31+
/**
32+
* Compares with another Property based on Selector#specificity.
33+
*
34+
* @api public
35+
*/
36+
37+
Property.prototype.compare = function (property) {
38+
var a = this.selector.specificity()
39+
, b = property.selector.specificity()
40+
, winner = compare(a, b)
41+
42+
if (winner == a) return this;
43+
return property;
44+
};
45+
46+
/**
47+
* Returns CSS property
48+
*
49+
* @api public
50+
*/
51+
52+
Property.prototype.toString = function () {
53+
return this.prop + ': ' + this.value + ';';
54+
};

lib/selector.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
/**
3+
* Module dependencies.
4+
*/
5+
6+
var parse = require('mootools-slick-parser').Slick.parse
7+
8+
/**
9+
* Module exports.
10+
*/
11+
12+
module.exports = exports = Selector;
13+
14+
/**
15+
* CSS selector constructor.
16+
*
17+
* @param {String} selector text
18+
* @param {Array} optionally, precalculated specificity
19+
* @api public
20+
*/
21+
22+
function Selector (text, spec) {
23+
this.text = text;
24+
this.spec = spec;
25+
}
26+
27+
/**
28+
* Lazy specificity getter
29+
*
30+
* @api public
31+
*/
32+
33+
Selector.prototype.specificity = function () {
34+
if (this.spec) return this.spec;
35+
36+
this.parsed = parse(this.text).expressions[0];
37+
this.spec = [0, 0, 0, 0];
38+
39+
for (var i = 0, l = this.parsed.length; i < l; i++) {
40+
var token = this.parsed[i];
41+
42+
// id awards a point in the second column
43+
if (undefined != token.id) this.spec[1]++;
44+
45+
// classes award a point each in the third column
46+
if (token.classes) this.spec[2] += token.classes.length;
47+
48+
// attributes award a point each in the third column
49+
if (token.attributes) this.spec[2] += token.attributes.length;
50+
51+
// pseudos award a point each in the third column
52+
if (token.pseudos) this.spec[2] += token.pseudos.length;
53+
54+
// tag awards a point in the fourth column
55+
if ('*' != token.tag) this.spec[3]++;
56+
}
57+
58+
return this.spec;
59+
}

0 commit comments

Comments
 (0)