123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- var _ = require("underscore");
- var promises = require("./promises");
- var documents = require("./documents");
- var htmlPaths = require("./styles/html-paths");
- var results = require("./results");
- var images = require("./images");
- var Html = require("./html");
- var writers = require("./writers");
- exports.DocumentConverter = DocumentConverter;
- function DocumentConverter(options) {
- return {
- convertToHtml: function(element) {
- var comments = _.indexBy(
- element.type === documents.types.document ? element.comments : [],
- "commentId"
- );
- var conversion = new DocumentConversion(options, comments);
- return conversion.convertToHtml(element);
- }
- };
- }
- function DocumentConversion(options, comments) {
- var noteNumber = 1;
- var noteReferences = [];
- var referencedComments = [];
- options = _.extend({ignoreEmptyParagraphs: true}, options);
- var idPrefix = options.idPrefix === undefined ? "" : options.idPrefix;
- var ignoreEmptyParagraphs = options.ignoreEmptyParagraphs;
- var defaultParagraphStyle = htmlPaths.topLevelElement("p");
- var styleMap = options.styleMap || [];
- function convertToHtml(document) {
- var messages = [];
- 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];
- }
- });
- }
- var writer = writers.writer({
- prettyPrint: options.prettyPrint,
- outputFormat: options.outputFormat
- });
- Html.write(writer, Html.simplify(replaceDeferred(html)));
- return new results.Result(writer.asString(), messages);
- });
- }
- function convertElements(elements, messages, options) {
- return flatMap(elements, function(element) {
- return elementToHtml(element, messages, options);
- });
- }
- function elementToHtml(element, messages, options) {
- if (!options) {
- throw new Error("options not set");
- }
- var handler = elementConverters[element.type];
- if (handler) {
- return handler(element, messages, options);
- } else {
- return [];
- }
- }
- function convertParagraph(element, messages, options) {
- return htmlPathForParagraph(element, messages).wrap(function() {
- var content = convertElements(element.children, messages, options);
- if (ignoreEmptyParagraphs) {
- return content;
- } else {
- return [Html.forceWrite].concat(content);
- }
- });
- }
- function htmlPathForParagraph(element, messages) {
- var style = findStyle(element);
- if (style) {
- return style.to;
- } else {
- if (element.styleId) {
- messages.push(unrecognisedStyleWarning("paragraph", element));
- }
- return defaultParagraphStyle;
- }
- }
- function convertRun(run, messages, options) {
- var nodes = function() {
- return convertElements(run.children, messages, options);
- };
- var paths = [];
- if (run.highlight !== null) {
- var path = findHtmlPath({type: "highlight", color: run.highlight});
- if (path) {
- paths.push(path);
- }
- }
- if (run.isSmallCaps) {
- paths.push(findHtmlPathForRunProperty("smallCaps"));
- }
- if (run.isAllCaps) {
- paths.push(findHtmlPathForRunProperty("allCaps"));
- }
- if (run.isStrikethrough) {
- paths.push(findHtmlPathForRunProperty("strikethrough", "s"));
- }
- if (run.isUnderline) {
- paths.push(findHtmlPathForRunProperty("underline"));
- }
- if (run.verticalAlignment === documents.verticalAlignment.subscript) {
- paths.push(htmlPaths.element("sub", {}, {fresh: false}));
- }
- if (run.verticalAlignment === documents.verticalAlignment.superscript) {
- paths.push(htmlPaths.element("sup", {}, {fresh: false}));
- }
- if (run.isItalic) {
- paths.push(findHtmlPathForRunProperty("italic", "em"));
- }
- if (run.isBold) {
- paths.push(findHtmlPathForRunProperty("bold", "strong"));
- }
- var stylePath = htmlPaths.empty;
- var style = findStyle(run);
- if (style) {
- stylePath = style.to;
- } else if (run.styleId) {
- messages.push(unrecognisedStyleWarning("run", run));
- }
- paths.push(stylePath);
- paths.forEach(function(path) {
- nodes = path.wrap.bind(path, nodes);
- });
- return nodes();
- }
- function findHtmlPathForRunProperty(elementType, defaultTagName) {
- var path = findHtmlPath({type: elementType});
- if (path) {
- return path;
- } else if (defaultTagName) {
- return htmlPaths.element(defaultTagName, {}, {fresh: false});
- } else {
- return htmlPaths.empty;
- }
- }
- function findHtmlPath(element, defaultPath) {
- var style = findStyle(element);
- return style ? style.to : defaultPath;
- }
- function findStyle(element) {
- for (var i = 0; i < styleMap.length; i++) {
- if (styleMap[i].from.matches(element)) {
- return styleMap[i];
- }
- }
- }
- function recoveringConvertImage(convertImage) {
- return function(image, messages) {
- return promises.attempt(function() {
- return convertImage(image, messages);
- }).caught(function(error) {
- messages.push(results.error(error));
- return [];
- });
- };
- }
- function noteHtmlId(note) {
- return referentHtmlId(note.noteType, note.noteId);
- }
- function noteRefHtmlId(note) {
- return referenceHtmlId(note.noteType, note.noteId);
- }
- function referentHtmlId(referenceType, referenceId) {
- return htmlId(referenceType + "-" + referenceId);
- }
- function referenceHtmlId(referenceType, referenceId) {
- return htmlId(referenceType + "-ref-" + referenceId);
- }
- function htmlId(suffix) {
- return idPrefix + suffix;
- }
- var defaultTablePath = htmlPaths.elements([
- htmlPaths.element("table", {}, {fresh: true})
- ]);
- function convertTable(element, messages, options) {
- return findHtmlPath(element, defaultTablePath).wrap(function() {
- return convertTableChildren(element, messages, options);
- });
- }
- function convertTableChildren(element, messages, options) {
- var bodyIndex = _.findIndex(element.children, function(child) {
- return !child.type === documents.types.tableRow || !child.isHeader;
- });
- if (bodyIndex === -1) {
- bodyIndex = element.children.length;
- }
- var children;
- if (bodyIndex === 0) {
- children = convertElements(
- element.children,
- messages,
- _.extend({}, options, {isTableHeader: false})
- );
- } else {
- var headRows = convertElements(
- element.children.slice(0, bodyIndex),
- messages,
- _.extend({}, options, {isTableHeader: true})
- );
- var bodyRows = convertElements(
- element.children.slice(bodyIndex),
- messages,
- _.extend({}, options, {isTableHeader: false})
- );
- children = [
- Html.freshElement("thead", {}, headRows),
- Html.freshElement("tbody", {}, bodyRows)
- ];
- }
- return [Html.forceWrite].concat(children);
- }
- function convertTableRow(element, messages, options) {
- var children = convertElements(element.children, messages, options);
- return [
- Html.freshElement("tr", {}, [Html.forceWrite].concat(children))
- ];
- }
- function convertTableCell(element, messages, options) {
- var tagName = options.isTableHeader ? "th" : "td";
- var children = convertElements(element.children, messages, options);
- var attributes = {};
- if (element.colSpan !== 1) {
- attributes.colspan = element.colSpan.toString();
- }
- if (element.rowSpan !== 1) {
- attributes.rowspan = element.rowSpan.toString();
- }
- return [
- Html.freshElement(tagName, attributes, [Html.forceWrite].concat(children))
- ];
- }
- function convertCommentReference(reference, messages, options) {
- return findHtmlPath(reference, htmlPaths.ignore).wrap(function() {
- var comment = comments[reference.commentId];
- var count = referencedComments.length + 1;
- var label = "[" + commentAuthorLabel(comment) + count + "]";
- referencedComments.push({label: label, comment: comment});
- // TODO: remove duplication with note references
- return [
- Html.freshElement("a", {
- href: "#" + referentHtmlId("comment", reference.commentId),
- id: referenceHtmlId("comment", reference.commentId)
- }, [Html.text(label)])
- ];
- });
- }
- function convertComment(referencedComment, messages, options) {
- // TODO: remove duplication with note references
- var label = referencedComment.label;
- var comment = referencedComment.comment;
- var body = convertElements(comment.body, messages, options).concat([
- Html.nonFreshElement("p", {}, [
- Html.text(" "),
- Html.freshElement("a", {"href": "#" + referenceHtmlId("comment", comment.commentId)}, [
- Html.text("↑")
- ])
- ])
- ]);
- return [
- Html.freshElement(
- "dt",
- {"id": referentHtmlId("comment", comment.commentId)},
- [Html.text("Comment " + label)]
- ),
- Html.freshElement("dd", {}, body)
- ];
- }
- function convertBreak(element, messages, options) {
- return htmlPathForBreak(element).wrap(function() {
- return [];
- });
- }
- function htmlPathForBreak(element) {
- var style = findStyle(element);
- if (style) {
- return style.to;
- } else if (element.breakType === "line") {
- return htmlPaths.topLevelElement("br");
- } else {
- return htmlPaths.empty;
- }
- }
- var elementConverters = {
- "document": function(document, messages, options) {
- var children = convertElements(document.children, messages, options);
- var notes = noteReferences.map(function(noteReference) {
- return document.notes.resolve(noteReference);
- });
- var notesNodes = convertElements(notes, messages, options);
- return children.concat([
- Html.freshElement("ol", {}, notesNodes),
- Html.freshElement("dl", {}, flatMap(referencedComments, function(referencedComment) {
- return convertComment(referencedComment, messages, options);
- }))
- ]);
- },
- "paragraph": convertParagraph,
- "run": convertRun,
- "text": function(element, messages, options) {
- return [Html.text(element.value)];
- },
- "tab": function(element, messages, options) {
- return [Html.text("\t")];
- },
- "hyperlink": function(element, messages, options) {
- var href = element.anchor ? "#" + htmlId(element.anchor) : element.href;
- var attributes = {href: href};
- if (element.targetFrame != null) {
- attributes.target = element.targetFrame;
- }
- var children = convertElements(element.children, messages, options);
- return [Html.nonFreshElement("a", attributes, children)];
- },
- "bookmarkStart": function(element, messages, options) {
- var anchor = Html.freshElement("a", {
- id: htmlId(element.name)
- }, [Html.forceWrite]);
- return [anchor];
- },
- "noteReference": function(element, messages, options) {
- noteReferences.push(element);
- var anchor = Html.freshElement("a", {
- href: "#" + noteHtmlId(element),
- id: noteRefHtmlId(element)
- }, [Html.text("[" + (noteNumber++) + "]")]);
- return [Html.freshElement("sup", {}, [anchor])];
- },
- "note": function(element, messages, options) {
- var children = convertElements(element.body, messages, options);
- 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);
- },
- "commentReference": convertCommentReference,
- "comment": convertComment,
- "image": deferredConversion(recoveringConvertImage(options.convertImage || images.dataUri)),
- "table": convertTable,
- "tableRow": convertTableRow,
- "tableCell": convertTableCell,
- "break": convertBreak
- };
- return {
- convertToHtml: convertToHtml
- };
- }
- var deferredId = 1;
- function deferredConversion(func) {
- return function(element, messages, options) {
- return [
- {
- type: "deferred",
- id: deferredId++,
- value: function() {
- return func(element, messages, options);
- }
- }
- ];
- };
- }
- 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);
- }
- });
- }
- var commentAuthorLabel = exports.commentAuthorLabel = function commentAuthorLabel(comment) {
- return comment.authorInitials || "";
- };
|