fix
This commit is contained in:
72
book/node_modules/juice/lib/cheerio.js
generated
vendored
Normal file
72
book/node_modules/juice/lib/cheerio.js
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var cheerio = require('cheerio');
|
||||
var utils = require('./utils');
|
||||
|
||||
var cheerioLoad = function(html, options, encodeEntities) {
|
||||
options = Object.assign({decodeEntities: false, _useHtmlParser2:true}, options);
|
||||
html = encodeEntities(html);
|
||||
return cheerio.load(html, options);
|
||||
};
|
||||
|
||||
var createEntityConverters = function () {
|
||||
var codeBlockLookup = [];
|
||||
|
||||
var encodeCodeBlocks = function(html) {
|
||||
var blocks = module.exports.codeBlocks;
|
||||
Object.keys(blocks).forEach(function(key) {
|
||||
var re = new RegExp(blocks[key].start + '([\\S\\s]*?)' + blocks[key].end, 'g');
|
||||
html = html.replace(re, function(match, subMatch) {
|
||||
codeBlockLookup.push(match);
|
||||
return 'JUICE_CODE_BLOCK_' + (codeBlockLookup.length - 1) + '_';
|
||||
});
|
||||
});
|
||||
return html;
|
||||
};
|
||||
|
||||
var decodeCodeBlocks = function(html) {
|
||||
for(var index = 0; index < codeBlockLookup.length; index++) {
|
||||
var re = new RegExp('JUICE_CODE_BLOCK_' + index + '_(="")?', 'gi');
|
||||
html = html.replace(re, function() {
|
||||
return codeBlockLookup[index];
|
||||
});
|
||||
}
|
||||
return html;
|
||||
};
|
||||
|
||||
return {
|
||||
encodeEntities: encodeCodeBlocks,
|
||||
decodeEntities: decodeCodeBlocks,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the input, calls the callback on the parsed DOM, and generates the output
|
||||
*
|
||||
* @param {String} html input html to be processed
|
||||
* @param {Object} options for the parser
|
||||
* @param {Function} callback to be invoked on the DOM
|
||||
* @param {Array} callbackExtraArguments to be passed to the callback
|
||||
* @return {String} resulting html
|
||||
*/
|
||||
module.exports = function(html, options, callback, callbackExtraArguments) {
|
||||
var entityConverters = createEntityConverters();
|
||||
|
||||
var $ = cheerioLoad(html, options, entityConverters.encodeEntities);
|
||||
var args = [ $ ];
|
||||
args.push.apply(args, callbackExtraArguments);
|
||||
var doc = callback.apply(undefined, args) || $;
|
||||
|
||||
if (options && options.xmlMode) {
|
||||
return entityConverters.decodeEntities(doc.xml());
|
||||
}
|
||||
return entityConverters.decodeEntities(doc.html());
|
||||
};
|
||||
|
||||
module.exports.codeBlocks = {
|
||||
EJS: { start: '<%', end: '%>' },
|
||||
HBS: { start: '{{', end: '}}' }
|
||||
};
|
138
book/node_modules/juice/lib/cli.js
generated
vendored
Normal file
138
book/node_modules/juice/lib/cli.js
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
'use strict';
|
||||
|
||||
var { program } = require('commander');
|
||||
var pkg = require('../package');
|
||||
|
||||
var cli = {};
|
||||
|
||||
module.exports = cli;
|
||||
|
||||
cli.getProgram = function() {
|
||||
program.name = pkg.name;
|
||||
|
||||
program.version(pkg.version)
|
||||
.usage('[options] input.html output.html');
|
||||
|
||||
Object.keys(cli.options).forEach(function(key) {
|
||||
program.option('--' + key + ' [value]', cli.options[key].def);
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
return program;
|
||||
};
|
||||
|
||||
cli.options = {
|
||||
'css': {
|
||||
pMap: 'css',
|
||||
map: 'cssFile',
|
||||
def: 'Add an extra CSS file by name' },
|
||||
'options-file': {
|
||||
pMap: 'optionsFile',
|
||||
def: 'Load options from a JSON file' },
|
||||
'extra-css': {
|
||||
pMap: 'extraCss',
|
||||
def: 'Add extra CSS' },
|
||||
'insert-preserved-extra-css': {
|
||||
pMap: 'insertPreservedExtraCss',
|
||||
def: 'insert preserved @font-face and @media into document?',
|
||||
coercion: JSON.parse },
|
||||
'apply-style-tags': {
|
||||
pMap: 'applyStyleTags',
|
||||
def: 'inline from style tags?',
|
||||
coercion: JSON.parse },
|
||||
'remove-style-tags': {
|
||||
pMap: 'removeStyleTags',
|
||||
def: 'remove style tags?',
|
||||
coercion: JSON.parse },
|
||||
'preserve-important': {
|
||||
pMap: 'preserveImportant',
|
||||
def: 'preserve important?',
|
||||
coercion: JSON.parse },
|
||||
'preserve-media-queries': {
|
||||
pMap: 'preserveMediaQueries',
|
||||
def: 'preserve media queries?',
|
||||
coercion: JSON.parse },
|
||||
'preserve-font-faces': {
|
||||
pMap: 'preserveFontFaces',
|
||||
def: 'preserve font faces?',
|
||||
coercion: JSON.parse },
|
||||
'preserve-key-frames': {
|
||||
pMap: 'preserveKeyFrames',
|
||||
def: 'preserve key frames?',
|
||||
coercion: JSON.parse },
|
||||
'preserve-pseudos': {
|
||||
pMap: 'preservePseudos',
|
||||
def: 'preserve pseudo selectors?',
|
||||
coercion: JSON.parse },
|
||||
'apply-width-attributes': {
|
||||
pMap: 'applyWidthAttributes',
|
||||
def: 'apply width attributes to relevent elements?',
|
||||
coercion: JSON.parse },
|
||||
'apply-height-attributes': {
|
||||
pMap: 'applyHeightAttributes',
|
||||
def: 'apply height attributes to relevent elements?',
|
||||
coercion: JSON.parse },
|
||||
'apply-attributes-table-elements': {
|
||||
pMap: 'applyAttributesTableElements',
|
||||
def: 'apply attributes with and equivalent CSS value to table elements?',
|
||||
coercion: JSON.parse },
|
||||
'xml-mode': {
|
||||
pMap: 'xmlMode',
|
||||
def: 'generate output with tags closed? input must be valid XML',
|
||||
coercion: JSON.parse },
|
||||
'web-resources-inline-attribute': {
|
||||
pMap: 'webResourcesInlineAttribute',
|
||||
map: 'inlineAttribute',
|
||||
def: 'see docs for web-resource-inliner inlineAttribute',
|
||||
coercion: JSON.parse },
|
||||
'web-resources-images': {
|
||||
pMap: 'webResourcesImages',
|
||||
map: 'images',
|
||||
def: 'see docs for web-resource-inliner images',
|
||||
coercion: JSON.parse },
|
||||
'web-resources-links': {
|
||||
pMap: 'webResourcesLinks',
|
||||
map: 'links',
|
||||
def: 'see docs for web-resource-inliner links',
|
||||
coercion: JSON.parse },
|
||||
'web-resources-scripts': {
|
||||
pMap: 'webResourcesScripts',
|
||||
map: 'scripts',
|
||||
def: 'see docs for web-resource-inliner scripts',
|
||||
coercion: JSON.parse },
|
||||
'web-resources-relative-to': {
|
||||
pMap: 'webResourcesRelativeTo',
|
||||
map: 'relativeTo',
|
||||
def: 'see docs for web-resource-inliner relativeTo' },
|
||||
'web-resources-rebase-relative-to': {
|
||||
pMap: 'webResourcesRebaseRelativeTo',
|
||||
map: 'rebaseRelativeTo',
|
||||
def: 'see docs for web-resource-inliner rebaseRelativeTo' },
|
||||
'web-resources-strict': {
|
||||
pMap: 'webResourcesStrict',
|
||||
map: 'strict',
|
||||
def: 'see docs for web-resource-inliner strict',
|
||||
coercion: JSON.parse }
|
||||
};
|
||||
|
||||
cli.argsToOptions = function(program) {
|
||||
var result = { webResources: {} };
|
||||
Object.keys(cli.options).forEach(function(key) {
|
||||
var option = cli.options[key];
|
||||
var value = program[option.pMap];
|
||||
if (value !== undefined) {
|
||||
if (option.coercion) {
|
||||
value = option.coercion(value);
|
||||
}
|
||||
|
||||
if (option.pMap.match(/webResources/)) {
|
||||
result.webResources[option.map] = value;
|
||||
} else {
|
||||
result[option.map || option.pMap] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
514
book/node_modules/juice/lib/inline.js
generated
vendored
Normal file
514
book/node_modules/juice/lib/inline.js
generated
vendored
Normal file
@ -0,0 +1,514 @@
|
||||
'use strict';
|
||||
|
||||
var utils = require('./utils');
|
||||
var numbers = require('./numbers');
|
||||
|
||||
module.exports = function makeJuiceClient(juiceClient) {
|
||||
|
||||
juiceClient.ignoredPseudos = ['hover', 'active', 'focus', 'visited', 'link'];
|
||||
juiceClient.widthElements = ['TABLE', 'TD', 'TH', 'IMG'];
|
||||
juiceClient.heightElements = ['TABLE', 'TD', 'TH', 'IMG'];
|
||||
juiceClient.tableElements = ['TABLE', 'TH', 'TR', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'THEAD', 'TBODY', 'TFOOT'];
|
||||
juiceClient.nonVisualElements = [ 'HEAD', 'TITLE', 'BASE', 'LINK', 'STYLE', 'META', 'SCRIPT', 'NOSCRIPT' ];
|
||||
juiceClient.styleToAttribute = {
|
||||
'background-color': 'bgcolor',
|
||||
'background-image': 'background',
|
||||
'text-align': 'align',
|
||||
'vertical-align': 'valign'
|
||||
};
|
||||
juiceClient.excludedProperties = [];
|
||||
|
||||
juiceClient.juiceDocument = juiceDocument;
|
||||
juiceClient.inlineDocument = inlineDocument;
|
||||
|
||||
function inlineDocument($, css, options) {
|
||||
|
||||
options = options || {};
|
||||
var rules = utils.parseCSS(css);
|
||||
var editedElements = [];
|
||||
var styleAttributeName = 'style';
|
||||
var counters = {};
|
||||
|
||||
if (options.styleAttributeName) {
|
||||
styleAttributeName = options.styleAttributeName;
|
||||
}
|
||||
|
||||
rules.forEach(handleRule);
|
||||
editedElements.forEach(setStyleAttrs);
|
||||
|
||||
if (options.inlinePseudoElements) {
|
||||
editedElements.forEach(inlinePseudoElements);
|
||||
}
|
||||
|
||||
if (options.applyWidthAttributes) {
|
||||
editedElements.forEach(function(el) {
|
||||
setDimensionAttrs(el, 'width');
|
||||
});
|
||||
}
|
||||
|
||||
if (options.applyHeightAttributes) {
|
||||
editedElements.forEach(function(el) {
|
||||
setDimensionAttrs(el, 'height');
|
||||
});
|
||||
}
|
||||
|
||||
if (options.applyAttributesTableElements) {
|
||||
editedElements.forEach(setAttributesOnTableElements);
|
||||
}
|
||||
|
||||
if (options.insertPreservedExtraCss && options.extraCss) {
|
||||
var preservedText = utils.getPreservedText(options.extraCss, {
|
||||
mediaQueries: options.preserveMediaQueries,
|
||||
fontFaces: options.preserveFontFaces,
|
||||
keyFrames: options.preserveKeyFrames
|
||||
});
|
||||
if (preservedText) {
|
||||
var $appendTo = null;
|
||||
if (options.insertPreservedExtraCss !== true) {
|
||||
$appendTo = $(options.insertPreservedExtraCss);
|
||||
} else {
|
||||
$appendTo = $('head');
|
||||
if (!$appendTo.length) { $appendTo = $('body'); }
|
||||
if (!$appendTo.length) { $appendTo = $.root(); }
|
||||
}
|
||||
|
||||
$appendTo.first().append('<style>' + preservedText + '</style>');
|
||||
}
|
||||
}
|
||||
|
||||
function handleRule(rule) {
|
||||
var sel = rule[0];
|
||||
var style = rule[1];
|
||||
var selector = new utils.Selector(sel);
|
||||
var parsedSelector = selector.parsed();
|
||||
|
||||
if (!parsedSelector) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pseudoElementType = getPseudoElementType(parsedSelector);
|
||||
|
||||
// skip rule if the selector has any pseudos which are ignored
|
||||
for (var i = 0; i < parsedSelector.length; ++i) {
|
||||
var subSel = parsedSelector[i];
|
||||
if (subSel.pseudos) {
|
||||
for (var j = 0; j < subSel.pseudos.length; ++j) {
|
||||
var subSelPseudo = subSel.pseudos[j];
|
||||
if (juiceClient.ignoredPseudos.indexOf(subSelPseudo.name) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pseudoElementType) {
|
||||
var last = parsedSelector[parsedSelector.length - 1];
|
||||
var pseudos = last.pseudos;
|
||||
last.pseudos = filterElementPseudos(last.pseudos);
|
||||
sel = parsedSelector.toString();
|
||||
last.pseudos = pseudos;
|
||||
}
|
||||
|
||||
var els;
|
||||
try {
|
||||
els = $(sel);
|
||||
} catch (err) {
|
||||
// skip invalid selector
|
||||
return;
|
||||
}
|
||||
|
||||
els.each(function() {
|
||||
var el = this;
|
||||
|
||||
if (el.name && juiceClient.nonVisualElements.indexOf(el.name.toUpperCase()) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pseudoElementType) {
|
||||
var pseudoElPropName = 'pseudo' + pseudoElementType;
|
||||
var pseudoEl = el[pseudoElPropName];
|
||||
if (!pseudoEl) {
|
||||
pseudoEl = el[pseudoElPropName] = $('<span />').get(0);
|
||||
pseudoEl.pseudoElementType = pseudoElementType;
|
||||
pseudoEl.pseudoElementParent = el;
|
||||
pseudoEl.counterProps = el.counterProps;
|
||||
el[pseudoElPropName] = pseudoEl;
|
||||
}
|
||||
el = pseudoEl;
|
||||
}
|
||||
|
||||
if (!el.styleProps) {
|
||||
el.styleProps = {};
|
||||
|
||||
// if the element has inline styles, fake selector with topmost specificity
|
||||
if ($(el).attr(styleAttributeName)) {
|
||||
var cssText = '* { ' + $(el).attr(styleAttributeName) + ' } ';
|
||||
addProps(utils.parseCSS(cssText)[0][1], new utils.Selector('<style>', true));
|
||||
}
|
||||
|
||||
// store reference to an element we need to compile style="" attr for
|
||||
editedElements.push(el);
|
||||
}
|
||||
|
||||
if (!el.counterProps) {
|
||||
el.counterProps = el.parent && el.parent.counterProps
|
||||
? Object.create(el.parent.counterProps)
|
||||
: {};
|
||||
}
|
||||
|
||||
function resetCounter(el, value) {
|
||||
var tokens = value.split(/\s+/);
|
||||
|
||||
for (var j = 0; j < tokens.length; j++) {
|
||||
var counter = tokens[j];
|
||||
var resetval = parseInt(tokens[j+1], 10);
|
||||
|
||||
isNaN(resetval)
|
||||
? el.counterProps[counter] = counters[counter] = 0
|
||||
: el.counterProps[counter] = counters[tokens[j++]] = resetval;
|
||||
}
|
||||
}
|
||||
|
||||
function incrementCounter(el, value) {
|
||||
var tokens = value.split(/\s+/);
|
||||
|
||||
for (var j = 0; j < tokens.length; j++) {
|
||||
var counter = tokens[j];
|
||||
|
||||
if (el.counterProps[counter] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var incrval = parseInt(tokens[j+1], 10);
|
||||
|
||||
isNaN(incrval)
|
||||
? el.counterProps[counter] = counters[counter] += 1
|
||||
: el.counterProps[counter] = counters[tokens[j++]] += incrval;
|
||||
}
|
||||
}
|
||||
|
||||
// go through the properties
|
||||
function addProps(style, selector) {
|
||||
for (var i = 0, l = style.length; i < l; i++) {
|
||||
if (style[i].type == 'property') {
|
||||
var name = style[i].name;
|
||||
var value = style[i].value;
|
||||
|
||||
if (name === 'counter-reset') {
|
||||
resetCounter(el, value);
|
||||
}
|
||||
|
||||
if (name === 'counter-increment') {
|
||||
incrementCounter(el, value);
|
||||
}
|
||||
|
||||
var important = value.match(/!important$/) !== null;
|
||||
if (important && !options.preserveImportant) value = removeImportant(value);
|
||||
// adds line number and column number for the properties as "additionalPriority" to the
|
||||
// properties because in CSS the position directly affect the priority.
|
||||
var additionalPriority = [style[i].position.start.line, style[i].position.start.col];
|
||||
var prop = new utils.Property(name, value, selector, important ? 2 : 0, additionalPriority);
|
||||
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) {
|
||||
// deleting a property let us change the order (move it to the end in the setStyleAttrs loop)
|
||||
if (existing && existing.selector !== selector) {
|
||||
delete el.styleProps[name];
|
||||
} else if (existing) {
|
||||
// make "prop" a special composed property.
|
||||
prop.nextProp = existing;
|
||||
}
|
||||
|
||||
el.styleProps[name] = prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addProps(style, selector);
|
||||
});
|
||||
}
|
||||
|
||||
function setStyleAttrs(el) {
|
||||
var l = Object.keys(el.styleProps).length;
|
||||
var props = [];
|
||||
// Here we loop each property and make sure to "expand"
|
||||
// linked "nextProp" properties happening when the same property
|
||||
// is declared multiple times in the same selector.
|
||||
Object.keys(el.styleProps).forEach(function(key) {
|
||||
var np = el.styleProps[key];
|
||||
while (typeof np !== 'undefined') {
|
||||
props.push(np);
|
||||
np = np.nextProp;
|
||||
}
|
||||
});
|
||||
// sort properties by their originating selector's specificity so that
|
||||
// props like "padding" and "padding-bottom" are resolved as expected.
|
||||
props.sort(function(a, b) {
|
||||
return a.compareFunc(b);
|
||||
});
|
||||
var string = props
|
||||
.filter(function(prop) {
|
||||
// Content becomes the innerHTML of pseudo elements, not used as a
|
||||
// style property
|
||||
return prop.prop !== 'content';
|
||||
})
|
||||
.map(function(prop) {
|
||||
return prop.prop + ': ' + prop.value.replace(/["]/g, '\'') + ';';
|
||||
})
|
||||
.join(' ');
|
||||
if (string) {
|
||||
$(el).attr(styleAttributeName, string);
|
||||
}
|
||||
}
|
||||
|
||||
function inlinePseudoElements(el) {
|
||||
if (el.pseudoElementType && el.styleProps.content) {
|
||||
var parsed = parseContent(el);
|
||||
if (parsed.img) {
|
||||
el.name = 'img';
|
||||
$(el).attr('src', parsed.img);
|
||||
} else {
|
||||
$(el).text(parsed);
|
||||
}
|
||||
var parent = el.pseudoElementParent;
|
||||
if (el.pseudoElementType === 'before') {
|
||||
$(parent).prepend(el);
|
||||
} else {
|
||||
$(parent).append(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setDimensionAttrs(el, dimension) {
|
||||
if (!el.name) { return; }
|
||||
var elName = el.name.toUpperCase();
|
||||
if (juiceClient[dimension + 'Elements'].indexOf(elName) > -1) {
|
||||
for (var i in el.styleProps) {
|
||||
if (el.styleProps[i].prop === dimension) {
|
||||
var value = el.styleProps[i].value;
|
||||
if (options.preserveImportant) {
|
||||
value = removeImportant(value);
|
||||
}
|
||||
if (value.match(/px/)) {
|
||||
var pxSize = value.replace('px', '');
|
||||
$(el).attr(dimension, pxSize);
|
||||
return;
|
||||
}
|
||||
if (juiceClient.tableElements.indexOf(elName) > -1 && value.match(/\%/)) {
|
||||
$(el).attr(dimension, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractBackgroundUrl(value) {
|
||||
return value.indexOf('url(') !== 0
|
||||
? value
|
||||
: value.replace(/^url\((["'])?([^"']+)\1\)$/, '$2');
|
||||
}
|
||||
|
||||
function setAttributesOnTableElements(el) {
|
||||
if (!el.name) { return; }
|
||||
var elName = el.name.toUpperCase();
|
||||
var styleProps = Object.keys(juiceClient.styleToAttribute);
|
||||
|
||||
if (juiceClient.tableElements.indexOf(elName) > -1) {
|
||||
for (var i in el.styleProps) {
|
||||
if (styleProps.indexOf(el.styleProps[i].prop) > -1) {
|
||||
var prop = juiceClient.styleToAttribute[el.styleProps[i].prop];
|
||||
var value = el.styleProps[i].value;
|
||||
if (options.preserveImportant) {
|
||||
value = removeImportant(value);
|
||||
}
|
||||
if (prop === 'background') {
|
||||
value = extractBackgroundUrl(value);
|
||||
}
|
||||
if (/(linear|radial)-gradient\(/i.test(value)) {
|
||||
continue;
|
||||
}
|
||||
$(el).attr(prop, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeImportant(value) {
|
||||
return value.replace(/\s*!important$/, '')
|
||||
}
|
||||
|
||||
function findVariableValue(el, variable) {
|
||||
while (el) {
|
||||
if (variable in el.styleProps) {
|
||||
return el.styleProps[variable].value;
|
||||
}
|
||||
|
||||
var el = el.pseudoElementParent || el.parent;
|
||||
}
|
||||
}
|
||||
|
||||
function applyCounterStyle(counter, style) {
|
||||
switch (style) {
|
||||
case 'lower-roman':
|
||||
return numbers.romanize(counter).toLowerCase();
|
||||
case 'upper-roman':
|
||||
return numbers.romanize(counter);
|
||||
case 'lower-latin':
|
||||
case 'lower-alpha':
|
||||
return numbers.alphanumeric(counter).toLowerCase();
|
||||
case 'upper-latin':
|
||||
case 'upper-alpha':
|
||||
return numbers.alphanumeric(counter);
|
||||
// TODO support more counter styles
|
||||
default:
|
||||
return counter.toString();
|
||||
}
|
||||
}
|
||||
|
||||
function parseContent(el) {
|
||||
var content = el.styleProps.content.value;
|
||||
|
||||
if (content === 'none' || content === 'normal') {
|
||||
return '';
|
||||
}
|
||||
|
||||
var imageUrlMatch = content.match(/^\s*url\s*\(\s*(.*?)\s*\)\s*$/i);
|
||||
if (imageUrlMatch) {
|
||||
var url = imageUrlMatch[1].replace(/^['"]|['"]$/g, '');
|
||||
return { img: url };
|
||||
}
|
||||
|
||||
var parsed = [];
|
||||
|
||||
var tokens = content.split(/['"]/);
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
if (tokens[i] === '') continue;
|
||||
|
||||
var varMatch = tokens[i].match(/var\s*\(\s*(.*?)\s*(,\s*(.*?)\s*)?\s*\)/i);
|
||||
if (varMatch) {
|
||||
var variable = findVariableValue(el, varMatch[1]) || varMatch[2];
|
||||
parsed.push(variable.replace(/^['"]|['"]$/g, ''));
|
||||
continue;
|
||||
}
|
||||
|
||||
var counterMatch = tokens[i].match(/counter\s*\(\s*(.*?)\s*(,\s*(.*?)\s*)?\s*\)/i);
|
||||
if (counterMatch && counterMatch[1] in el.counterProps) {
|
||||
var counter = el.counterProps[counterMatch[1]];
|
||||
parsed.push(applyCounterStyle(counter, counterMatch[3]));
|
||||
continue;
|
||||
}
|
||||
|
||||
var attrMatch = tokens[i].match(/attr\s*\(\s*(.*?)\s*\)/i);
|
||||
if (attrMatch) {
|
||||
var attr = attrMatch[1];
|
||||
parsed.push(el.pseudoElementParent
|
||||
? el.pseudoElementParent.attribs[attr]
|
||||
: el.attribs[attr]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
parsed.push(tokens[i]);
|
||||
}
|
||||
|
||||
content = parsed.join('');
|
||||
// Naive unescape, assume no unicode char codes
|
||||
content = content.replace(/\\/g, '');
|
||||
return content;
|
||||
}
|
||||
|
||||
// Return "before" or "after" if the given selector is a pseudo element (e.g.,
|
||||
// a::after).
|
||||
function getPseudoElementType(selector) {
|
||||
if (selector.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pseudos = selector[selector.length - 1].pseudos;
|
||||
if (!pseudos) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < pseudos.length; i++) {
|
||||
if (isPseudoElementName(pseudos[i])) {
|
||||
return pseudos[i].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPseudoElementName(pseudo) {
|
||||
return pseudo.name === 'before' || pseudo.name === 'after';
|
||||
}
|
||||
|
||||
function filterElementPseudos(pseudos) {
|
||||
return pseudos.filter(function(pseudo) {
|
||||
return !isPseudoElementName(pseudo);
|
||||
});
|
||||
}
|
||||
|
||||
function juiceDocument($, options) {
|
||||
options = utils.getDefaultOptions(options);
|
||||
var css = extractCssFromDocument($, options);
|
||||
css += '\n' + options.extraCss;
|
||||
inlineDocument($, css, options);
|
||||
return $;
|
||||
}
|
||||
|
||||
function getStylesData($, options) {
|
||||
var results = [];
|
||||
var stylesList = $('style');
|
||||
var styleDataList, styleData, styleElement;
|
||||
stylesList.each(function() {
|
||||
styleElement = this;
|
||||
// the API for Cheerio using parse5 (default) and htmlparser2 are slightly different
|
||||
// detect this by checking if .childNodes exist (as opposed to .children)
|
||||
var usingParse5 = !!styleElement.childNodes;
|
||||
styleDataList = usingParse5 ? styleElement.childNodes : styleElement.children;
|
||||
if (styleDataList.length !== 1) {
|
||||
if (options.removeStyleTags) {
|
||||
$(styleElement).remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
styleData = styleDataList[0].data;
|
||||
if (options.applyStyleTags && $(styleElement).attr('data-embed') === undefined) {
|
||||
results.push(styleData);
|
||||
}
|
||||
if (options.removeStyleTags && $(styleElement).attr('data-embed') === undefined) {
|
||||
var text = usingParse5 ? styleElement.childNodes[0].nodeValue : styleElement.children[0].data;
|
||||
var preservedText = utils.getPreservedText(text, {
|
||||
mediaQueries: options.preserveMediaQueries,
|
||||
fontFaces: options.preserveFontFaces,
|
||||
keyFrames: options.preserveKeyFrames,
|
||||
pseudos: options.preservePseudos
|
||||
}, juiceClient.ignoredPseudos);
|
||||
if (preservedText) {
|
||||
if (usingParse5) {
|
||||
styleElement.childNodes[0].nodeValue = preservedText;
|
||||
} else {
|
||||
styleElement.children[0].data = preservedText;
|
||||
}
|
||||
} else {
|
||||
$(styleElement).remove();
|
||||
}
|
||||
}
|
||||
$(styleElement).removeAttr('data-embed');
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
function extractCssFromDocument($, options) {
|
||||
var results = getStylesData($, options);
|
||||
var css = results.join('\n');
|
||||
return css;
|
||||
}
|
||||
|
||||
return juiceClient;
|
||||
|
||||
};
|
40
book/node_modules/juice/lib/numbers.js
generated
vendored
Normal file
40
book/node_modules/juice/lib/numbers.js
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Converts a decimal number to roman numeral.
|
||||
* https://stackoverflow.com/questions/9083037/convert-a-number-into-a-roman-numeral-in-javascript
|
||||
*
|
||||
* @param {Number} number
|
||||
* @api private.
|
||||
*/
|
||||
exports.romanize = function(num) {
|
||||
if (isNaN(num))
|
||||
return NaN;
|
||||
var digits = String(+num).split(""),
|
||||
key = ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM",
|
||||
"","X","XX","XXX","XL","L","LX","LXX","LXXX","XC",
|
||||
"","I","II","III","IV","V","VI","VII","VIII","IX"],
|
||||
roman = "",
|
||||
i = 3;
|
||||
while (i--)
|
||||
roman = (key[+digits.pop() + (i * 10)] || "") + roman;
|
||||
return Array(+digits.join("") + 1).join("M") + roman;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a decimal number to alphanumeric numeral.
|
||||
* https://stackoverflow.com/questions/45787459/convert-number-to-alphabet-string-javascript
|
||||
*
|
||||
* @param {Number} number
|
||||
* @api private.
|
||||
*/
|
||||
exports.alphanumeric = function(num) {
|
||||
var s = '', t;
|
||||
|
||||
while (num > 0) {
|
||||
t = (num - 1) % 26;
|
||||
s = String.fromCharCode(65 + t) + s;
|
||||
num = (num - t)/26 | 0;
|
||||
}
|
||||
return s || undefined;
|
||||
}
|
65
book/node_modules/juice/lib/property.js
generated
vendored
Normal file
65
book/node_modules/juice/lib/property.js
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = exports = Property;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('./utils');
|
||||
|
||||
/**
|
||||
* CSS property constructor.
|
||||
*
|
||||
* @param {String} property
|
||||
* @param {String} value
|
||||
* @param {Selector} selector the property originates from
|
||||
* @param {Integer} priority 0 for normal properties, 2 for !important properties.
|
||||
* @param {Array} additional array of integers representing more detailed priorities (sorting)
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Property(prop, value, selector, priority, additionalPriority) {
|
||||
this.prop = prop;
|
||||
this.value = value;
|
||||
this.selector = selector;
|
||||
this.priority = priority || 0;
|
||||
this.additionalPriority = additionalPriority || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares with another Property based on Selector#specificity.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Property.prototype.compareFunc = function(property) {
|
||||
var a = [];
|
||||
a.push.apply(a, this.selector.specificity());
|
||||
a.push.apply(a, this.additionalPriority);
|
||||
a[0] += this.priority;
|
||||
var b = [];
|
||||
b.push.apply(b, property.selector.specificity());
|
||||
b.push.apply(b, property.additionalPriority);
|
||||
b[0] += property.priority;
|
||||
return utils.compareFunc(a, b);
|
||||
};
|
||||
|
||||
Property.prototype.compare = function(property) {
|
||||
var winner = this.compareFunc(property);
|
||||
if (winner === 1) {
|
||||
return this;
|
||||
}
|
||||
return property;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns CSS property
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Property.prototype.toString = function() {
|
||||
return this.prop + ': ' + this.value.replace(/['"]+/g, '') + ';';
|
||||
};
|
97
book/node_modules/juice/lib/selector.js
generated
vendored
Normal file
97
book/node_modules/juice/lib/selector.js
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
'use strict';
|
||||
|
||||
var parser = require('slick/parser');
|
||||
|
||||
module.exports = exports = Selector;
|
||||
|
||||
/**
|
||||
* CSS selector constructor.
|
||||
*
|
||||
* @param {String} selector text
|
||||
* @param {Array} optionally, precalculated specificity
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Selector(text, styleAttribute) {
|
||||
this.text = text;
|
||||
this.spec = undefined;
|
||||
this.styleAttribute = styleAttribute || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parsed selector.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Selector.prototype.parsed = function() {
|
||||
if (!this.tokens) { this.tokens = parse(this.text); }
|
||||
return this.tokens;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lazy specificity getter
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Selector.prototype.specificity = function() {
|
||||
var styleAttribute = this.styleAttribute;
|
||||
if (!this.spec) { this.spec = specificity(this.text, this.parsed()); }
|
||||
return this.spec;
|
||||
|
||||
function specificity(text, parsed) {
|
||||
var expressions = parsed || parse(text);
|
||||
var spec = [styleAttribute ? 1 : 0, 0, 0, 0];
|
||||
var nots = [];
|
||||
|
||||
for (var i = 0; i < expressions.length; i++) {
|
||||
var expression = expressions[i];
|
||||
var pseudos = expression.pseudos;
|
||||
|
||||
// id awards a point in the second column
|
||||
if (expression.id) { spec[1]++; }
|
||||
|
||||
// classes and attributes award a point each in the third column
|
||||
if (expression.attributes) { spec[2] += expression.attributes.length; }
|
||||
if (expression.classList) { spec[2] += expression.classList.length; }
|
||||
|
||||
// tag awards a point in the fourth column
|
||||
if (expression.tag && expression.tag !== '*') { spec[3]++; }
|
||||
|
||||
// pseudos award a point each in the fourth column
|
||||
if (pseudos) {
|
||||
spec[3] += pseudos.length;
|
||||
|
||||
for (var p = 0; p < pseudos.length; p++) {
|
||||
if (pseudos[p].name === 'not') {
|
||||
nots.push(pseudos[p].value);
|
||||
spec[3]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var ii = nots.length; ii--;) {
|
||||
var not = specificity(nots[ii]);
|
||||
for (var jj = 4; jj--;) { spec[jj] += not[jj]; }
|
||||
}
|
||||
|
||||
return spec;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a selector and returns the tokens.
|
||||
*
|
||||
* @param {String} selector
|
||||
* @api private.
|
||||
*/
|
||||
|
||||
function parse(text) {
|
||||
try {
|
||||
return parser(text)[0];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
167
book/node_modules/juice/lib/utils.js
generated
vendored
Normal file
167
book/node_modules/juice/lib/utils.js
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var mensch = require('mensch');
|
||||
var Selector = require('./selector');
|
||||
var Property = require('./property');
|
||||
|
||||
exports.Selector = Selector;
|
||||
exports.Property = Property;
|
||||
|
||||
/**
|
||||
* Returns an array of the selectors.
|
||||
*
|
||||
* @license Sizzle CSS Selector Engine - MIT
|
||||
* @param {String} selectorText from mensch
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.extract = function extract(selectorText) {
|
||||
var attr = 0;
|
||||
var sels = [];
|
||||
var sel = '';
|
||||
|
||||
for (var i = 0, l = selectorText.length; i < l; i++) {
|
||||
var c = selectorText.charAt(i);
|
||||
|
||||
if (attr) {
|
||||
if (']' === c || ')' === c) { attr--; }
|
||||
sel += c;
|
||||
} else {
|
||||
if (',' === c) {
|
||||
sels.push(sel);
|
||||
sel = '';
|
||||
} else {
|
||||
if ('[' === c || '(' === c) { attr++; }
|
||||
if (sel.length || (c !== ',' && c !== '\n' && c !== ' ')) { sel += c; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sel.length) {
|
||||
sels.push(sel);
|
||||
}
|
||||
|
||||
return sels;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a parse tree for a CSS source.
|
||||
* If it encounters multiple selectors separated by a comma, it splits the
|
||||
* tree.
|
||||
*
|
||||
* @param {String} css source
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.parseCSS = function(css) {
|
||||
var parsed = mensch.parse(css, {position: true, comments: true});
|
||||
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].type == 'rule') {
|
||||
var rule = rules[i];
|
||||
var selectors = rule.selectors;
|
||||
|
||||
for (var ii = 0, ll = selectors.length; ii < ll; ii++) {
|
||||
ret.push([selectors[ii], rule.declarations]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns preserved text for a CSS source.
|
||||
*
|
||||
* @param {String} css source
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.getPreservedText = function(css, options, ignoredPseudos) {
|
||||
var parsed = mensch.parse(css, {position: true, comments: true});
|
||||
var rules = typeof parsed.stylesheet != 'undefined' && parsed.stylesheet.rules ? parsed.stylesheet.rules : [];
|
||||
var preserved = [];
|
||||
var lastStart = null;
|
||||
|
||||
for (var i = rules.length - 1; i >= 0; i--) {
|
||||
if ((options.fontFaces && rules[i].type === 'font-face') ||
|
||||
(options.mediaQueries && rules[i].type === 'media') ||
|
||||
(options.keyFrames && rules[i].type === 'keyframes') ||
|
||||
(options.pseudos && rules[i].selectors && this.matchesPseudo(rules[i].selectors[0], ignoredPseudos))) {
|
||||
preserved.unshift(
|
||||
mensch.stringify(
|
||||
{ stylesheet: { rules: [ rules[i] ] }},
|
||||
{ comments: false, indentation: ' ' }
|
||||
)
|
||||
);
|
||||
}
|
||||
lastStart = rules[i].position.start;
|
||||
}
|
||||
|
||||
if (preserved.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return '\n' + preserved.join('\n') + '\n';
|
||||
};
|
||||
|
||||
exports.normalizeLineEndings = function(text) {
|
||||
return text.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
|
||||
};
|
||||
|
||||
exports.matchesPseudo = function(needle, haystack) {
|
||||
return haystack.find(function (element) {
|
||||
return needle.indexOf(element) > -1;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two specificity vectors, returning the winning one.
|
||||
*
|
||||
* @param {Array} vector a
|
||||
* @param {Array} vector b
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.compareFunc = function(a, b) {
|
||||
var min = Math.min(a.length, b.length);
|
||||
for (var i = 0; i < min; i++) {
|
||||
if (a[i] === b[i]) { continue; }
|
||||
if (a[i] > b[i]) { return 1; }
|
||||
return -1;
|
||||
}
|
||||
|
||||
return a.length - b.length;
|
||||
};
|
||||
|
||||
exports.compare = function(a, b) {
|
||||
return exports.compareFunc(a, b) == 1 ? a : b;
|
||||
};
|
||||
|
||||
exports.getDefaultOptions = function(options) {
|
||||
var result = Object.assign({
|
||||
extraCss: '',
|
||||
insertPreservedExtraCss: true,
|
||||
applyStyleTags: true,
|
||||
removeStyleTags: true,
|
||||
preserveMediaQueries: true,
|
||||
preserveFontFaces: true,
|
||||
preserveKeyFrames: true,
|
||||
preservePseudos: true,
|
||||
applyWidthAttributes: true,
|
||||
applyHeightAttributes: true,
|
||||
applyAttributesTableElements: true,
|
||||
url: ''
|
||||
}, options);
|
||||
|
||||
result.webResources = result.webResources || {};
|
||||
|
||||
return result;
|
||||
};
|
Reference in New Issue
Block a user