diff options
| author | Michael Williamson <mike@zwobble.org> | 2016-06-09 23:15:15 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2016-06-20 12:06:19 +0100 |
| commit | 9f9bc763f60e5ef4f22212c52964bde3d3574e26 (patch) | |
| tree | deb72d7fb314ee64146e6a3ed6d135d1834d7da6 /lib | |
| parent | ac3981df81148fad21c32b22496ecc145ad36e98 (diff) | |
Batch up async conversions until end of conversion
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/document-to-html.js | 280 | ||||
| -rw-r--r-- | lib/images.js | 6 | ||||
| -rw-r--r-- | lib/promises.js | 3 |
3 files changed, 159 insertions, 130 deletions
diff --git a/lib/document-to-html.js b/lib/document-to-html.js index 33b6d46..747e12f 100644 --- a/lib/document-to-html.js +++ b/lib/document-to-html.js @@ -1,4 +1,3 @@ -var async = require("async"); var _ = require("underscore"); var promises = require("./promises"); @@ -12,14 +11,6 @@ var writers = require("./writers"); exports.DocumentConverter = DocumentConverter; -// TODO: investigate tidier solutions -if (typeof setImmediate === "undefined") { - var setImmediate = function(callback) { - setTimeout(callback, 0); - }; -} - - function DocumentConverter(options) { return { convertToHtml: function() { @@ -44,48 +35,69 @@ function DocumentConversion(options) { function convertToHtml(document) { var messages = []; - return promises.nfcall(elementToHtml, document, messages).then(function(html) { + var html = elementToHtml(document, messages); + + var deferredNodes = []; + walkHtml(html, function(node) { + if (node.type === "deferred") { + deferredNodes.push(node); + } + }); + var deferredValues = {}; + return promises.mapSeries(deferredNodes, function(deferred) { + return deferred.value().then(function(value) { + deferredValues[deferred.id] = value; + }); + }).then(function() { + function replaceDeferred(nodes) { + return flatMap(nodes, function(node) { + if (node.type === "deferred") { + return deferredValues[node.id]; + } else if (node.children) { + return [ + _.extend({}, node, { + children: replaceDeferred(node.children) + }) + ]; + } else { + return [node]; + } + }); + } + html = replaceDeferred(html); + var writer = writers.writer({ prettyPrint: options.prettyPrint, outputFormat: options.outputFormat }); Html.write(writer, Html.simplify(html)); - return new results.Result(writer.asString(), messages); + return promises.when(new results.Result(writer.asString(), messages)); }); } - function convertElements(elements, messages, callback) { - async.mapSeries(elements, function(element, callback) { - return elementToHtml(element, messages, callback); - }, function(error, results) { - if (error) { - callback(error); - } else { - callback(null, _.flatten(results, true)); - } + function convertElements(elements, messages) { + return flatMap(elements, function(element) { + return elementToHtml(element, messages); }); } - function elementToHtml(element, messages, callback) { + function elementToHtml(element, messages) { var handler = elementConverters[element.type]; if (handler) { - handler(element, messages, callback); + return handler(element, messages); } else { - callback(); + return []; } } - function convertParagraph(element, messages, callback) { + function convertParagraph(element, messages) { var style = styleForParagraph(element, messages); - setImmediate(function() { - convertElements(element.children, messages, function(error, children) { - if (!options.ignoreEmptyParagraphs) { - children = [Html.forceWrite].concat(children); - } - callback(null, Html.pathToNodes(style, children)); - }); - }); + var children = convertElements(element.children, messages); + if (!options.ignoreEmptyParagraphs) { + children = [Html.forceWrite].concat(children); + } + return Html.pathToNodes(style, children); } function styleForParagraph(element, messages) { @@ -101,38 +113,37 @@ function DocumentConversion(options) { } } - function convertRun(run, messages, callback) { + function convertRun(run, messages) { + var style; if (run.styleId) { - var style = findStyle(run); + style = findStyle(run); if (!style) { messages.push(unrecognisedStyleWarning("run", run)); } } - convertElements(run.children, messages, function(error, children) { - var result = children; - if (run.isStrikethrough) { - result = convertRunProperty(result, "strikethrough", "s"); - } - if (run.isUnderline) { - result = convertUnderline(result); - } - if (run.verticalAlignment === documents.verticalAlignment.subscript) { - result = wrapInNonFreshElement(result, "sub"); - } - if (run.verticalAlignment === documents.verticalAlignment.superscript) { - result = wrapInNonFreshElement(result, "sup"); - } - if (run.isItalic) { - result = convertRunProperty(result, "italic", "em"); - } - if (run.isBold) { - result = convertRunProperty(result, "bold", "strong"); - } - if (style) { - result = Html.pathToNodes(style.to, result); - } - callback(null, result); - }); + var result = convertElements(run.children, messages); + if (run.isStrikethrough) { + result = convertRunProperty(result, "strikethrough", "s"); + } + if (run.isUnderline) { + result = convertUnderline(result); + } + if (run.verticalAlignment === documents.verticalAlignment.subscript) { + result = wrapInNonFreshElement(result, "sub"); + } + if (run.verticalAlignment === documents.verticalAlignment.superscript) { + result = wrapInNonFreshElement(result, "sup"); + } + if (run.isItalic) { + result = convertRunProperty(result, "italic", "em"); + } + if (run.isBold) { + result = convertRunProperty(result, "bold", "strong"); + } + if (style) { + result = Html.pathToNodes(style.to, result); + } + return result; } function wrapInNonFreshElement(nodes, tagName) { @@ -174,14 +185,10 @@ function DocumentConversion(options) { }); function recoveringConvertImage(convertImage) { - return function(image, messages, callback) { - convertImage(image, messages, function(error, result) { - if (error) { - messages.push(results.warning(error.message)); - callback(null, []); - } else { - callback(null, result); - } + return function(image, messages) { + return convertImage(image, messages).caught(function(error) { + messages.push(results.warning(error.message)); + return []; }); }; } @@ -198,98 +205,92 @@ function DocumentConversion(options) { return idPrefix + suffix; } - function convertTable(element, messages, callback) { - wrapChildrenInFreshElement(element, "table", messages, callback); + function convertTable(element, messages) { + return wrapChildrenInFreshElement(element, "table", messages); } - function convertTableRow(element, messages, callback) { - wrapChildrenInFreshElement(element, "tr", messages, callback); + function convertTableRow(element, messages) { + return wrapChildrenInFreshElement(element, "tr", messages); } - function convertTableCell(element, messages, callback) { - convertElements(element.children, messages, function(error, children) { - var attributes = {}; - if (element.colSpan !== 1) { - attributes.colspan = element.colSpan.toString(); - } - if (element.rowSpan !== 1) { - attributes.rowspan = element.rowSpan.toString(); - } + function convertTableCell(element, messages) { + var children = convertElements(element.children, messages); + var attributes = {}; + if (element.colSpan !== 1) { + attributes.colspan = element.colSpan.toString(); + } + if (element.rowSpan !== 1) { + attributes.rowspan = element.rowSpan.toString(); + } - callback(null, [ - Html.freshElement("td", attributes, [Html.forceWrite].concat(children)) - ]); - }); + return [ + Html.freshElement("td", attributes, [Html.forceWrite].concat(children)) + ]; } - function wrapChildrenInFreshElement(element, wrapElementName, messages, callback) { - convertElements(element.children, messages, function(error, children) { - callback(null, [ - Html.freshElement(wrapElementName, {}, [Html.forceWrite].concat(children)) - ]); - }); + function wrapChildrenInFreshElement(element, wrapElementName, messages) { + var children = convertElements(element.children, messages); + return [ + Html.freshElement(wrapElementName, {}, [Html.forceWrite].concat(children)) + ]; } var elementConverters = { - "document": function(document, messages, callback) { - convertElements(document.children, messages, function(error, children) { - var notes = noteReferences.map(function(noteReference) { - return document.notes.resolve(noteReference); - }); - convertElements(notes, messages, function(error, notesNodes) { - callback(null, children.concat([ - Html.freshElement("ol", {}, notesNodes) - ])); - }); + "document": function(document, messages) { + var children = convertElements(document.children, messages); + var notes = noteReferences.map(function(noteReference) { + return document.notes.resolve(noteReference); }); + var notesNodes = convertElements(notes, messages); + return children.concat([ + Html.freshElement("ol", {}, notesNodes) + ]); }, "paragraph": convertParagraph, "run": convertRun, - "text": function(element, messages, callback) { - callback(null, [Html.text(element.value)]); + "text": function(element, messages) { + return [Html.text(element.value)]; }, - "tab": function(element, messages, callback) { - callback(null, [Html.text("\t")]); + "tab": function(element, messages) { + return [Html.text("\t")]; }, - "hyperlink": function(element, messages, callback) { + "hyperlink": function(element, messages) { var href = element.anchor ? "#" + htmlId(element.anchor) : element.href; - convertElements(element.children, messages, function(error, children) { - callback(null, [Html.freshElement("a", {href: href}, children)]); - }); + var children = convertElements(element.children, messages); + return [Html.freshElement("a", {href: href}, children)]; }, - "bookmarkStart": function(element, messages, callback) { + "bookmarkStart": function(element, messages) { var anchor = Html.freshElement("a", { id: htmlId(element.name) }, [Html.forceWrite]); - callback(null, [anchor]); + return [anchor]; }, - "noteReference": function(element, messages, callback) { + "noteReference": function(element, messages) { noteReferences.push(element); var anchor = Html.freshElement("a", { href: "#" + noteHtmlId(element), id: noteRefHtmlId(element) }, [Html.text("[" + (noteNumber++) + "]")]); - callback(null, [Html.freshElement("sup", {}, [anchor])]); + return [Html.freshElement("sup", {}, [anchor])]; }, - "note": function(element, messages, callback) { - convertElements(element.body, messages, function(error, children) { - var backLink = Html.elementWithTag(htmlPaths.element("p", {}, {fresh: false}), [ - Html.text(" "), - Html.freshElement("a", {href: "#" + noteRefHtmlId(element)}, [Html.text("↑")]), - ]); - var body = children.concat([backLink]); - - callback(null, Html.freshElement("li", {id: noteHtmlId(element)}, body)); - }); + "note": function(element, messages) { + var children = convertElements(element.body, messages); + var backLink = Html.elementWithTag(htmlPaths.element("p", {}, {fresh: false}), [ + Html.text(" "), + Html.freshElement("a", {href: "#" + noteRefHtmlId(element)}, [Html.text("↑")]), + ]); + var body = children.concat([backLink]); + + return Html.freshElement("li", {id: noteHtmlId(element)}, body); }, - "image": recoveringConvertImage(options.convertImage || defaultConvertImage), + "image": deferredConversion(recoveringConvertImage(options.convertImage || defaultConvertImage)), "table": convertTable, "tableRow": convertTableRow, "tableCell": convertTableCell, - "lineBreak": function(element, messages, callback) { - callback(null, [Html.selfClosingElement("br")]); + "lineBreak": function(element, messages) { + return [Html.selfClosingElement("br")]; } }; return { @@ -297,9 +298,38 @@ function DocumentConversion(options) { }; } +var deferredId = 1; + +function deferredConversion(func) { + return function(element, messages) { + return [ + { + type: "deferred", + id: deferredId++, + value: function() { + return func(element, messages); + } + } + ]; + }; +} + function unrecognisedStyleWarning(type, element) { return results.warning( "Unrecognised " + type + " style: '" + element.styleName + "'" + " (Style ID: " + element.styleId + ")" ); } + +function flatMap(values, func) { + return _.flatten(values.map(func), true); +} + +function walkHtml(nodes, callback) { + nodes.forEach(function(node) { + callback(node); + if (node.children) { + walkHtml(node.children, callback); + } + }); +} diff --git a/lib/images.js b/lib/images.js index 6aac18a..fdc3601 100644 --- a/lib/images.js +++ b/lib/images.js @@ -4,16 +4,14 @@ var promises = require("./promises"); var Html = require("./html"); exports.imgElement = function(func) { - return function(element, messages, callback) { + return function(element, messages) { return promises.when(func(element)).then(function(result) { var attributes = _.clone(result); if (element.altText) { attributes.alt = element.altText; } return [Html.selfClosingElement("img", attributes)]; - }).then(function(value) { - callback(null, value); - }, callback); + }); }; }; diff --git a/lib/promises.js b/lib/promises.js index 4c10285..24dc34a 100644 --- a/lib/promises.js +++ b/lib/promises.js @@ -1,5 +1,5 @@ var _ = require("underscore"); -var bluebird = require("bluebird/js/main/promise")(); +var bluebird = require("bluebird/js/release/promise")(); exports.defer = bluebird.defer; exports.when = bluebird.resolve; @@ -8,6 +8,7 @@ exports.all = bluebird.all; exports.props = bluebird.props; exports.reject = bluebird.reject; exports.promisify = bluebird.promisify; +exports.mapSeries = bluebird.mapSeries; exports.nfcall = function(func) { var args = Array.prototype.slice.call(arguments, 1); |
