%PDF- %PDF-
Direktori : /home/opti/workflow/node_modules/juice/lib/ |
Current File : /home/opti/workflow/node_modules/juice/lib/inline.js |
'use strict'; var utils = require('./utils'); 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'; 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(); 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; 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); } // 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; var important = style[i].value.match(/!important$/) !== null; if (important && !options.preserveImportant) value = value.replace(/\s*!important$/, ''); // 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.styleProps.content.value); 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) { if (el.styleProps[i].value.match(/px/)) { var pxSize = el.styleProps[i].value.replace('px', ''); $(el).attr(dimension, pxSize); return; } if (juiceClient.tableElements.indexOf(elName) > -1 && el.styleProps[i].value.match(/\%/)) { $(el).attr(dimension, el.styleProps[i].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 (prop === 'background') { value = extractBackgroundUrl(value); } if (/(linear|radial)-gradient\(/i.test(value)) { continue; } $(el).attr(prop, value); } } } } } function parseContent(content) { 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 }; } // Naive parsing, assume well-formed value content = content.slice(1, content.length - 1); // 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; styleDataList = styleElement.childNodes; if (styleDataList.length !== 1) { 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 preservedText = utils.getPreservedText(styleElement.childNodes[0].nodeValue, { mediaQueries: options.preserveMediaQueries, fontFaces: options.preserveFontFaces, keyFrames: options.preserveKeyFrames, pseudos: options.preservePseudos }, juiceClient.ignoredPseudos); if (preservedText) { styleElement.childNodes[0].nodeValue = 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; };