UNPKG

tenon-api-client

Version:

Tenon Accessibility Testing API Client Module

132 lines (124 loc) 5 kB
'use strict'; /* * This is a simple inliner of LOCAL script and css tags. It will not handle http references. * Doesn't (yet) handle CSS @import. Basically this is all one level deep. * * This module expects an object with one required property: fname, which should be a path to * an HTML file to process. The local files to be included must have relative links (relative * from the HTML file path). * * An additional property in the configuration is cssUrlReplacer (optional). If present, * it should be a function that is called for URLs within CSS files. * (For example: background: url(...);) It will receive the content of the url and must * return a replacement (which may be the unchanged url). Note: This is not a useful * thing to do, at least within the Tenon API project. * * The configuration property inlineContent (default true) specifies whether to perform * the core function of inlining local javascript and css. If falsey, this module simply * fulfills with the content of the HTML file. * * Returns a promise. It resolves with a string containing HTML with inlined JS and CSS. * (or just the untouched HTML if inlineContent is set to false.) */ /*jshint -W079 */ var path = require('path'), Promise = require('bluebird'), fs = Promise.promisifyAll(require("fs")) ; module.exports = function(conf) { return new Promise(function(resolve, reject) { var dirname = path.dirname(conf.fname) + path.sep, sre = /<script.+?src=["'](?!https?:\/\/)([^"']+?)["'].*?>/ig, srcre = / *src=["'][^"']+?["']/i, cre = /<link.+?href=["'](?!https?:\/\/)([^"']+?)["'].*>/ig, hrefre = / *href=["'][^"']+?["']/i, crerel = / *rel=["'][^"']+?["']/i, cretag = /<link/i, creend = / *\/{0,1}>/, cssurl = /(URL *\(["']?)([^"')]+)(["']?\))/ig, filecontent, incfiles = {} ; /* inlineLocalContent processes the HTML file, * inlining local CSS and JavaScript. The inlined * content is available in the incfiles object, * keyed by src or href attribute value. */ function inlineLocalContent() { // inline local javascript filecontent = filecontent.replace(sre, function(matched, src) { if (!incfiles[src]) { return matched; // This file wasn't available } return matched.replace(srcre, '') + '\n' + incfiles[src]; }); // now inline local css filecontent = filecontent.replace(cre, function(matched, src) { var tag; if (!incfiles[src]) { return matched; } if (matched.toLowerCase().search('stylesheet') === -1) { return matched; } tag = matched.replace(hrefre, ''); // remove href attribute tag = tag.replace(crerel, ''); // remove rel attribute tag = tag.replace(cretag, '<style'); // change link to style tag = tag.replace(creend, '>'); // remove possible self-closing in style tag return tag + '\n' + incfiles[src] + '\n</style>'; }); //console.log(filecontent); resolve(filecontent); } /* fetch the content of one local CSS or Javascript file. * Add the content to the incfiles object. */ function getIncludedContent(fileName, css) { return fs.readFileAsync(dirname + fileName, {encoding: 'utf8'}).then(function(data) { if (css && conf.cssUrlReplacer) { data = data.replace(cssurl, function(matched, pref, theUrl, suf) { //console.log('readFileAsync, css replacer: ' + url + ', ' + theUrl); return pref + conf.cssUrlReplacer(theUrl) + suf; }); } incfiles[fileName] = data; }) .catch(function(e) { // If we can't read the file, just issue a console warning and continue. // Content for this file will not be inlined. console.warn('Inliner:\n' + e); }); } /* * Start by reading in the content of the HTML input file. * Find all the local script and link tags then read them in. * Once all have been read, call inlineLocalContent to do the inlining. */ fs.readFile(conf.fname, {encoding: 'utf8'}, function(err, data) { // read the file, find all the local includes (JS & CSS). var found, files; if (data) { filecontent = data; if (!conf.inlineContent) { //console.log('returning non-inlined'); resolve(filecontent); return; } files = []; while ((found = sre.exec(data)) !== null) { files.push(getIncludedContent(found[1])); } while ((found = cre.exec(data)) !== null) { files.push(getIncludedContent(found[1], true)); } if (files.length === 0) { resolve(filecontent); // no files to inline, just return content } else { Promise.settle(files).then(inlineLocalContent); } } else { reject(err); } }); }); };