All new
alle new
This commit is contained in:
1 parent
da64472251
commit
5b20adee48
1605 files changed
+396517
-1674
No files matched your search
+469
@@ -0,0 +1,469 @@
|
||||
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, Object.create(null));
|
||||
|
||||
var deferredNodes = [];
|
||||
walkHtml(html, function(node) {
|
||||
if (node.type === "deferred") {
|
||||
deferredNodes.push(node);
|
||||
}
|
||||
});
|
||||
var deferredValues = Object.create(null);
|
||||
return promises.forEachSeries(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, context) {
|
||||
return flatMap(elements, function(element) {
|
||||
return elementToHtml(element, messages, context);
|
||||
});
|
||||
}
|
||||
|
||||
function elementToHtml(element, messages, context) {
|
||||
if (!context) {
|
||||
throw new Error("context not set");
|
||||
}
|
||||
var handler = elementConverters[element.type];
|
||||
if (handler) {
|
||||
return handler(element, messages, context);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function convertParagraph(element, messages, context) {
|
||||
return htmlPathForParagraph(element, messages).wrap(function() {
|
||||
var content = convertElements(element.children, messages, context);
|
||||
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, context) {
|
||||
var nodes = function() {
|
||||
return convertElements(run.children, messages, context);
|
||||
};
|
||||
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.try(function() {
|
||||
return convertImage(image, messages);
|
||||
}).catch(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, context) {
|
||||
return findHtmlPath(element, defaultTablePath).wrap(function() {
|
||||
return convertTableChildren(element, messages, context);
|
||||
});
|
||||
}
|
||||
|
||||
function convertTableChildren(element, messages, context) {
|
||||
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({}, context, {isTableHeader: false})
|
||||
);
|
||||
} else {
|
||||
var headRows = convertElements(
|
||||
element.children.slice(0, bodyIndex),
|
||||
messages,
|
||||
_.extend({}, context, {isTableHeader: true})
|
||||
);
|
||||
var bodyRows = convertElements(
|
||||
element.children.slice(bodyIndex),
|
||||
messages,
|
||||
_.extend({}, context, {isTableHeader: false})
|
||||
);
|
||||
children = [
|
||||
Html.freshElement("thead", {}, headRows),
|
||||
Html.freshElement("tbody", {}, bodyRows)
|
||||
];
|
||||
}
|
||||
return [Html.forceWrite].concat(children);
|
||||
}
|
||||
|
||||
function convertTableRow(element, messages, context) {
|
||||
var children = convertElements(element.children, messages, context);
|
||||
return [
|
||||
Html.freshElement("tr", {}, [Html.forceWrite].concat(children))
|
||||
];
|
||||
}
|
||||
|
||||
function convertTableCell(element, messages, context) {
|
||||
var tagName = context.isTableHeader ? "th" : "td";
|
||||
var children = convertElements(element.children, messages, context);
|
||||
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, context) {
|
||||
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, context) {
|
||||
// TODO: remove duplication with note references
|
||||
|
||||
var label = referencedComment.label;
|
||||
var comment = referencedComment.comment;
|
||||
var body = convertElements(comment.body, messages, context).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, context) {
|
||||
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, context) {
|
||||
var children = convertElements(document.children, messages, context);
|
||||
var notes = noteReferences.map(function(noteReference) {
|
||||
return document.notes.resolve(noteReference);
|
||||
});
|
||||
var notesNodes = convertElements(notes, messages, context);
|
||||
return children.concat([
|
||||
Html.freshElement("ol", {}, notesNodes),
|
||||
Html.freshElement("dl", {}, flatMap(referencedComments, function(referencedComment) {
|
||||
return convertComment(referencedComment, messages, context);
|
||||
}))
|
||||
]);
|
||||
},
|
||||
"paragraph": convertParagraph,
|
||||
"run": convertRun,
|
||||
"text": function(element, messages, context) {
|
||||
return [Html.text(element.value)];
|
||||
},
|
||||
"tab": function(element, messages, context) {
|
||||
return [Html.text("\t")];
|
||||
},
|
||||
"hyperlink": function(element, messages, context) {
|
||||
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, context);
|
||||
return [Html.nonFreshElement("a", attributes, children)];
|
||||
},
|
||||
"checkbox": function(element) {
|
||||
var attributes = {type: "checkbox"};
|
||||
if (element.checked) {
|
||||
attributes["checked"] = "checked";
|
||||
}
|
||||
return [Html.freshElement("input", attributes)];
|
||||
},
|
||||
"bookmarkStart": function(element, messages, context) {
|
||||
var anchor = Html.freshElement("a", {
|
||||
id: htmlId(element.name)
|
||||
}, [Html.forceWrite]);
|
||||
return [anchor];
|
||||
},
|
||||
"noteReference": function(element, messages, context) {
|
||||
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, context) {
|
||||
var children = convertElements(element.body, messages, context);
|
||||
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, context) {
|
||||
return [
|
||||
{
|
||||
type: "deferred",
|
||||
id: deferredId++,
|
||||
value: function() {
|
||||
return func(element, messages, context);
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
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 || "";
|
||||
};
|
||||
Reference in new issue
Block a user