aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2016-06-09 23:15:15 +0100
committerMichael Williamson <mike@zwobble.org>2016-06-20 12:06:19 +0100
commit9f9bc763f60e5ef4f22212c52964bde3d3574e26 (patch)
treedeb72d7fb314ee64146e6a3ed6d135d1834d7da6 /lib
parentac3981df81148fad21c32b22496ecc145ad36e98 (diff)
Batch up async conversions until end of conversion
Diffstat (limited to 'lib')
-rw-r--r--lib/document-to-html.js280
-rw-r--r--lib/images.js6
-rw-r--r--lib/promises.js3
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);