alle new
This commit is contained in:
erik committed 2026-10-01 06:46:51 +02:00
1 parent da64472251
commit 5b20adee48
1605 files changed
+396517 -1674

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
{
"globals": {
"__dirname": false,
"describe": false,
"it": false,
"setTimeout": false
}
}
+892
View File
@@ -0,0 +1,892 @@
var assert = require("assert");
var promises = require("../lib/promises");
var documents = require("../lib/documents");
var documentToHtml = require("../lib/document-to-html");
var DocumentConverter = documentToHtml.DocumentConverter;
var commentAuthorLabel = documentToHtml.commentAuthorLabel;
var test = require("./test")(module);
var htmlPaths = require("../lib/styles/html-paths");
var xml = require("../lib/xml");
var results = require("../lib/results");
var documentMatchers = require("../lib/styles/document-matchers");
var Html = require("../lib/html");
test('should empty document to empty string', function() {
var document = new documents.Document([]);
var converter = new DocumentConverter();
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "");
});
});
test('should convert document containing one paragraph to single p element', function() {
var document = new documents.Document([
paragraphOfText("Hello.")
]);
var converter = new DocumentConverter();
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "<p>Hello.</p>");
});
});
test('ignores empty paragraphs', function() {
var document = new documents.Document([
paragraphOfText("")
]);
var converter = new DocumentConverter();
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "");
});
});
test('text is HTML-escaped', function() {
var document = new documents.Document([
paragraphOfText("1 < 2")
]);
var converter = new DocumentConverter();
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "<p>1 &lt; 2</p>");
});
});
test('should convert document containing multiple paragraphs to multiple p elements', function() {
var document = new documents.Document([
paragraphOfText("Hello."),
paragraphOfText("Goodbye.")
]);
var converter = new DocumentConverter();
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "<p>Hello.</p><p>Goodbye.</p>");
});
});
test('uses style mappings to pick HTML element for docx paragraph', function() {
var document = new documents.Document([
paragraphOfText("Hello.", "Heading1", "Heading 1")
]);
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.paragraph({styleName: documentMatchers.equalTo("Heading 1")}),
to: htmlPaths.topLevelElement("h1")
}
]
});
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "<h1>Hello.</h1>");
});
});
test('mappings for style names are case insensitive', function() {
var document = new documents.Document([
paragraphOfText("Hello.", "Heading1", "heading 1")
]);
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.paragraph({styleName: documentMatchers.equalTo("Heading 1")}),
to: htmlPaths.topLevelElement("h1")
}
]
});
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "<h1>Hello.</h1>");
});
});
test('can use non-default HTML element for unstyled paragraphs', function() {
var document = new documents.Document([
paragraphOfText("Hello.")
]);
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.paragraph(),
to: htmlPaths.topLevelElement("h1")
}
]
});
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "<h1>Hello.</h1>");
});
});
test('warning is emitted if paragraph style is unrecognised', function() {
var document = new documents.Document([
paragraphOfText("Hello.", "Heading1", "Heading 1")
]);
var converter = new DocumentConverter();
return converter.convertToHtml(document).then(function(result) {
assert.deepEqual(result.messages, [results.warning("Unrecognised paragraph style: 'Heading 1' (Style ID: Heading1)")]);
});
});
test('can use stacked styles to generate nested HTML elements', function() {
var document = new documents.Document([
paragraphOfText("Hello.")
]);
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.paragraph(),
to: htmlPaths.elements(["h1", "span"])
}
]
});
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, "<h1><span>Hello.</span></h1>");
});
});
test('bold runs are wrapped in <strong> tags by default', function() {
var run = runOfText("Hello.", {isBold: true});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<strong>Hello.</strong>");
});
});
test('bold runs can be configured with style mapping', function() {
var run = runOfText("Hello.", {isBold: true});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.bold,
to: htmlPaths.elements([htmlPaths.element("em")])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<em>Hello.</em>");
});
});
test('bold runs can exist inside other tags', function() {
var run = new documents.Paragraph([
runOfText("Hello.", {isBold: true})
]);
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<p><strong>Hello.</strong></p>");
});
});
test('consecutive bold runs are wrapped in a single <strong> element', function() {
var paragraph = new documents.Paragraph([
runOfText("Hello", {isBold: true}),
runOfText(".", {isBold: true})
]);
var converter = new DocumentConverter();
return converter.convertToHtml(paragraph).then(function(result) {
assert.equal(result.value, "<p><strong>Hello.</strong></p>");
});
});
test('underline runs are ignored by default', function() {
var run = runOfText("Hello.", {isUnderline: true});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "Hello.");
});
});
test('underline runs can be mapped using style mapping', function() {
var run = runOfText("Hello.", {isUnderline: true});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.underline,
to: htmlPaths.elements([htmlPaths.element("u")])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<u>Hello.</u>");
});
});
test('style mapping for underline runs does not close parent elements', function() {
var run = runOfText("Hello.", {isUnderline: true, isBold: true});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.underline,
to: htmlPaths.elements([htmlPaths.element("u")])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<strong><u>Hello.</u></strong>");
});
});
test('strikethrough runs are wrapped in <s> tags by default', function() {
var run = runOfText("Hello.", {isStrikethrough: true});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<s>Hello.</s>");
});
});
test('strikethrough runs can be configured with style mapping', function() {
var run = runOfText("Hello.", {isStrikethrough: true});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.strikethrough,
to: htmlPaths.elements([htmlPaths.element("del")])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<del>Hello.</del>");
});
});
test('italic runs are wrapped in <em> tags', function() {
var run = runOfText("Hello.", {isItalic: true});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<em>Hello.</em>");
});
});
test('italic runs can be configured with style mapping', function() {
var run = runOfText("Hello.", {isItalic: true});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.italic,
to: htmlPaths.elements([htmlPaths.element("strong")])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<strong>Hello.</strong>");
});
});
test('run can be both bold and italic', function() {
var run = runOfText("Hello.", {isBold: true, isItalic: true});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<strong><em>Hello.</em></strong>");
});
});
test('superscript runs are wrapped in <sup> tags', function() {
var run = runOfText("Hello.", {
verticalAlignment: documents.verticalAlignment.superscript
});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<sup>Hello.</sup>");
});
});
test('subscript runs are wrapped in <sub> tags', function() {
var run = runOfText("Hello.", {
verticalAlignment: documents.verticalAlignment.subscript
});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<sub>Hello.</sub>");
});
});
test('all caps runs are ignored by default', function() {
var run = runOfText("Hello.", {isAllCaps: true});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "Hello.");
});
});
test('all caps runs can be configured with style mapping', function() {
var run = runOfText("Hello.", {isAllCaps: true});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.allCaps,
to: htmlPaths.elements([htmlPaths.element("span")])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<span>Hello.</span>");
});
});
test('small caps runs are ignored by default', function() {
var run = runOfText("Hello.", {isSmallCaps: true});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "Hello.");
});
});
test('small caps runs can be configured with style mapping', function() {
var run = runOfText("Hello.", {isSmallCaps: true});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.smallCaps,
to: htmlPaths.elements([htmlPaths.element("span")])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<span>Hello.</span>");
});
});
test('highlighted runs are ignored by default', function() {
var run = runOfText("Hello.", {highlight: "yellow"});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "Hello.");
});
});
test('highlighted runs can be configured with style mapping for all highlights', function() {
var run = runOfText("Hello.", {highlight: "yellow"});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.highlight(null),
to: htmlPaths.elements([htmlPaths.element("mark")])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<mark>Hello.</mark>");
});
});
test('highlighted runs can be configured with style mapping for specific highlight color', function() {
var paragraph = new documents.Paragraph([
runOfText("Yellow", {highlight: "yellow"}),
runOfText("Red", {highlight: "red"})
]);
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.highlight({color: "yellow"}),
to: htmlPaths.elements([htmlPaths.element("mark", {"class": "yellow"})])
},
{
from: documentMatchers.highlight({color: undefined}),
to: htmlPaths.elements([htmlPaths.element("mark")])
}
]
});
return converter.convertToHtml(paragraph).then(function(result) {
assert.equal(result.value, '<p><mark class="yellow">Yellow</mark><mark>Red</mark></p>');
});
});
test('run styles are converted to HTML if mapping exists', function() {
var run = runOfText("Hello.", {styleId: "Heading1Char", styleName: "Heading 1 Char"});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.run({styleName: documentMatchers.equalTo("Heading 1 Char")}),
to: htmlPaths.elements(["strong"])
}
]
});
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, "<strong>Hello.</strong>");
});
});
test('warning is emitted if run style is unrecognised', function() {
var run = runOfText("Hello.", {styleId: "Heading1Char", styleName: "Heading 1 Char"});
var converter = new DocumentConverter();
return converter.convertToHtml(run).then(function(result) {
assert.deepEqual(result.messages, [results.warning("Unrecognised run style: 'Heading 1 Char' (Style ID: Heading1Char)")]);
});
});
test('docx hyperlink is converted to <a>', function() {
var hyperlink = new documents.Hyperlink(
[runOfText("Hello.")],
{href: "http://www.example.com"}
);
var converter = new DocumentConverter();
return converter.convertToHtml(hyperlink).then(function(result) {
assert.equal(result.value, '<a href="http://www.example.com">Hello.</a>');
});
});
test('docx hyperlink can be collapsed', function() {
var hyperlink = new documents.Document([
new documents.Hyperlink(
[runOfText("Hello ")],
{href: "http://www.example.com"}
),
new documents.Hyperlink(
[runOfText("world")],
{href: "http://www.example.com"}
)
]);
var converter = new DocumentConverter();
return converter.convertToHtml(hyperlink).then(function(result) {
assert.equal(result.value, '<a href="http://www.example.com">Hello world</a>');
});
});
test('docx hyperlink with anchor is converted to <a>', function() {
var hyperlink = new documents.Hyperlink(
[runOfText("Hello.")],
{anchor: "_Peter"}
);
var converter = new DocumentConverter({
idPrefix: "doc-42-"
});
return converter.convertToHtml(hyperlink).then(function(result) {
assert.equal(result.value, '<a href="#doc-42-_Peter">Hello.</a>');
});
});
test('hyperlink target frame is used as anchor target', function() {
var hyperlink = new documents.Hyperlink(
[runOfText("Hello.")],
{anchor: "start", targetFrame: "_blank"}
);
var converter = new DocumentConverter();
return converter.convertToHtml(hyperlink).then(function(result) {
assert.equal(result.value, '<a href="#start" target="_blank">Hello.</a>');
});
});
test('unchecked checkbox is converted to unchecked checkbox input', function() {
var checkbox = documents.checkbox({checked: false});
var converter = new DocumentConverter();
return converter.convertToHtml(checkbox).then(function(result) {
assert.equal(result.value, '<input type="checkbox" />');
});
});
test('checked checkbox is converted to checked checkbox input', function() {
var checkbox = documents.checkbox({checked: true});
var converter = new DocumentConverter();
return converter.convertToHtml(checkbox).then(function(result) {
assert.equal(result.value, '<input type="checkbox" checked="checked" />');
});
});
test('bookmarks are converted to anchors', function() {
var bookmarkStart = new documents.BookmarkStart({name: "_Peter"});
var converter = new DocumentConverter({
idPrefix: "doc-42-"
});
var document = new documents.Document([bookmarkStart]);
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, '<a id="doc-42-_Peter"></a>');
});
});
test('docx tab is converted to tab in HTML', function() {
var tab = new documents.Tab();
var converter = new DocumentConverter();
return converter.convertToHtml(tab).then(function(result) {
assert.equal(result.value, "\t");
});
});
test('docx table is converted to table in HTML', function() {
var table = new documents.Table([
new documents.TableRow([
new documents.TableCell([paragraphOfText("Top left")]),
new documents.TableCell([paragraphOfText("Top right")])
]),
new documents.TableRow([
new documents.TableCell([paragraphOfText("Bottom left")]),
new documents.TableCell([paragraphOfText("Bottom right")])
])
]);
var converter = new DocumentConverter();
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = "<table>" +
"<tr><td><p>Top left</p></td><td><p>Top right</p></td></tr>" +
"<tr><td><p>Bottom left</p></td><td><p>Bottom right</p></td></tr>" +
"</table>";
assert.equal(result.value, expectedHtml);
});
});
test('table style mappings can be used to map tables', function() {
var table = new documents.Table([], {styleName: "Normal Table"});
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.table({styleName: documentMatchers.equalTo("Normal Table")}),
to: htmlPaths.topLevelElement("table", {"class": "normal-table"})
}
]
});
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = '<table class="normal-table"></table>';
assert.equal(result.value, expectedHtml);
});
});
test('header rows are wrapped in thead', function() {
var table = new documents.Table([
new documents.TableRow([new documents.TableCell([])], {isHeader: true}),
new documents.TableRow([new documents.TableCell([])], {isHeader: true}),
new documents.TableRow([new documents.TableCell([])], {isHeader: false})
]);
var converter = new DocumentConverter();
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = "<table>" +
"<thead><tr><th></th></tr><tr><th></th></tr></thead>" +
"<tbody><tr><td></td></tr></tbody>" +
"</table>";
assert.equal(result.value, expectedHtml);
});
});
test('tbody is omitted if all rows are headers', function() {
var table = new documents.Table([
new documents.TableRow([new documents.TableCell([])], {isHeader: true})
]);
var converter = new DocumentConverter();
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = "<table>" +
"<thead><tr><th></th></tr></thead>" +
"</table>";
assert.equal(result.value, expectedHtml);
});
});
test('unexpected table children do not cause error', function() {
var table = new documents.Table([
new documents.tab()
]);
var converter = new DocumentConverter();
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = "<table>\t</table>";
assert.equal(result.value, expectedHtml);
});
});
test('empty cells are preserved in table', function() {
var table = new documents.Table([
new documents.TableRow([
new documents.TableCell([paragraphOfText("")]),
new documents.TableCell([paragraphOfText("Top right")])
])
]);
var converter = new DocumentConverter();
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = "<table>" +
"<tr><td></td><td><p>Top right</p></td></tr>" +
"</table>";
assert.equal(result.value, expectedHtml);
});
});
test('empty rows are preserved in table', function() {
var table = new documents.Table([
new documents.TableRow([
new documents.TableCell([paragraphOfText("Row 1")])
]),
new documents.TableRow([])
]);
var converter = new DocumentConverter();
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = "<table>" +
"<tr><td><p>Row 1</p></td></tr><tr></tr>" +
"</table>";
assert.equal(result.value, expectedHtml);
});
});
test('table cells are written with colSpan if not equal to one', function() {
var table = new documents.Table([
new documents.TableRow([
new documents.TableCell([paragraphOfText("Top left")], {colSpan: 2}),
new documents.TableCell([paragraphOfText("Top right")])
])
]);
var converter = new DocumentConverter();
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = "<table>" +
"<tr><td colspan=\"2\"><p>Top left</p></td><td><p>Top right</p></td></tr>" +
"</table>";
assert.equal(result.value, expectedHtml);
});
});
test('table cells are written with rowSpan if not equal to one', function() {
var table = new documents.Table([
new documents.TableRow([
new documents.TableCell([], {rowSpan: 2})
])
]);
var converter = new DocumentConverter();
return converter.convertToHtml(table).then(function(result) {
var expectedHtml = "<table>" +
"<tr><td rowspan=\"2\"></td></tr>" +
"</table>";
assert.equal(result.value, expectedHtml);
});
});
test('line break is converted to <br>', function() {
var converter = new DocumentConverter();
return converter.convertToHtml(documents.lineBreak).then(function(result) {
assert.equal(result.value, "<br />");
});
});
test('breaks that are not line breaks are ignored', function() {
var converter = new DocumentConverter();
return converter.convertToHtml(documents.pageBreak).then(function(result) {
assert.equal(result.value, "");
});
});
test('breaks can be mapped using style mappings', function() {
var converter = new DocumentConverter({
styleMap: [
{
from: documentMatchers.pageBreak,
to: htmlPaths.topLevelElement("hr")
},
{
from: documentMatchers.lineBreak,
to: htmlPaths.topLevelElement("br", {class: "line-break"})
}
]
});
var run = documents.run([documents.pageBreak, documents.lineBreak]);
return converter.convertToHtml(run).then(function(result) {
assert.equal(result.value, '<hr /><br class="line-break" />');
});
});
test('footnote reference is converted to superscript intra-page link', function() {
var footnoteReference = new documents.NoteReference({
noteType: "footnote",
noteId: "4"
});
var converter = new DocumentConverter({
idPrefix: "doc-42-"
});
return converter.convertToHtml(footnoteReference).then(function(result) {
assert.equal(result.value, '<sup><a href="#doc-42-footnote-4" id="doc-42-footnote-ref-4">[1]</a></sup>');
});
});
test('footnotes are included after the main body', function() {
var footnoteReference = new documents.NoteReference({
noteType: "footnote",
noteId: "4"
});
var document = new documents.Document(
[new documents.Paragraph([
runOfText("Knock knock"),
new documents.Run([footnoteReference])
])],
{
notes: new documents.Notes({
4: new documents.Note({
noteType: "footnote",
noteId: "4",
body: [paragraphOfText("Who's there?")]
})
})
}
);
var converter = new DocumentConverter({
idPrefix: "doc-42-"
});
return converter.convertToHtml(document).then(function(result) {
var expectedOutput = '<p>Knock knock<sup><a href="#doc-42-footnote-4" id="doc-42-footnote-ref-4">[1]</a></sup></p>' +
'<ol><li id="doc-42-footnote-4"><p>Who\'s there? <a href="#doc-42-footnote-ref-4">↑</a></p></li></ol>';
assert.equal(result.value, expectedOutput);
});
});
test('comments are ignored by default', function() {
var reference = documents.commentReference({commentId: "4"});
var comment = documents.comment({
commentId: "4",
body: [paragraphOfText("Who's there?")]
});
var document = documents.document([
documents.paragraph([
runOfText("Knock knock"),
documents.run([reference])
])
], {comments: [comment]});
var converter = new DocumentConverter({});
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, '<p>Knock knock</p>');
assert.deepEqual(result.messages, []);
});
});
test('comment references are linked to comment after main body', function() {
var reference = documents.commentReference({commentId: "4"});
var comment = documents.comment({
commentId: "4",
body: [paragraphOfText("Who's there?")],
authorName: "The Piemaker",
authorInitials: "TP"
});
var document = documents.document([
documents.paragraph([
runOfText("Knock knock"),
documents.run([reference])
])
], {comments: [comment]});
var converter = new DocumentConverter({
idPrefix: "doc-42-",
styleMap: [
{from: documentMatchers.commentReference, to: htmlPaths.element("sup")}
]
});
return converter.convertToHtml(document).then(function(result) {
var expectedHtml = (
'<p>Knock knock<sup><a href="#doc-42-comment-4" id="doc-42-comment-ref-4">[TP1]</a></sup></p>' +
'<dl><dt id="doc-42-comment-4">Comment [TP1]</dt><dd><p>Who\'s there? <a href="#doc-42-comment-ref-4">↑</a></p></dd></dl>'
);
assert.equal(result.value, expectedHtml);
assert.deepEqual(result.messages, []);
});
});
test('images are written with data URIs', function() {
var imageBuffer = new Buffer("Not an image at all!");
var image = new documents.Image({
readImage: function(encoding) {
return promises.resolve(imageBuffer.toString(encoding));
},
contentType: "image/png"
});
var converter = new DocumentConverter();
return converter.convertToHtml(image).then(function(result) {
assert.equal(result.value, '<img src="data:image/png;base64,' + imageBuffer.toString("base64") + '" />');
});
});
test('images have alt attribute if available', function() {
var imageBuffer = new Buffer("Not an image at all!");
var image = new documents.Image({
readImage: function() {
return promises.resolve(imageBuffer);
},
altText: "It's a hat"
});
var converter = new DocumentConverter();
return converter.convertToHtml(image)
.then(function(result) {
return xml.readString(result.value);
})
.then(function(htmlImageElement) {
assert.equal(htmlImageElement.attributes.alt, "It's a hat");
});
});
test('can add custom handler for images', function() {
var imageBuffer = new Buffer("Not an image at all!");
var image = new documents.Image({
readImage: function(encoding) {
return promises.resolve(imageBuffer.toString(encoding));
},
contentType: "image/png"
});
var converter = new DocumentConverter({
convertImage: function(element, messages) {
return element.read("utf8").then(function(altText) {
return [Html.freshElement("img", {alt: altText})];
});
}
});
return converter.convertToHtml(image).then(function(result) {
assert.equal(result.value, '<img alt="Not an image at all!" />');
});
});
test('when custom image handler throws error then error is stored in error message', function() {
var error = new Error("Failed to convert image");
var image = new documents.Image({
readImage: function(encoding) {
return promises.resolve(new Buffer().toString(encoding));
},
contentType: "image/png"
});
var converter = new DocumentConverter({
convertImage: function(element, messages) {
throw error;
}
});
return converter.convertToHtml(image).then(function(result) {
assert.equal(result.value, '');
assert.equal(result.messages.length, 1);
var message = result.messages[0];
assert.equal("error", message.type);
assert.equal("Failed to convert image", message.message);
assert.equal(error, message.error);
});
});
test('long documents do not cause stack overflow', function() {
var paragraphs = [];
for (var i = 0; i < 1000; i++) {
paragraphs.push(paragraphOfText("Hello."));
}
var document = new documents.Document(paragraphs);
var converter = new DocumentConverter();
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value.indexOf("<p>Hello.</p>"), 0);
});
});
function paragraphOfText(text, styleId, styleName) {
var run = runOfText(text);
return new documents.Paragraph([run], {
styleId: styleId,
styleName: styleName
});
}
function runOfText(text, properties) {
var textElement = new documents.Text(text);
return new documents.Run([textElement], properties);
}
test('when initials are not blank then comment author label is initials', function() {
assert.equal(commentAuthorLabel({authorInitials: "TP"}), "TP");
});
test('when initials are blank then comment author label is blank', function() {
assert.equal(commentAuthorLabel({authorInitials: ""}), "");
assert.equal(commentAuthorLabel({authorInitials: undefined}), "");
assert.equal(commentAuthorLabel({authorInitials: null}), "");
});
File diff suppressed because it is too large. Load diff
+53
View File
@@ -0,0 +1,53 @@
var assert = require("assert");
var createCommentsReader = require("../../lib/docx/comments-reader").createCommentsReader;
var createBodyReader = require("../../lib/docx/body-reader").createBodyReader;
var stylesReader = require("../../lib/docx/styles-reader");
var documents = require("../../lib/documents");
var xml = require("../../lib/xml");
var test = require("../test")(module);
function readComment(element) {
var bodyReader = createBodyReader({styles: stylesReader.defaultStyles});
var commentsReader = createCommentsReader(bodyReader);
var comments = commentsReader(element);
assert.equal(comments.value.length, 1);
return comments.value[0];
}
test('ID and body of comment are read', function() {
var body = [xml.element("w:p")];
var comment = readComment(xml.element("w:comments", {}, [
xml.element("w:comment", {"w:id": "1"}, body)
]));
assert.deepEqual(comment.body, [new documents.Paragraph([])]);
assert.deepEqual(comment.commentId, "1");
});
test('when optional attributes of comment are missing then they are read as null', function() {
var comment = readComment(xml.element("w:comments", {}, [
xml.element("w:comment", {"w:id": "1"})
]));
assert.strictEqual(comment.authorName, null);
assert.strictEqual(comment.authorInitials, null);
});
test('when optional attributes of comment are blank then they are read as null', function() {
var comment = readComment(xml.element("w:comments", {}, [
xml.element("w:comment", {"w:id": "1", "w:author": " ", "w:initials": " "})
]));
assert.strictEqual(comment.authorName, null);
assert.strictEqual(comment.authorInitials, null);
});
test('when optional attributes of comment are not blank then they are read', function() {
var comment = readComment(xml.element("w:comments", {}, [
xml.element("w:comment", {"w:id": "1", "w:author": "The Piemaker", "w:initials": "TP"})
]));
assert.strictEqual(comment.authorName, "The Piemaker");
assert.strictEqual(comment.authorInitials, "TP");
});
+45
View File
@@ -0,0 +1,45 @@
var assert = require("assert");
var readContentTypesFromXml = require("../../lib/docx/content-types-reader").readContentTypesFromXml;
var XmlElement = require("../../lib/xml").Element;
var test = require("../test")(module);
test('reads default-per-extension from XML', function() {
var contentTypes = readContentTypesFromXml(
new XmlElement("content-types:Types", {}, [
new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"})
])
);
assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png");
});
test('reads overrides in preference to defaults', function() {
var contentTypes = readContentTypesFromXml(
new XmlElement("content-types:Types", {}, [
new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}),
new XmlElement("content-types:Override", {PartName: "/word/media/hat.png", ContentType: "image/hat"})
])
);
assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/hat");
});
test('fallback content types have common image types', function() {
var contentTypes = readContentTypesFromXml(
new XmlElement("content-types:Types", {}, [])
);
assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png");
assert.equal(contentTypes.findContentType("word/media/hat.gif"), "image/gif");
assert.equal(contentTypes.findContentType("word/media/hat.jpg"), "image/jpeg");
assert.equal(contentTypes.findContentType("word/media/hat.jpeg"), "image/jpeg");
assert.equal(contentTypes.findContentType("word/media/hat.bmp"), "image/bmp");
assert.equal(contentTypes.findContentType("word/media/hat.tif"), "image/tiff");
assert.equal(contentTypes.findContentType("word/media/hat.tiff"), "image/tiff");
});
test('fallback content types are case insensitive on extension', function() {
var contentTypes = readContentTypesFromXml(
new XmlElement("content-types:Types", {}, [])
);
assert.equal(contentTypes.findContentType("word/media/hat.PnG"), "image/png");
});
+42
View File
@@ -0,0 +1,42 @@
var hamjest = require("hamjest");
var _ = require("underscore");
var documents = require("../../lib/documents");
exports.isEmptyRun = isRun({children: []});
exports.isRun = isRun;
exports.isText = isText;
exports.isCheckbox = isCheckbox;
exports.isHyperlink = isHyperlink;
exports.isTable = isTable;
exports.isRow = isRow;
function isRun(properties) {
return isDocumentElement(documents.types.run, properties);
}
function isText(text) {
return isDocumentElement(documents.types.text, {value: text});
}
function isCheckbox(properties) {
return isDocumentElement(documents.types.checkbox, properties);
}
function isHyperlink(properties) {
return isDocumentElement(documents.types.hyperlink, properties);
}
function isTable(options) {
return isDocumentElement(documents.types.table, options);
}
function isRow(options) {
return isDocumentElement(documents.types.tableRow, options);
}
function isDocumentElement(type, properties) {
return hamjest.hasProperties(_.extend({type: hamjest.equalTo(type)}, properties));
}
+41
View File
@@ -0,0 +1,41 @@
var assert = require("assert");
var documents = require("../../lib/documents");
var DocumentXmlReader = require("../../lib/docx/document-xml-reader").DocumentXmlReader;
var xml = require("../../lib/xml");
var test = require("../test")(module);
var createBodyReaderForTests = require("./testing").createBodyReaderForTests;
test("when body element is present then body is read", function() {
var bodyReader = createBodyReaderForTests({});
var documentXmlReader = new DocumentXmlReader({
bodyReader: bodyReader
});
var textXml = xml.element("w:t", {}, [xml.text("Hello!")]);
var runXml = xml.element("w:r", {}, [textXml]);
var paragraphXml = xml.element("w:p", {}, [runXml]);
var bodyXml = xml.element("w:body", {}, [paragraphXml]);
var documentXml = xml.element("w:document", {}, [bodyXml]);
var result = documentXmlReader.convertXmlToDocument(documentXml);
assert.deepEqual(result.messages, []);
assert.deepEqual(result.value, documents.document(
[documents.paragraph([documents.run([documents.text("Hello!")])])],
{}
));
});
test("when body element is not present then error is thrown", function() {
var bodyReader = createBodyReaderForTests({});
var documentXmlReader = new DocumentXmlReader({
bodyReader: bodyReader
});
var paragraphXml = xml.element("w:p", {}, []);
var bodyXml = xml.element("w:body2", {}, [paragraphXml]);
var documentXml = xml.element("w:document", {}, [bodyXml]);
assert.throws(function() {
documentXmlReader.convertXmlToDocument(documentXml);
}, /Could not find the body element: are you sure this is a docx file?/);
});
+179
View File
@@ -0,0 +1,179 @@
var assert = require("assert");
var docxReader = require("../../lib/docx/docx-reader");
var documents = require("../../lib/documents");
var xml = require("../../lib/xml");
var testing = require("../testing");
var test = require("../test")(module);
var testData = testing.testData;
var createFakeDocxFile = testing.createFakeDocxFile;
test("can read document with single paragraph with single run of text", function() {
var expectedDocument = documents.Document([
documents.Paragraph([
documents.Run([
documents.Text("Hello.")
])
])
]);
var docxFile = createFakeDocxFile({
"word/document.xml": testData("simple/word/document.xml")
});
return docxReader.read(docxFile).then(function(result) {
assert.deepEqual(expectedDocument, result.value);
});
});
test("hyperlink hrefs are read from relationships file", function() {
var docxFile = createFakeDocxFile({
"word/document.xml": testData("hyperlinks/word/document.xml"),
"word/_rels/document.xml.rels": testData("hyperlinks/word/_rels/document.xml.rels")
});
return docxReader.read(docxFile).then(function(result) {
var paragraph = result.value.children[0];
assert.equal(1, paragraph.children.length);
var hyperlink = paragraph.children[0];
assert.equal(hyperlink.href, "http://www.example.com");
assert.equal(hyperlink.children.length, 1);
});
});
var relationshipNamespaces = {
"r": "http://schemas.openxmlformats.org/package/2006/relationships"
};
test("main document is found using _rels/.rels", function() {
var relationships = xml.element("r:Relationships", {}, [
xml.element("r:Relationship", {
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
"Target": "/word/document2.xml"
})
]);
var docxFile = createFakeDocxFile({
"word/document2.xml": testData("simple/word/document.xml"),
"_rels/.rels": xml.writeString(relationships, relationshipNamespaces)
});
var expectedDocument = documents.Document([
documents.Paragraph([
documents.Run([
documents.Text("Hello.")
])
])
]);
return docxReader.read(docxFile).then(function(result) {
assert.deepEqual(expectedDocument, result.value);
});
});
test("error is thrown when main document part does not exist", function() {
var relationships = xml.element("r:Relationships", {}, [
xml.element("r:Relationship", {
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
"Target": "/word/document2.xml"
})
]);
var docxFile = createFakeDocxFile({
"_rels/.rels": xml.writeString(relationships, relationshipNamespaces)
});
return docxReader.read(docxFile).then(function(result) {
assert.ok(false, "Expected error");
}, function(error) {
assert.equal(error.message, "Could not find main document part. Are you sure this is a valid .docx file?");
});
});
test("part paths", {
"main document part is found using package relationships": function() {
var relationships = xml.element("r:Relationships", {}, [
xml.element("r:Relationship", {
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
"Target": "/word/document2.xml"
})
]);
var docxFile = createFakeDocxFile({
"word/document2.xml": " ",
"_rels/.rels": xml.writeString(relationships, relationshipNamespaces)
});
return docxReader._findPartPaths(docxFile).then(function(partPaths) {
assert.equal(partPaths.mainDocument, "word/document2.xml");
});
},
"word/document.xml is used as fallback location for main document part": function() {
var docxFile = createFakeDocxFile({
"word/document.xml": " "
});
return docxReader._findPartPaths(docxFile).then(function(partPaths) {
assert.equal(partPaths.mainDocument, "word/document.xml");
});
}
});
[
{
name: "comments",
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
},
{
name: "endnotes",
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes"
},
{
name: "footnotes",
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"
},
{
name: "numbering",
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering"
},
{
name: "styles",
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
}
].forEach(function(options) {
test(options.name + " part is found using main document relationships", function() {
var docxFile = createFakeDocxFile({
"_rels/.rels": createPackageRelationships("word/document.xml"),
"word/document.xml": " ",
"word/_rels/document.xml.rels": xml.writeString(xml.element("r:Relationships", {}, [
xml.element("r:Relationship", {
"Type": options.type,
"Target": "target-path.xml"
})
]), relationshipNamespaces),
"word/target-path.xml": " "
});
return docxReader._findPartPaths(docxFile).then(function(partPaths) {
assert.equal(partPaths[options.name], "word/target-path.xml");
});
});
test("word/" + options.name + ".xml is used as fallback location for " + options.name + " part", function() {
var zipContents = {
"_rels/.rels": createPackageRelationships("word/document.xml"),
"word/document.xml": " "
};
zipContents["word/" + options.name + ".xml"] = " ";
var docxFile = createFakeDocxFile(zipContents);
return docxReader._findPartPaths(docxFile).then(function(partPaths) {
assert.equal(partPaths[options.name], "word/" + options.name + ".xml");
});
});
});
function createPackageRelationships(mainDocumentPath) {
return xml.writeString(xml.element("r:Relationships", {}, [
xml.element("r:Relationship", {
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
"Target": mainDocumentPath
})
]), relationshipNamespaces);
}
+104
View File
@@ -0,0 +1,104 @@
var path = require("path");
var assert = require("assert");
var Files = require("../../lib/docx/files").Files;
var uriToPath = require("../../lib/docx/files").uriToPath;
var fs = require("../../lib/fs");
var testing = require("../testing");
var test = require("../test")(module);
test("Files", {
"when external file access is disabled then reading file raises error": function() {
var files = new Files({externalFileAccess: false});
return assertError(files.read("/tmp/image.png", "base64"), function(err) {
assert.strictEqual(err.message, "could not read external image '/tmp/image.png', external file access is disabled");
});
},
"can open files with file URI": function() {
var filePath = path.resolve(testing.testPath("tiny-picture.png"));
var files = new Files({externalFileAccess: true});
return files.read("file:///" + filePath.replace(/^\//, ""), "base64").then(function(contents) {
return fs.readFile(filePath, "base64").then(function(expectedContents) {
assert.deepEqual(contents, expectedContents);
});
});
},
"can open files with relative URI": function() {
var filePath = path.resolve(testing.testPath("tiny-picture.png"));
var files = new Files({
externalFileAccess: true,
relativeToFile: testing.testPath("./document.docx")
});
return files.read("tiny-picture.png", "base64").then(function(contents) {
return fs.readFile(filePath, "base64").then(function(expectedContents) {
assert.deepEqual(contents, expectedContents);
});
});
},
"given base is not set when opening relative uri then error is raised": function() {
var files = new Files({externalFileAccess: true});
return assertError(files.read("not-a-real-file.png", "base64"), function(err) {
assert.equal(err.message, "could not find external image 'not-a-real-file.png', path of input document is unknown");
});
},
"error if relative uri cannot be opened": function() {
var files = new Files({
externalFileAccess: true,
relativeToFile: "/tmp/document.docx"
});
return assertError(files.read("not-a-real-file.png", "base64"), function(err) {
assertRegex(err.message, /could not open external image: 'not-a-real-file.png' \(document directory: '\/tmp'\)\nENOENT.*\/tmp\/not-a-real-file.png.*/);
});
}
});
function assertError(promise, func) {
return promise.then(function() {
assert(false, "Expected error");
}, func);
}
function assertRegex(actual, expected) {
assert.ok(expected.test(actual), "Expected regex: " + expected + "\nbut was: " + actual);
}
test("uriToPath", {
"leading slash is retained on non-Windows file URIs": function() {
assert.equal(uriToPath("file:///a/b/c", "linux"), "/a/b/c");
assert.equal(uriToPath("file:///a/b/c", "win32"), "/a/b/c");
},
"URI is unquoted": function() {
assert.equal(uriToPath("file:///a%20b"), "/a b");
},
"when host is set to localhost then path can be found": function() {
assert.equal(uriToPath("file://localhost/a/b/c"), "/a/b/c");
},
"when host is set but not localhost then path cannot be found": function() {
assert.throws(function() {
uriToPath("file://example/a/b/c");
}, /Could not convert URI to path: file:\/\/example\/a\/b\/c/);
},
"leading slash is not dropped on Windows file URIs when platform is not Windows": function() {
assert.equal(uriToPath("file:///c:/a", "linux"), "/c:/a");
},
"leading slash is dropped on Windows file URIs when platform is Windows": function() {
assert.equal(uriToPath("file:///c:/a", "win32"), "c:/a");
assert.equal(uriToPath("file:///C:/a", "win32"), "C:/a");
},
"relative URI is unquoted": function() {
assert.equal(uriToPath("a%20b/c"), "a b/c");
}
});
+36
View File
@@ -0,0 +1,36 @@
var assert = require("assert");
var createFootnotesReader = require("../../lib/docx/notes-reader").createFootnotesReader;
var createBodyReader = require("../../lib/docx/body-reader").createBodyReader;
var stylesReader = require("../../lib/docx/styles-reader");
var documents = require("../../lib/documents");
var XmlElement = require("../../lib/xml").Element;
var test = require("../test")(module);
test('ID and body of footnote are read', function() {
var bodyReader = new createBodyReader({styles: stylesReader.defaultStyles});
var footnoteBody = [new XmlElement("w:p", {}, [])];
var footnotes = createFootnotesReader(bodyReader)(
new XmlElement("w:footnotes", {}, [
new XmlElement("w:footnote", {"w:id": "1"}, footnoteBody)
])
);
assert.equal(footnotes.value.length, 1);
assert.deepEqual(footnotes.value[0].body, [new documents.Paragraph([])]);
assert.deepEqual(footnotes.value[0].noteId, "1");
});
footnoteTypeIsIgnored('continuationSeparator');
footnoteTypeIsIgnored('separator');
function footnoteTypeIsIgnored(type) {
test('footnotes of type ' + type + ' are ignored', function() {
var footnotes = createFootnotesReader()(
new XmlElement("w:footnotes", {}, [
new XmlElement("w:footnote", {"w:id": "1", "w:type": type}, [])
])
);
assert.equal(footnotes.value.length, 0);
});
}
+199
View File
@@ -0,0 +1,199 @@
var assert = require("assert");
var duck = require("duck");
var readNumberingXml = require("../../lib/docx/numbering-xml").readNumberingXml;
var stylesReader = require("../../lib/docx/styles-reader");
var XmlElement = require("../../lib/xml").Element;
var test = require("../test")(module);
test('w:num element inherits levels from w:abstractNum', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
new XmlElement("w:numFmt", {"w:val": "bullet"})
]),
new XmlElement("w:lvl", {"w:ilvl": "1"}, [
new XmlElement("w:numFmt", {"w:val": "decimal"})
])
]),
new XmlElement("w:num", {"w:numId": "47"}, [
new XmlElement("w:abstractNumId", {"w:val": "42"})
])
]),
{styles: stylesReader.defaultStyles}
);
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
isOrdered: false
}));
duck.assertThat(numbering.findLevel("47", "1"), duck.hasProperties({
isOrdered: true
}));
});
test('w:num element referencing non-existent w:abstractNumId is ignored', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:num", {"w:numId": "47"}, [
new XmlElement("w:abstractNumId", {"w:val": "42"})
])
]),
{styles: stylesReader.defaultStyles}
);
duck.assertThat(numbering.findLevel("47", "0"), duck.equalTo(null));
});
test('given no other levels with index of 0 when level is missing w:ilvl then level index is 0', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
new XmlElement("w:lvl", {}, [
new XmlElement("w:numFmt", {"w:val": "decimal"})
])
]),
new XmlElement("w:num", {"w:numId": "47"}, [
new XmlElement("w:abstractNumId", {"w:val": "42"})
])
]),
{styles: stylesReader.defaultStyles}
);
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
isOrdered: true
}));
});
test('given previous other level with index of 0 when level is missing w:ilvl then level is ignored', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
new XmlElement("w:numFmt", {"w:val": "bullet"})
]),
new XmlElement("w:lvl", {}, [
new XmlElement("w:numFmt", {"w:val": "decimal"})
])
]),
new XmlElement("w:num", {"w:numId": "47"}, [
new XmlElement("w:abstractNumId", {"w:val": "42"})
])
]),
{styles: stylesReader.defaultStyles}
);
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
isOrdered: false
}));
});
test('given subsequent other level with index of 0 when level is missing w:ilvl then level is ignored', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
new XmlElement("w:lvl", {}, [
new XmlElement("w:numFmt", {"w:val": "decimal"})
]),
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
new XmlElement("w:numFmt", {"w:val": "bullet"})
])
]),
new XmlElement("w:num", {"w:numId": "47"}, [
new XmlElement("w:abstractNumId", {"w:val": "42"})
])
]),
{styles: stylesReader.defaultStyles}
);
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
isOrdered: false
}));
});
test('when level is missing w:numFmt then level is ordered', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
new XmlElement("w:lvl", {"w:ilvl": "0"})
]),
new XmlElement("w:num", {"w:numId": "47"}, [
new XmlElement("w:abstractNumId", {"w:val": "42"})
])
]),
{styles: stylesReader.defaultStyles}
);
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
isOrdered: true
}));
});
test('when w:abstractNum has w:numStyleLink then style is used to find w:num', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:abstractNum", {"w:abstractNumId": "100"}, [
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
new XmlElement("w:numFmt", {"w:val": "decimal"})
])
]),
new XmlElement("w:abstractNum", {"w:abstractNumId": "101"}, [
new XmlElement("w:numStyleLink", {"w:val": "List1"})
]),
new XmlElement("w:num", {"w:numId": "200"}, [
new XmlElement("w:abstractNumId", {"w:val": "100"})
]),
new XmlElement("w:num", {"w:numId": "201"}, [
new XmlElement("w:abstractNumId", {"w:val": "101"})
])
]),
{styles: new stylesReader.Styles({}, {}, {}, {"List1": {numId: "200"}})}
);
duck.assertThat(numbering.findLevel("201", "0"), duck.hasProperties({
isOrdered: true
}));
});
test('when w:abstractNum has self-recursive w:numStyleLink then level is not found', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:abstractNum", {"w:abstractNumId": "100"}, [
new XmlElement("w:numStyleLink", {"w:val": "List1"})
]),
new XmlElement("w:num", {"w:numId": "200"}, [
new XmlElement("w:abstractNumId", {"w:val": "100"})
])
]),
{styles: new stylesReader.Styles({}, {}, {}, {"List1": {numId: "200"}})}
);
duck.assertThat(numbering.findLevel("200", "0"), duck.equalTo(null));
});
// See: 17.9.23 pStyle (Paragraph Style's Associated Numbering Level) in ECMA-376, 4th Edition
test('numbering level can be found by paragraph style ID', function() {
var numbering = readNumberingXml(
new XmlElement("w:numbering", {}, [
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
new XmlElement("w:numFmt", {"w:val": "bullet"})
])
]),
new XmlElement("w:abstractNum", {"w:abstractNumId": "43"}, [
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
new XmlElement("w:pStyle", {"w:val": "List"}),
new XmlElement("w:numFmt", {"w:val": "decimal"})
])
])
]),
{styles: stylesReader.defaultStyles}
);
duck.assertThat(numbering.findLevelByParagraphStyleId("List"), duck.hasProperties({
isOrdered: true
}));
duck.assertThat(numbering.findLevelByParagraphStyleId("Paragraph"), duck.equalTo(null));
});
test('when styles is missing then error is thrown', function() {
assert.throws(function() {
readNumberingXml(new XmlElement("w:numbering", {}, []));
}, /styles is missing/);
});
+41
View File
@@ -0,0 +1,41 @@
var assert = require("assert");
var xml = require("../../lib/xml");
var officeXmlReader = require("../../lib/docx/office-xml-reader");
var test = require("../test")(module);
test("mc:AlternateContent", {
"when mc:Fallback is present then mc:Fallback is read": function() {
var xmlString =
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<numbering xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">' +
'<mc:AlternateContent>' +
'<mc:Choice Requires="w14">' +
'<choice/>' +
'</mc:Choice>' +
'<mc:Fallback>' +
'<fallback/>' +
'</mc:Fallback>' +
'</mc:AlternateContent>' +
'</numbering>';
return officeXmlReader.read(xmlString).then(function(element) {
assert.deepEqual(element.children, [xml.element("fallback")]);
});
},
"when mc:Fallback is not present then element is ignored": function() {
var xmlString =
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<numbering xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">' +
'<mc:AlternateContent>' +
'<mc:Choice Requires="w14">' +
'<choice/>' +
'</mc:Choice>' +
'</mc:AlternateContent>' +
'</numbering>';
return officeXmlReader.read(xmlString).then(function(element) {
assert.deepEqual(element.children, []);
});
}
});
+65
View File
@@ -0,0 +1,65 @@
var assert = require("assert");
var readRelationships = require("../../lib/docx/relationships-reader").readRelationships;
var xml = require("../../lib/xml");
var test = require("../test")(module);
test("relationships can be found by ID", function() {
var relationships = readRelationships(relationshipsElement([
relationshipElement({
"Id": "rId1",
"Target": "http://example.com/",
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
}),
relationshipElement({
"Id": "rId2",
"Target": "http://example.net/",
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
})
]));
assert.equal(relationships.findTargetByRelationshipId("rId1"), "http://example.com/");
});
test("relationships can be found by type", function() {
var relationships = readRelationships(relationshipsElement([
relationshipElement({
"Id": "rId2",
"Target": "docProps/core.xml",
"Type": "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
}),
relationshipElement({
"Id": "rId1",
"Target": "word/document.xml",
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
}),
relationshipElement({
"Id": "rId3",
"Target": "word/document2.xml",
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
})
]));
assert.deepEqual(
relationships.findTargetsByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"),
["word/document.xml", "word/document2.xml"]
);
});
test("when there are no relationships of requested type then empty array is returned", function() {
var relationships = readRelationships(relationshipsElement([]));
assert.deepEqual(
relationships.findTargetsByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"),
[]
);
});
function relationshipsElement(children) {
return xml.element("relationships:Relationships", {}, children);
}
function relationshipElement(attributes) {
return xml.element("relationships:Relationship", attributes, []);
}
+113
View File
@@ -0,0 +1,113 @@
var assert = require("assert");
var JSZip = require("jszip");
var zipfile = require("../../lib/zipfile");
var styleMap = require("../../lib/docx/style-map");
var test = require("../test")(module);
test('reading embedded style map on document without embedded style map returns null', function() {
return normalDocx().then(function(zip) {
return styleMap.readStyleMap(zip).then(function(contents) {
assert.equal(contents, null);
});
});
});
test('embedded style map can be read after being written', function() {
return normalDocx().then(function(zip) {
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
return styleMap.readStyleMap(zip).then(function(contents) {
assert.equal(contents, "p => h1");
});
});
});
});
test('embedded style map is written to separate file', function() {
return normalDocx().then(function(zip) {
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
return zip.read("mammoth/style-map", "utf8").then(function(contents) {
assert.equal(contents, "p => h1");
});
});
});
});
test('embedded style map is referenced in relationships', function() {
return normalDocx().then(function(zip) {
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
return zip.read("word/_rels/document.xml.rels", "utf8").then(function(contents) {
assert.equal(contents, expectedRelationshipsXml);
});
});
});
});
test('re-embedding style map replaces original', function() {
return normalDocx().then(function(zip) {
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
return styleMap.writeStyleMap(zip, "p => h2");
}).then(function() {
return zip.read("word/_rels/document.xml.rels", "utf8").then(function(contents) {
assert.equal(contents, expectedRelationshipsXml);
});
}).then(function() {
return styleMap.readStyleMap(zip).then(function(contents) {
assert.equal(contents, "p => h2");
});
});
});
});
test('embedded style map has override content type in [Content_Types].xml', function() {
return normalDocx().then(function(zip) {
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
return zip.read("[Content_Types].xml", "utf8").then(function(contents) {
assert.equal(contents, expectedContentTypesXml);
});
});
});
});
test('replacing style map keeps content type', function() {
return normalDocx().then(function(zip) {
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
return styleMap.writeStyleMap(zip, "p => h2");
}).then(function() {
return zip.read("[Content_Types].xml", "utf8").then(function(contents) {
assert.equal(contents, expectedContentTypesXml);
});
});
});
});
var expectedRelationshipsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
'<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>' +
'<Relationship Id="rMammothStyleMap" Type="http://schemas.zwobble.org/mammoth/style-map" Target="/mammoth/style-map"/>' +
'</Relationships>';
var expectedContentTypesXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
'<Default Extension="png" ContentType="image/png"/>' +
'<Override PartName="/mammoth/style-map" ContentType="text/prs.mammoth.style-map"/>' +
'</Types>';
function normalDocx() {
var zip = new JSZip();
var originalRelationshipsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
'<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>' +
'</Relationships>';
var originalContentTypesXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
'<Default Extension="png" ContentType="image/png"/>' +
'</Types>';
zip.file("word/_rels/document.xml.rels", originalRelationshipsXml);
zip.file("[Content_Types].xml", originalContentTypesXml);
return zip.generateAsync({type: "arraybuffer"}).then(function(buffer) {
return zipfile.openArrayBuffer(buffer);
});
}
+155
View File
@@ -0,0 +1,155 @@
var assert = require("assert");
var readStylesXml = require("../../lib/docx/styles-reader").readStylesXml;
var XmlElement = require("../../lib/xml").Element;
var test = require("../test")(module);
test('paragraph style is null if no style with that ID exists', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [])
);
assert.equal(styles.findParagraphStyleById("Heading1"), null);
});
test('paragraph style can be found by ID', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
paragraphStyleElement("Heading1", "Heading 1")
])
);
assert.equal(styles.findParagraphStyleById("Heading1").styleId, "Heading1");
});
test('table style can be found by ID', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
tableStyleElement("TableNormal", "Normal Table")
])
);
assert.equal(styles.findTableStyleById("TableNormal").styleId, "TableNormal");
});
test('character style can be found by ID', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
characterStyleElement("Heading1Char", "Heading 1 Char")
])
);
assert.equal(styles.findCharacterStyleById("Heading1Char").styleId, "Heading1Char");
});
test('paragraph and character styles are distinct', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
paragraphStyleElement("Heading1", "Heading 1"),
characterStyleElement("Heading1Char", "Heading 1 Char")
])
);
assert.equal(styles.findCharacterStyleById("Heading1"), null);
assert.equal(styles.findParagraphStyleById("Heading1Char"), null);
});
test('character and table styles are distinct', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
tableStyleElement("Heading1", "Heading 1")
])
);
assert.equal(styles.findCharacterStyleById("Heading1"), null);
});
test('styles include names', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
paragraphStyleElement("Heading1", "Heading 1")
])
);
assert.equal(styles.findParagraphStyleById("Heading1").name, "Heading 1");
});
test('style name is null if w:name element does not exist', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
styleWithoutWNameElement("paragraph", "Heading1"),
styleWithoutWNameElement("character", "Heading1Char")
])
);
assert.equal(styles.findParagraphStyleById("Heading1").name, null);
assert.equal(styles.findCharacterStyleById("Heading1Char").name, null);
});
test('numbering style is null if no style with that ID exists', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [])
);
assert.equal(styles.findNumberingStyleById("List1"), null);
});
test('numbering style has null numId if style has no paragraph properties', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
new XmlElement("w:style", {"w:type": "numbering", "w:styleId": "List1"})
])
);
assert.equal(styles.findNumberingStyleById("List1").numId, null);
});
test('numbering style has numId read from paragraph properties', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
new XmlElement("w:style", {"w:type": "numbering", "w:styleId": "List1"}, [
new XmlElement("w:pPr", {}, [
new XmlElement("w:numPr", {}, [
new XmlElement("w:numId", {"w:val": "42"})
])
])
])
])
);
assert.equal(styles.findNumberingStyleById("List1").numId, "42");
});
test('when multiple style elements have same style ID then only first element is used', function() {
var styles = readStylesXml(
new XmlElement("w:styles", {}, [
tableStyleElement("TableNormal", "Normal Table"),
tableStyleElement("TableNormal", "Table Normal")
])
);
assert.equal(styles.findTableStyleById("TableNormal").name, "Normal Table");
});
test('w:type of __proto__ does not cause prototype pollution', function() {
readStylesXml(
new XmlElement("w:styles", {}, [
new XmlElement("w:style", {"w:type": "__proto__", "w:styleId": "List1"}, [
new XmlElement("w:name", {"w:val": "List 1"}, [])
])
])
);
assert.equal({}["List1"], undefined);
});
function paragraphStyleElement(id, name) {
return styleElement("paragraph", id, name);
}
function characterStyleElement(id, name) {
return styleElement("character", id, name);
}
function tableStyleElement(id, name) {
return styleElement("table", id, name);
}
function styleElement(type, id, name) {
return new XmlElement("w:style", {"w:type": type, "w:styleId": id}, [
new XmlElement("w:name", {"w:val": name}, [])
]);
}
function styleWithoutWNameElement(type, id) {
return new XmlElement("w:style", {"w:type": type, "w:styleId": id}, []);
}
+12
View File
@@ -0,0 +1,12 @@
var createBodyReader = require("../../lib/docx/body-reader").createBodyReader;
var defaultNumbering = require("../../lib/docx/numbering-xml").defaultNumbering;
var Styles = require("../../lib/docx/styles-reader").Styles;
function createBodyReaderForTests(options) {
options = Object.create(options || {});
options.styles = options.styles || new Styles({}, {});
options.numbering = options.numbering || defaultNumbering;
return createBodyReader(options);
}
exports.createBodyReaderForTests = createBodyReaderForTests;
+22
View File
@@ -0,0 +1,22 @@
var assert = require("assert");
var zipfile = require("../../lib/docx/uris");
var test = require("../test")(module);
test("uriToZipEntryName", {
"when path does not have leading slash then path is resolved relative to base": function() {
assert.equal(
zipfile.uriToZipEntryName("one/two", "three/four"),
"one/two/three/four"
);
},
"when path has leading slash then base is ignored": function() {
assert.equal(
zipfile.uriToZipEntryName("one/two", "/three/four"),
"three/four"
);
}
});
+141
View File
@@ -0,0 +1,141 @@
var assert = require("assert");
var _ = require("underscore");
var test = require("../test")(module);
var html = require("../../lib/html");
var htmlPaths = require("../../lib/styles/html-paths");
var nonFreshElement = html.nonFreshElement;
var text = html.text;
test("empty text nodes are removed", function() {
assert.deepEqual(
simplifyNode(text("")),
[]
);
});
test("elements with no children are removed", function() {
assert.deepEqual(
simplifyNode(nonFreshElement("p", {}, [])),
[]
);
});
test("elements only containing empty nodes are removed", function() {
assert.deepEqual(
simplifyNode(nonFreshElement("p", {}, [text("")])),
[]
);
});
test("empty children of element are removed", function() {
assert.deepEqual(
simplifyNode(nonFreshElement("p", {}, [text("Hello"), text("")])),
[nonFreshElement("p", {}, [text("Hello")])]
);
});
test("empty void elements are not removed", function() {
assert.deepEqual(
simplifyNode(nonFreshElement("br", {}, [])),
[nonFreshElement("br", {}, [])]
);
});
test("successive fresh elements are not collapsed", function() {
var path = htmlPaths.elements([
htmlPaths.element("p", {}, {fresh: true})
]);
var original = concat(
pathToNodes(path, [text("Hello")]),
pathToNodes(path, [text(" there")])
);
assert.deepEqual(
html.simplify(original),
original);
});
test("successive plain non-fresh elements are collapsed if they have the same tag name", function() {
var path = htmlPaths.elements([
htmlPaths.element("p", {}, {fresh: false})
]);
assert.deepEqual(
html.simplify(concat(
pathToNodes(path, [text("Hello")]),
pathToNodes(path, [text(" there")])
)),
pathToNodes(path, [text("Hello"), text(" there")])
);
});
test("non-fresh can collapse into preceding fresh element", function() {
var freshPath = htmlPaths.elements([
htmlPaths.element("p", {}, {fresh: true})]);
var nonFreshPath = htmlPaths.elements([
htmlPaths.element("p", {}, {fresh: false})]);
assert.deepEqual(
html.simplify(concat(
pathToNodes(freshPath, [text("Hello")]),
pathToNodes(nonFreshPath, [text(" there")])
)),
pathToNodes(freshPath, [text("Hello"), text(" there")])
);
});
test("children of collapsed element can collapse with children of another collapsed element", function() {
assert.deepEqual(
html.simplify([
nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("Hello")])]),
nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("there")])])
]),
[nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("Hello"), text("there")])])]
);
});
test("empty elements are removed before collapsing", function() {
var freshPath = htmlPaths.elements([
htmlPaths.element("p", {}, {fresh: true})]);
var nonFreshPath = htmlPaths.elements([
htmlPaths.element("p", {}, {fresh: false})]);
assert.deepEqual(
html.simplify(concat(
pathToNodes(nonFreshPath, [text("Hello")]),
pathToNodes(freshPath, []),
pathToNodes(nonFreshPath, [text(" there")])
)),
pathToNodes(nonFreshPath, [text("Hello"), text(" there")])
);
});
test("when separator is present then separator is prepended to collapsed element", function() {
var unseparatedPath = htmlPaths.elements([
htmlPaths.element("pre", {}, {fresh: false})
]);
var separatedPath = htmlPaths.elements([
htmlPaths.element("pre", {}, {fresh: false, separator: "\n"})
]);
assert.deepEqual(
html.simplify(concat(
pathToNodes(unseparatedPath, [text("Hello")]),
pathToNodes(separatedPath, [text(" the"), text("re")])
)),
pathToNodes(unseparatedPath, [text("Hello"), text("\n"), text(" the"), text("re")])
);
});
function simplifyNode(node) {
return html.simplify([node]);
}
function concat() {
return _.flatten(arguments, true);
}
function pathToNodes(path, nodes) {
return path.wrap(function() {
return nodes;
});
}
+42
View File
@@ -0,0 +1,42 @@
var assert = require("assert");
var test = require("../test")(module);
var html = require("../../lib/html");
var writers = require("../../lib/writers");
test("text is HTML escaped", function() {
assert.equal(
generateString(html.text("<>&")),
"&lt;&gt;&amp;");
});
test("double quotes outside of attributes are not escaped", function() {
assert.equal(
generateString(html.text('"')),
'"');
});
test("element attributes are HTML escaped", function() {
assert.equal(
generateString(html.freshElement("p", {"x": "<"})),
'<p x="&lt;"></p>');
});
test("double quotes inside attributes are escaped", function() {
assert.equal(
generateString(html.freshElement("p", {"x": '"'})),
'<p x="&quot;"></p>');
});
test("element children are written", function() {
assert.equal(
generateString(html.freshElement("p", {}, [html.text("Hello")])),
'<p>Hello</p>');
});
function generateString(node) {
var writer = writers.writer();
html.write(writer, [node]);
return writer.asString();
}
+154
View File
@@ -0,0 +1,154 @@
var assert = require("assert");
var hamjest = require("hamjest");
var assertThat = hamjest.assertThat;
var contains = hamjest.contains;
var equalTo = hamjest.equalTo;
var hasProperties = hamjest.hasProperties;
var mammoth = require("../");
var documents = require("../lib/documents");
var promises = require("../lib/promises");
var test = require("./test")(module);
test('mammoth.images.inline() should be an alias of mammoth.images.imgElement()', function() {
assert.ok(mammoth.images.inline === mammoth.images.imgElement);
});
test('mammoth.images.dataUri() encodes images in base64', function() {
var imageBuffer = new Buffer("abc");
var image = new documents.Image({
readImage: function(encoding) {
return promises.resolve(imageBuffer.toString(encoding));
},
contentType: "image/jpeg"
});
return mammoth.images.dataUri(image).then(function(result) {
assertThat(result, contains(
hasProperties({tag: hasProperties({attributes: {"src": "data:image/jpeg;base64,YWJj"}})})
));
});
});
test('mammoth.images.imgElement()', {
'when element does not have alt text then alt attribute is not set': function() {
var imageBuffer = new Buffer("abc");
var image = new documents.Image({
readImage: function(encoding) {
return promises.resolve(imageBuffer.toString(encoding));
},
contentType: "image/jpeg"
});
var result = mammoth.images.imgElement(function(image) {
return {src: "<src>"};
})(image);
return result.then(function(result) {
assertThat(result, contains(
hasProperties({
tag: hasProperties({
attributes: equalTo({src: "<src>"})
})
})
));
});
},
'when element has alt text then alt attribute is set': function() {
var imageBuffer = new Buffer("abc");
var image = new documents.Image({
readImage: function(encoding) {
return promises.resolve(imageBuffer.toString(encoding));
},
contentType: "image/jpeg",
altText: "<alt>"
});
var result = mammoth.images.imgElement(function(image) {
return {src: "<src>"};
})(image);
return result.then(function(result) {
assertThat(result, contains(
hasProperties({
tag: hasProperties({
attributes: equalTo({alt: "<alt>", src: "<src>"})
})
})
));
});
},
'image alt text can be overridden by alt attribute returned from function': function() {
var imageBuffer = new Buffer("abc");
var image = new documents.Image({
readImage: function(encoding) {
return promises.resolve(imageBuffer.toString(encoding));
},
contentType: "image/jpeg",
altText: "<alt>"
});
var result = mammoth.images.imgElement(function(image) {
return {alt: "<alt override>", src: "<src>"};
})(image);
return result.then(function(result) {
assertThat(result, contains(
hasProperties({
tag: hasProperties({
attributes: equalTo({alt: "<alt override>", src: "<src>"})
})
})
));
});
}
});
test("imageFilenameExtension", {
"extension is derived from subtype of content type": function() {
var image = new documents.Image({
contentType: "image/gif"
});
var result = mammoth.images.imageFilenameExtension(image);
assertThat(result, equalTo("gif"));
},
"data after second slash is ignored": function() {
var image = new documents.Image({
contentType: "image/gif/jpeg"
});
var result = mammoth.images.imageFilenameExtension(image);
assertThat(result, equalTo("gif"));
},
"backslashes are treated as forward slashes": function() {
var image = new documents.Image({
contentType: "image\\gif\\..\\"
});
var result = mammoth.images.imageFilenameExtension(image);
assertThat(result, equalTo("gif"));
},
"when there is no subtype then null is returned": function() {
var image = new documents.Image({
contentType: "image"
});
var result = mammoth.images.imageFilenameExtension(image);
assertThat(result, equalTo(undefined));
}
});
+103
View File
@@ -0,0 +1,103 @@
var assert = require("assert");
var child_process = require("child_process"); // eslint-disable-line camelcase
var path = require("path");
var fs = require("fs");
var temp = require('temp').track();
var promises = require("../lib/promises");
var test = require("./test")(module);
var testPath = require("./testing").testPath;
test("HTML is printed to stdout if output file is not set", function() {
return runMammoth(testPath("single-paragraph.docx")).then(function(result) {
assert.equal(result.stderrOutput, "");
assert.equal(result.output, "<p>Walking on imported air</p>");
});
});
test("HTML is written to file if output file is set", function() {
return createTempDir().then(function(tempDir) {
var outputPath = path.join(tempDir, "output.html");
return runMammoth(testPath("single-paragraph.docx"), outputPath).then(function(result) {
assert.equal(result.stderrOutput, "");
assert.equal(result.output, "");
assert.equal(fs.readFileSync(outputPath, "utf8"), "<p>Walking on imported air</p>");
});
});
});
var imageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=";
test("inline images are included in output if writing to single file", function() {
return runMammoth(testPath("tiny-picture.docx")).then(function(result) {
assert.equal(result.stderrOutput, "");
assert.equal(result.output, '<p><img src="data:image/png;base64,' + imageBase64 + '" /></p>');
});
});
test("images are written to separate files if output dir is set", function() {
return createTempDir().then(function(tempDir) {
var outputPath = path.join(tempDir, "tiny-picture.html");
var imagePath = path.join(tempDir, "1.png");
return runMammoth(testPath("tiny-picture.docx"), "--output-dir", tempDir).then(function(result) {
assert.equal(result.stderrOutput, "");
assert.equal(result.output, "");
assert.equal(fs.readFileSync(outputPath, "utf8"), '<p><img src="1.png" /></p>');
assert.equal(fs.readFileSync(imagePath, "base64"), imageBase64);
});
});
});
test("style map is used if set", function() {
return createTempDir().then(function(tempDir) {
var styleMapPath = path.join(tempDir, "style-map");
fs.writeFileSync(styleMapPath, "p => span:fresh");
return runMammoth(testPath("single-paragraph.docx"), "--style-map", styleMapPath).then(function(result) {
assert.equal(result.stderrOutput, "");
assert.equal(result.output, "<span>Walking on imported air</span>");
});
});
});
test("--output-format=markdown option generate markdown output", function() {
return runMammoth(testPath("single-paragraph.docx"), "--output-format=markdown").then(function(result) {
assert.equal(result.stderrOutput, "");
assert.equal(result.output, "Walking on imported air\n\n");
});
});
test("when input file does not exist then process exits with error", function() {
return tryRunMammoth(testPath("non-existent.docx")).then(function(result) {
assert.strictEqual(result.error.code, 2);
assert.strictEqual(result.stdout, "");
assert.ok(result.stderr.startsWith("Fatal Error: ENOENT"));
});
});
function runMammoth() {
var args = Array.prototype.slice.call(arguments, 0);
return tryRunMammoth(args).then(function(result) {
console.log(result.stderr); // eslint-disable-line no-console
assert.equal(result.error, null);
return {output: result.stdout, stderrOutput: result.stderr};
});
}
function tryRunMammoth(args) {
var deferred = promises.defer();
var processArgs = ["node", "bin/mammoth"].concat(args);
// TODO: proper escaping of args
var command = processArgs.join(" ");
child_process.exec(command, function(error, stdout, stderr) { // eslint-disable-line camelcase
deferred.resolve({error: error, stdout: stdout, stderr: stderr});
});
return deferred.promise;
}
function createTempDir() {
return temp.mkdir();
}
+557
View File
@@ -0,0 +1,557 @@
var assert = require("assert");
var path = require("path");
var _ = require("underscore");
var mammoth = require("../");
var fs = require("../lib/fs");
var results = require("../lib/results");
var testing = require("./testing");
var test = require("./test")(module);
var testData = testing.testData;
var createFakeDocxFile = testing.createFakeDocxFile;
test('should convert docx containing one paragraph to single p element', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, "<p>Walking on imported air</p>");
assert.deepEqual(result.messages, []);
});
});
test('should convert docx represented by a Buffer', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return fs.readFile(docxPath)
.then(function(buffer) {
return mammoth.convertToHtml({buffer: buffer});
})
.then(function(result) {
assert.equal(result.value, "<p>Walking on imported air</p>");
assert.deepEqual(result.messages, []);
});
});
test('should read docx xml files with unicode byte order mark', function() {
var docxPath = path.join(__dirname, "test-data/utf8-bom.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, "<p>This XML has a byte order mark.</p>");
assert.deepEqual(result.messages, []);
});
});
test('empty paragraphs are ignored by default', function() {
var docxPath = path.join(__dirname, "test-data/empty.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, "");
assert.deepEqual(result.messages, []);
});
});
test('empty paragraphs are preserved if ignoreEmptyParagraphs is false', function() {
var docxPath = path.join(__dirname, "test-data/empty.docx");
return mammoth.convertToHtml({path: docxPath}, {ignoreEmptyParagraphs: false}).then(function(result) {
assert.equal(result.value, "<p></p>");
assert.deepEqual(result.messages, []);
});
});
test('style map can be expressed as string', function() {
var docxFile = createFakeDocxFile({
"word/document.xml": testData("simple/word/document.xml")
});
var options = {
styleMap: "p => h1"
};
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
assert.equal("<h1>Hello.</h1>", result.value);
});
});
test('style map can be expressed as array of style mappings', function() {
var docxFile = createFakeDocxFile({
"word/document.xml": testData("simple/word/document.xml")
});
var options = {
styleMap: ["p => h1"]
};
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
assert.equal("<h1>Hello.</h1>", result.value);
});
});
test('embedded style map is used if present', function() {
var docxPath = path.join(__dirname, "test-data/embedded-style-map.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, "<h1>Walking on imported air</h1>");
assert.deepEqual(result.messages, []);
});
});
test('explicit style map takes precedence over embedded style map', function() {
var docxPath = path.join(__dirname, "test-data/embedded-style-map.docx");
var options = {
styleMap: ["p => p"]
};
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
assert.equal(result.value, "<p>Walking on imported air</p>");
assert.deepEqual(result.messages, []);
});
});
test('explicit style map is combined with embedded style map', function() {
var docxPath = path.join(__dirname, "test-data/embedded-style-map.docx");
var options = {
styleMap: ["r => strong"]
};
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
assert.equal(result.value, "<h1><strong>Walking on imported air</strong></h1>");
assert.deepEqual(result.messages, []);
});
});
test('embedded style maps can be disabled', function() {
var docxPath = path.join(__dirname, "test-data/embedded-style-map.docx");
var options = {
includeEmbeddedStyleMap: false
};
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
assert.equal(result.value, "<p>Walking on imported air</p>");
assert.deepEqual(result.messages, []);
});
});
test('embedded style map can be written using toBuffer() and then read', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return fs.readFile(docxPath)
.then(function(buffer) {
return mammoth.embedStyleMap({buffer: buffer}, "p => h1");
})
.then(function(docx) {
var buffer = docx.toBuffer();
assert.ok(Buffer.isBuffer(buffer));
return mammoth.convertToHtml({buffer: buffer});
})
.then(function(result) {
assert.equal(result.value, "<h1>Walking on imported air</h1>");
assert.deepEqual(result.messages, []);
});
});
test('embedded style map can be written using toArrayBuffer() and then read', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return fs.readFile(docxPath)
.then(function(buffer) {
return mammoth.embedStyleMap({buffer: buffer}, "p => h1");
})
.then(function(docx) {
var arrayBuffer = docx.toArrayBuffer();
assert.ok(!Buffer.isBuffer(arrayBuffer));
return mammoth.convertToHtml({buffer: Buffer.from(arrayBuffer)});
})
.then(function(result) {
assert.equal(result.value, "<h1>Walking on imported air</h1>");
assert.deepEqual(result.messages, []);
});
});
test('embedded style map can be retrieved', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return fs.readFile(docxPath)
.then(function(buffer) {
return mammoth.embedStyleMap({buffer: buffer}, "p => h1");
})
.then(function(docx) {
return mammoth.readEmbeddedStyleMap({buffer: docx.toBuffer()});
})
.then(function(styleMap) {
assert.equal(styleMap, "p => h1");
});
});
test('warning if style mapping is not understood', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
var options = {
styleMap: "????\np => h1"
};
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
assert.equal("<h1>Walking on imported air</h1>", result.value);
var warning = "Did not understand this style mapping, so ignored it: ????\n" +
'Error was at character number 1: Expected element type but got unrecognisedCharacter "?"';
assert.deepEqual(result.messages, [results.warning(warning)]);
});
});
test('options are passed to document converter when calling mammoth.convertToHtml', function() {
var docxFile = createFakeDocxFile({
"word/document.xml": testData("simple/word/document.xml")
});
var options = {
styleMap: "p => h1"
};
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
assert.equal("<h1>Hello.</h1>", result.value);
});
});
test('options.transformDocument is used to transform document if set', function() {
var docxFile = createFakeDocxFile({
"word/document.xml": testData("simple/word/document.xml")
});
var options = {
transformDocument: function(document) {
document.children[0].styleId = "Heading1";
return document;
}
};
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
assert.equal("<h1>Hello.</h1>", result.value);
});
});
test('mammoth.transforms.paragraph only transforms paragraphs', function() {
var docxFile = createFakeDocxFile({
"word/document.xml": testData("simple/word/document.xml")
});
var options = {
transformDocument: mammoth.transforms.paragraph(function(paragraph) {
return _.extend(paragraph, {styleId: "Heading1"});
})
};
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
assert.equal("<h1>Hello.</h1>", result.value);
});
});
test('inline images referenced by path relative to part are included in output', function() {
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>');
});
});
test('inline images referenced by path relative to base are included in output', function() {
var docxPath = path.join(__dirname, "test-data/tiny-picture-target-base-relative.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>');
});
});
test('src of inline images can be changed using read("base64")', function() {
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
var convertImage = mammoth.images.imgElement(function(element) {
return element.read("base64").then(function(encodedImage) {
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
});
});
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
assert.deepEqual(result.messages, []);
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
});
});
test('src of inline images can be changed using readAsBase64String()', function() {
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
var convertImage = mammoth.images.imgElement(function(element) {
return element.readAsBase64String().then(function(encodedImage) {
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
});
});
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
assert.deepEqual(result.messages, []);
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
});
});
test('src of inline images can be changed using readAsArrayBuffer()', function() {
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
var convertImage = mammoth.images.imgElement(function(element) {
return element.readAsArrayBuffer().then(function(arrayBuffer) {
assert.ok(!Buffer.isBuffer(arrayBuffer));
var encodedImage = Buffer.from(arrayBuffer).toString("base64");
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
});
});
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
assert.deepEqual(result.messages, []);
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
});
});
test('src of inline images can be changed using read()', function() {
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
var convertImage = mammoth.images.imgElement(function(element) {
return element.read().then(function(buffer) {
assert.ok(Buffer.isBuffer(buffer));
var encodedImage = buffer.toString("base64");
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
});
});
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
assert.deepEqual(result.messages, []);
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
});
});
test('src of inline images can be changed using readAsBuffer()', function() {
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
var convertImage = mammoth.images.imgElement(function(element) {
return element.readAsBuffer().then(function(buffer) {
assert.ok(Buffer.isBuffer(buffer));
var encodedImage = buffer.toString("base64");
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
});
});
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
assert.deepEqual(result.messages, []);
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
});
});
test('when external file access is enabled then images stored outside of document are included in output', function() {
var docxPath = path.join(__dirname, "test-data/external-picture.docx");
return mammoth.convertToHtml({path: docxPath}, {externalFileAccess: true}).then(function(result) {
assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>');
assert.deepEqual(result.messages, []);
});
});
test('when external file access is enabled then error if images stored outside of document are specified when passing file without path', function() {
var docxPath = path.join(__dirname, "test-data/external-picture.docx");
var buffer = fs.readFileSync(docxPath);
return mammoth.convertToHtml({buffer: buffer}, {externalFileAccess: true}).then(function(result) {
assert.equal(result.value, '');
assert.equal(result.messages[0].message, "could not find external image 'tiny-picture.png', path of input document is unknown");
assert.equal(result.messages[0].type, "error");
});
});
test('given external file access is disabled by default then error if images stored outside of document are specified', function() {
var docxPath = path.join(__dirname, "test-data/external-picture.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, '');
assert.equal(result.messages[0].message, "could not read external image 'tiny-picture.png', external file access is disabled");
assert.equal(result.messages[0].type, "error");
});
});
test('simple list is converted to list elements', function() {
var docxPath = path.join(__dirname, "test-data/simple-list.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, '<ul><li>Apple</li><li>Banana</li></ul>');
});
});
test('word tables are converted to html tables', function() {
var docxPath = path.join(__dirname, "test-data/tables.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
var expectedHtml = "<p>Above</p>" +
"<table>" +
"<tr><td><p>Top left</p></td><td><p>Top right</p></td></tr>" +
"<tr><td><p>Bottom left</p></td><td><p>Bottom right</p></td></tr>" +
"</table>" +
"<p>Below</p>";
assert.equal(result.value, expectedHtml);
assert.deepEqual(result.messages, []);
});
});
test('footnotes are appended to text', function() {
// TODO: don't duplicate footnotes with multiple references
var docxPath = path.join(__dirname, "test-data/footnotes.docx");
var options = {
idPrefix: "doc-42-"
};
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
var expectedOutput = '<p>Ouch' +
'<sup><a href="#doc-42-footnote-1" id="doc-42-footnote-ref-1">[1]</a></sup>.' +
'<sup><a href="#doc-42-footnote-2" id="doc-42-footnote-ref-2">[2]</a></sup></p>' +
'<ol><li id="doc-42-footnote-1"><p> A tachyon walks into a bar. <a href="#doc-42-footnote-ref-1">↑</a></p></li>' +
'<li id="doc-42-footnote-2"><p> Fin. <a href="#doc-42-footnote-ref-2">↑</a></p></li></ol>';
assert.equal(result.value, expectedOutput);
assert.deepEqual(result.messages, []);
});
});
test('endnotes are appended to text', function() {
var docxPath = path.join(__dirname, "test-data/endnotes.docx");
var options = {
idPrefix: "doc-42-"
};
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
var expectedOutput = '<p>Ouch' +
'<sup><a href="#doc-42-endnote-2" id="doc-42-endnote-ref-2">[1]</a></sup>.' +
'<sup><a href="#doc-42-endnote-3" id="doc-42-endnote-ref-3">[2]</a></sup></p>' +
'<ol><li id="doc-42-endnote-2"><p> A tachyon walks into a bar. <a href="#doc-42-endnote-ref-2">↑</a></p></li>' +
'<li id="doc-42-endnote-3"><p> Fin. <a href="#doc-42-endnote-ref-3">↑</a></p></li></ol>';
assert.equal(result.value, expectedOutput);
assert.deepEqual(result.messages, []);
});
});
test('relationships are handled properly in footnotes', function() {
var docxPath = path.join(__dirname, "test-data/footnote-hyperlink.docx");
var options = {
idPrefix: "doc-42-"
};
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
var expectedOutput =
'<p><sup><a href="#doc-42-footnote-1" id="doc-42-footnote-ref-1">[1]</a></sup></p>' +
'<ol><li id="doc-42-footnote-1"><p> <a href="http://www.example.com">Example</a> <a href="#doc-42-footnote-ref-1">↑</a></p></li></ol>';
assert.equal(result.value, expectedOutput);
assert.deepEqual(result.messages, []);
});
});
test('when style mapping is defined for comment references then comments are included', function() {
var docxPath = path.join(__dirname, "test-data/comments.docx");
var options = {
idPrefix: "doc-42-",
styleMap: "comment-reference => sup"
};
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
var expectedOutput = (
'<p>Ouch' +
'<sup><a href="#doc-42-comment-0" id="doc-42-comment-ref-0">[MW1]</a></sup>.' +
'<sup><a href="#doc-42-comment-2" id="doc-42-comment-ref-2">[MW2]</a></sup></p>' +
'<dl><dt id="doc-42-comment-0">Comment [MW1]</dt><dd><p>A tachyon walks into a bar. <a href="#doc-42-comment-ref-0">↑</a></p></dd>' +
'<dt id="doc-42-comment-2">Comment [MW2]</dt><dd><p>Fin. <a href="#doc-42-comment-ref-2">↑</a></p></dd></dl>'
);
assert.equal(result.value, expectedOutput);
assert.deepEqual(result.messages, []);
});
});
test('textboxes are read', function() {
var docxPath = path.join(__dirname, "test-data/text-box.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
var expectedOutput = '<p>Datum plane</p>';
assert.equal(result.value, expectedOutput);
});
});
test('underline is ignored by default', function() {
var docxPath = path.join(__dirname, "test-data/underline.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, '<p><strong>The Sunset Tree</strong></p>');
});
});
test('underline can be configured with style mapping', function() {
var docxPath = path.join(__dirname, "test-data/underline.docx");
return mammoth.convertToHtml({path: docxPath}, {styleMap: "u => em"}).then(function(result) {
assert.equal(result.value, '<p><strong>The <em>Sunset</em> Tree</strong></p>');
});
});
test('strikethrough is converted to <s> by default', function() {
var docxPath = path.join(__dirname, "test-data/strikethrough.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, "<p><s>Today's Special: Salmon</s> Sold out</p>");
});
});
test('strikethrough conversion can be configured with style mappings', function() {
var docxPath = path.join(__dirname, "test-data/strikethrough.docx");
return mammoth.convertToHtml({path: docxPath}, {styleMap: "strike => del"}).then(function(result) {
assert.equal(result.value, "<p><del>Today's Special: Salmon</del> Sold out</p>");
});
});
test('indentation is used if prettyPrint is true', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return mammoth.convertToHtml({path: docxPath}, {prettyPrint: true}).then(function(result) {
assert.equal(result.value, "<p>\n Walking on imported air\n</p>");
assert.deepEqual(result.messages, []);
});
});
test('using styleMapping throws error', function() {
try {
mammoth.styleMapping();
} catch (error) {
assert.equal(
error.message,
'Use a raw string instead of mammoth.styleMapping e.g. "p[style-name=\'Title\'] => h1" instead of mammoth.styleMapping("p[style-name=\'Title\'] => h1")'
);
}
});
test('can convert single paragraph to markdown', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return mammoth.convertToMarkdown({path: docxPath}).then(function(result) {
assert.equal(result.value, "Walking on imported air\n\n");
assert.deepEqual(result.messages, []);
});
});
test('extractRawText only retains raw text', function() {
var docxPath = path.join(__dirname, "test-data/simple-list.docx");
return mammoth.extractRawText({path: docxPath}).then(function(result) {
assert.equal(result.value, 'Apple\n\nBanana\n\n');
});
});
test('extractRawText can use .docx files represented by a Buffer', function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return fs.readFile(docxPath)
.then(function(buffer) {
return mammoth.extractRawText({buffer: buffer});
})
.then(function(result) {
assert.equal(result.value, "Walking on imported air\n\n");
assert.deepEqual(result.messages, []);
});
});
test('can read strict format', function() {
var docxPath = path.join(__dirname, "test-data/strict-format.docx");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.equal(result.value, "<p>Test</p>");
assert.deepEqual(result.messages, []);
});
});
test('should throw error if file is not a valid docx document', function() {
var docxPath = path.join(__dirname, "test-data/empty.zip");
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
assert.ok(false, "Expected error");
}, function(error) {
assert.equal(error.message, "Could not find main document part. Are you sure this is a valid .docx file?");
});
});
test("Promise.done() is available", {
"on convertToHtml()": function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return mammoth.convertToHtml({path: docxPath}).done();
},
"on convertToMarkdown()": function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return mammoth.convertToMarkdown({path: docxPath}).done();
},
"on extractRawText()": function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return mammoth.extractRawText({path: docxPath}).done();
},
"on embedStyleMap()": function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return fs.readFile(docxPath).then(function(buffer) {
return mammoth.embedStyleMap({buffer: buffer}, "p => h1").done();
});
},
"on readEmbeddedStyleMap()": function() {
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
return fs.readFile(docxPath)
.then(function(buffer) {
return mammoth.embedStyleMap({buffer: buffer}, "p => h1");
})
.then(function(docx) {
return mammoth.readEmbeddedStyleMap({buffer: docx.toBuffer()}).done();
});
}
});
+1
View File
@@ -0,0 +1 @@
--ui exports
+63
View File
@@ -0,0 +1,63 @@
var assert = require("assert");
var _ = require("underscore");
var optionsReader = require("../lib/options-reader");
var standardOptions = optionsReader._standardOptions;
var readOptions = optionsReader.readOptions;
var test = require("./test")(module);
test('standard options are used if options is undefined', function() {
var options = readOptions(undefined);
assert.deepEqual(standardOptions, _.omit(options, "customStyleMap", "readStyleMap"));
assert.deepEqual(options.customStyleMap, []);
});
test('standard options are used if options is empty', function() {
var options = readOptions({});
assert.deepEqual(standardOptions, _.omit(options, "customStyleMap", "readStyleMap"));
assert.deepEqual(options.customStyleMap, []);
});
test('custom style map as string is prepended to standard style map', function() {
var options = readOptions({
styleMap: "p.SectionTitle => h2"
});
var styleMap = options.readStyleMap();
assert.deepEqual("p.SectionTitle => h2", styleMap[0]);
assert.deepEqual(optionsReader._defaultStyleMap, styleMap.slice(1));
});
test('custom style map as array is prepended to standard style map', function() {
var options = readOptions({
styleMap: ["p.SectionTitle => h2"]
});
var styleMap = options.readStyleMap();
assert.deepEqual("p.SectionTitle => h2", styleMap[0]);
assert.deepEqual(optionsReader._defaultStyleMap, styleMap.slice(1));
});
test('lines starting with # in custom style map are ignored', function() {
var options = readOptions({
styleMap: "# p.SectionTitle => h3\np.SectionTitle => h2"
});
var styleMap = options.readStyleMap();
assert.deepEqual("p.SectionTitle => h2", styleMap[0]);
assert.deepEqual(optionsReader._defaultStyleMap, styleMap.slice(1));
});
test('blank lines in custom style map are ignored', function() {
var options = readOptions({
styleMap: "\n\n"
});
assert.deepEqual(optionsReader._defaultStyleMap, options.readStyleMap());
});
test('default style mappings are ignored if includeDefaultStyleMap is false', function() {
var options = readOptions({
styleMap: "p.SectionTitle => h2",
includeDefaultStyleMap: false
});
assert.deepEqual(["p.SectionTitle => h2"], options.readStyleMap());
});
+238
View File
@@ -0,0 +1,238 @@
var assert = require("assert");
var promises = require("../lib/promises");
var test = require("./test")(module);
test("forEachSeries", {
"empty array resolves immediately without calling function": function() {
return promises.forEachSeries([], function() {
throw new Error();
}).then(function(result) {
assert.deepStrictEqual(result, undefined);
});
},
"function is called on element after the previous element has been processed": function() {
var log = [];
return promises.forEachSeries([1, 2, 3], function(element) {
return new promises.Promise(function(resolve) {
log.push(["start", element]);
setTimeout(function() {
log.push(["end", element]);
resolve();
}, 0);
});
}).then(function(result) {
assert.deepStrictEqual(result, undefined);
assert.deepStrictEqual(log, [
["start", 1],
["end", 1],
["start", 2],
["end", 2],
["start", 3],
["end", 3]
]);
});
},
"processing stops on sync error": function() {
var log = [];
return promises.forEachSeries([1, 2, 3], function(element) {
return new promises.Promise(function(resolve) {
log.push(["start", element]);
if (element === 2) {
throw new Error("failure");
} else {
setTimeout(function() {
log.push(["end", element]);
resolve();
}, 0);
}
});
}).then(
function() {
assert.fail("Expected rejection");
},
function(error) {
assert.strictEqual(error.message, "failure");
assert.deepStrictEqual(log, [
["start", 1],
["end", 1],
["start", 2]
]);
}
);
},
"processing stops on async error": function() {
var log = [];
return promises.forEachSeries([1, 2, 3], function(element) {
return new promises.Promise(function(resolve, reject) {
log.push(["start", element]);
setTimeout(function() {
if (element === 2) {
reject(new Error("failure"));
} else {
log.push(["end", element]);
resolve();
}
}, 0);
});
}).then(
function() {
assert.fail("Expected rejection");
},
function(error) {
assert.strictEqual(error.message, "failure");
assert.deepStrictEqual(log, [
["start", 1],
["end", 1],
["start", 2]
]);
}
);
}
});
test("try", {
"when function succeeds with non-promise then promise is resolved": function() {
return promises.try(function() {
return "success";
}).then(function(result) {
assert.strictEqual(result, "success");
});
},
"when function succeeds with promise then promise is resolved": function() {
return promises.try(function() {
return promises.resolve("success");
}).then(function(result) {
assert.strictEqual(result, "success");
});
},
"when function throws error then promise is rejected": function() {
return promises.try(function() {
throw new Error("failure");
}).then(
function() {
assert.fail("Expected rejection");
},
function(error) {
assert.strictEqual(error.message, "failure");
}
);
},
"when function fails with promise then promise is rejected": function() {
return promises.try(function() {
return promises.reject(new Error("failure"));
}).then(
function() {
assert.fail("Expected rejection");
},
function(error) {
assert.strictEqual(error.message, "failure");
}
);
}
});
test("props", {
"props({}) resolve to {}": function() {
return promises.props({}).then(function(result) {
assert.deepStrictEqual(result, {});
});
},
"when object has promise values then props returns with all values resolved": function() {
return promises.props({
a: promises.resolve(1),
b: promises.resolve(2),
c: promises.resolve(3)
}).then(function(result) {
assert.deepStrictEqual(result, {
a: 1,
b: 2,
c: 3
});
});
},
"when object has non-promise values then props returns with same values": function() {
return promises.props({
a: 1,
b: 2,
c: 3
}).then(function(result) {
assert.deepStrictEqual(result, {
a: 1,
b: 2,
c: 3
});
});
},
"when value is rejected promise then props is rejected promise": function() {
return promises.props({
a: promises.resolve(1),
b: promises.reject(new Error("failure")),
c: promises.resolve(3)
}).then(
function(result) {
assert.fail("Expected rejection");
},
function(error) {
assert.strictEqual(error.message, "failure");
}
);
},
"props(...).also(...) can be used to add props": function() {
return promises.props({
a: promises.resolve(1)
}).also(function(result) {
return {
b: result.a + 1
};
}).then(function(result) {
assert.deepStrictEqual(result, {
a: 1,
b: 2
});
});
},
"also can be used multiple times": function() {
return promises.props({
a: promises.resolve(1)
}).also(function(result) {
return {
b: result.a + 1
};
}).also(function(result) {
return {
c: result.b + 1
};
}).then(function(result) {
assert.deepStrictEqual(result, {
a: 1,
b: 2,
c: 3
});
});
}
});
test("toExternalPromise() returns promise with .done()", function() {
var nativePromise = Promise.resolve("hello");
var externalPromise = promises.toExternalPromise(nativePromise);
externalPromise.done();
externalPromise.then().done();
});
+61
View File
@@ -0,0 +1,61 @@
var assert = require("assert");
var documents = require("../lib/documents");
var test = require("./test")(module);
var convertElementToRawText = require("../lib/raw-text").convertElementToRawText;
test('text element is converted to text content', function() {
var element = new documents.Text("Hello.");
var result = convertElementToRawText(element);
assert.strictEqual(result, "Hello.");
});
test('tab element is converted to tab character', function() {
var element = documents.tab();
var result = convertElementToRawText(element);
assert.strictEqual(result, "\t");
});
test('paragraphs are terminated with newlines', function() {
var element = new documents.Paragraph(
[
new documents.Text("Hello "),
new documents.Text("world.")
],
{}
);
var result = convertElementToRawText(element);
assert.strictEqual(result, "Hello world.\n\n");
});
test('children are recursively converted to text', function() {
var element = new documents.Document([
new documents.Paragraph(
[
new documents.Text("Hello "),
new documents.Text("world.")
],
{}
)
]);
var result = convertElementToRawText(element);
assert.strictEqual(result, "Hello world.\n\n");
});
test('non-text element without children is converted to empty string', function() {
var element = documents.lineBreak;
var result = convertElementToRawText(element);
assert.strictEqual(result, "");
});
+15
View File
@@ -0,0 +1,15 @@
var assert = require("assert");
var results = require("../lib/results");
var Result = results.Result;
var test = require("./test")(module);
test("Result.combine removes any duplicate messages", function() {
var first = new Result(null, [results.warning("Warning...")]);
var second = new Result(null, [results.warning("Warning...")]);
var combined = Result.combine([first, second]);
assert.deepEqual(combined.messages, [results.warning("Warning...")]);
});
+291
View File
@@ -0,0 +1,291 @@
var assert = require("assert");
var htmlPaths = require("../lib/styles/html-paths");
var documentMatchers = require("../lib/styles/document-matchers");
var styleReader = require("../lib/style-reader");
var results = require("../lib/results");
var test = require("./test")(module);
var readHtmlPath = styleReader.readHtmlPath;
var readDocumentMatcher = styleReader.readDocumentMatcher;
var readStyle = styleReader.readStyle;
test('styleReader.readHtmlPath', {
'reads empty path': function() {
assertHtmlPath("", htmlPaths.empty);
},
'reads single element': function() {
assertHtmlPath("p", htmlPaths.elements(["p"]));
},
'reads choice of elements': function() {
assertHtmlPath(
"ul|ol",
htmlPaths.elements([
htmlPaths.element(["ul", "ol"])
])
);
},
'reads nested elements': function() {
assertHtmlPath("ul > li", htmlPaths.elements(["ul", "li"]));
},
'reads class on element': function() {
var expected = htmlPaths.elements([
htmlPaths.element("p", {"class": "tip"})
]);
assertHtmlPath("p.tip", expected);
},
'reads class with escaped colon': function() {
var expected = htmlPaths.elements([
htmlPaths.element("p", {"class": "a:b"})
]);
assertHtmlPath("p.a\\:b", expected);
},
'reads multiple classes on element': function() {
var expected = htmlPaths.elements([
htmlPaths.element("p", {"class": "tip help"})
]);
assertHtmlPath("p.tip.help", expected);
},
'reads attribute on element': function() {
var expected = htmlPaths.elements([
htmlPaths.element("p", {"lang": "fr"})
]);
assertHtmlPath("p[lang='fr']", expected);
},
'reads multiple attributes on element': function() {
var expected = htmlPaths.elements([
htmlPaths.element("p", {"lang": "fr", "data-x": "y"})
]);
assertHtmlPath("p[lang='fr'][data-x='y']", expected);
},
'reads when element must be fresh': function() {
var expected = htmlPaths.elements([
htmlPaths.element("p", {}, {"fresh": true})
]);
assertHtmlPath("p:fresh", expected);
},
'reads separator for elements': function() {
var expected = htmlPaths.elements([
htmlPaths.element("p", {}, {separator: "x"})
]);
assertHtmlPath("p:separator('x')", expected);
},
'reads separator with escape sequence': function() {
var expected = htmlPaths.elements([
htmlPaths.element("p", {}, {separator: "\r\n\t\'\\"})
]);
assertHtmlPath("p:separator('\\r\\n\\t\\'\\\\')", expected);
},
'reads ignore element': function() {
assertHtmlPath("!", htmlPaths.ignore);
}
});
function assertHtmlPath(input, expected) {
assert.deepEqual(readHtmlPath(input), results.success(expected));
}
test("styleReader.readDocumentMatcher", {
"reads plain paragraph": function() {
assertDocumentMatcher("p", documentMatchers.paragraph());
},
"reads paragraph with style ID": function() {
assertDocumentMatcher(
"p.Heading1",
documentMatchers.paragraph({styleId: "Heading1"})
);
},
"reads paragraph with exact style name": function() {
assertDocumentMatcher(
"p[style-name='Heading 1']",
documentMatchers.paragraph({styleName: documentMatchers.equalTo("Heading 1")})
);
},
"reads paragraph with style name prefix": function() {
assertDocumentMatcher(
"p[style-name^='Heading']",
documentMatchers.paragraph({styleName: documentMatchers.startsWith("Heading")})
);
},
"reads p:ordered-list(1) as ordered list with index of 0": function() {
assertDocumentMatcher(
"p:ordered-list(1)",
documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 0}})
);
},
"reads p:unordered-list(1) as unordered list with index of 0": function() {
assertDocumentMatcher(
"p:unordered-list(1)",
documentMatchers.paragraph({list: {isOrdered: false, levelIndex: 0}})
);
},
"reads plain run": function() {
assertDocumentMatcher(
"r",
documentMatchers.run()
);
},
"reads plain table": function() {
assertDocumentMatcher("table", documentMatchers.table());
},
"reads table with style ID": function() {
assertDocumentMatcher(
"table.TableNormal",
documentMatchers.table({
styleId: "TableNormal"
})
);
},
"reads table with style name": function() {
assertDocumentMatcher(
"table[style-name='Normal Table']",
documentMatchers.table({
styleName: documentMatchers.equalTo("Normal Table")
})
);
},
"reads bold": function() {
assertDocumentMatcher(
"b",
documentMatchers.bold
);
},
"reads italic": function() {
assertDocumentMatcher(
"i",
documentMatchers.italic
);
},
"reads underline": function() {
assertDocumentMatcher(
"u",
documentMatchers.underline
);
},
"reads strikethrough": function() {
assertDocumentMatcher(
"strike",
documentMatchers.strikethrough
);
},
"reads all-caps": function() {
assertDocumentMatcher(
"all-caps",
documentMatchers.allCaps
);
},
"reads small-caps": function() {
assertDocumentMatcher(
"small-caps",
documentMatchers.smallCaps
);
},
"reads highlight without color": function() {
assertDocumentMatcher(
"highlight",
documentMatchers.highlight()
);
},
"reads highlight with color": function() {
assertDocumentMatcher(
"highlight[color='yellow']",
documentMatchers.highlight({color: "yellow"})
);
},
"reads comment-reference": function() {
assertDocumentMatcher(
"comment-reference",
documentMatchers.commentReference
);
},
"reads line breaks": function() {
assertDocumentMatcher(
"br[type='line']",
documentMatchers.lineBreak
);
},
"reads page breaks": function() {
assertDocumentMatcher(
"br[type='page']",
documentMatchers.pageBreak
);
},
"reads column breaks": function() {
assertDocumentMatcher(
"br[type='column']",
documentMatchers.columnBreak
);
}
});
function assertDocumentMatcher(input, expected) {
assert.deepEqual(readDocumentMatcher(input), results.success(expected));
}
test("styleReader.read", {
"document matcher is mapped to HTML path using arrow": function() {
assertStyleMapping(
"p => h1",
{
from: documentMatchers.paragraph(),
to: htmlPaths.elements(["h1"])
}
);
},
"reads style mapping with no HTML path": function() {
assertStyleMapping(
"r =>",
{
from: documentMatchers.run(),
to: htmlPaths.empty
}
);
},
"error when not all input is consumed": function() {
assert.deepEqual(
readStyle("r => span a"),
new results.Result(null, [results.warning("Did not understand this style mapping, so ignored it: r => span a\nError was at character number 10: Expected end but got whitespace")])
);
}
});
function assertStyleMapping(input, expected) {
assert.deepEqual(readStyle(input), results.success(expected));
}
+91
View File
@@ -0,0 +1,91 @@
var assert = require("assert");
var test = require("../test")(module);
var documentMatchers = require("../../lib/styles/document-matchers");
var documents = require("../../lib/documents");
var Paragraph = documents.Paragraph;
test("paragraph with no options matches any paragraph", function() {
var matcher = documentMatchers.paragraph();
assert.ok(matcher.matches(new Paragraph()));
assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1")));
});
test("paragraph style ID only matches paragraphs with that style ID", function() {
var matcher = documentMatchers.paragraph({styleId: "Heading1"});
assert.ok(!matcher.matches(new Paragraph()));
assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1")));
assert.ok(!matcher.matches(paragraphWithStyle("Heading2", "Heading 2")));
});
test("paragraph style name only matches paragraphs with that style name", function() {
var matcher = documentMatchers.paragraph({styleName: documentMatchers.equalTo("Heading 1")});
assert.ok(!matcher.matches(new Paragraph()));
assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1")));
assert.ok(!matcher.matches(paragraphWithStyle("Heading2", "Heading 2")));
});
test("ordered-list(index) matches an ordered list with specified level index", function() {
var matcher = documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 1}});
assert.ok(!matcher.matches(new Paragraph()));
assert.ok(matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: true}})));
assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 0, isOrdered: true}})));
assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: false}})));
});
test("unordered-list(index) matches an unordered list with specified level index", function() {
var matcher = documentMatchers.paragraph({list: {isOrdered: false, levelIndex: 1}});
assert.ok(!matcher.matches(new Paragraph()));
assert.ok(matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: false}})));
assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: true}})));
});
test("matchers for lists with index 0 do not match elements that are not lists", function() {
var matcher = documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 0}});
assert.ok(!matcher.matches(new Paragraph()));
});
test("highlight matcher does not match non-highlight elements", function() {
var matcher = documentMatchers.highlight();
assert.ok(!matcher.matches(new Paragraph()));
});
test("highlight matcher without color matches all highlight elements", function() {
var matcher = documentMatchers.highlight({});
assert.ok(matcher.matches({type: "highlight", color: "yellow"}));
});
test("highlight matcher with color matches highlight with that color", function() {
var matcher = documentMatchers.highlight({color: "yellow"});
assert.ok(matcher.matches({type: "highlight", color: "yellow"}));
});
test("highlight matcher with color does not match highlights with other colors", function() {
var matcher = documentMatchers.highlight({color: "yellow"});
assert.ok(!matcher.matches({type: "highlight", color: "red"}));
});
function paragraphWithStyle(styleId, styleName) {
return new Paragraph([], {styleId: styleId, styleName: styleName});
}
test("equalTo matcher is case insensitive", function() {
var matcher = documentMatchers.equalTo("Heading 1");
assert.ok(matcher.operator(matcher.operand, "heaDING 1"));
assert.ok(!matcher.operator(matcher.operand, "heaDING 2"));
});
test("startsWith matches strings with prefix", function() {
var matcher = documentMatchers.startsWith("Heading");
assert.ok(matcher.operator(matcher.operand, "Heading"));
assert.ok(matcher.operator(matcher.operand, "Heading 1"));
assert.ok(!matcher.operator(matcher.operand, "Custom Heading"));
assert.ok(!matcher.operator(matcher.operand, "Head"));
assert.ok(!matcher.operator(matcher.operand, "Header 2"));
});
test("startsWith matcher is case insensitive", function() {
var matcher = documentMatchers.startsWith("Heading");
assert.ok(matcher.operator(matcher.operand, "heaDING"));
});
+20
View File
@@ -0,0 +1,20 @@
var assert = require("assert");
var test = require("../test")(module);
var htmlPaths = require("../../lib/styles/html-paths");
test("element can match multiple tag names", function() {
var pathPart = htmlPaths.element(["ul", "ol"]);
assert.ok(pathPart.matchesElement({tagName: "ul"}));
assert.ok(pathPart.matchesElement({tagName: "ol"}));
assert.ok(!pathPart.matchesElement({tagName: "p"}));
});
test("element matches if attributes are the same", function() {
var pathPart = htmlPaths.element(["p"], {"class": "tip"});
assert.ok(!pathPart.matchesElement({tagName: "p"}));
assert.ok(!pathPart.matchesElement({tagName: "p", attributes: {"class": "tip help"}}));
assert.ok(pathPart.matchesElement({tagName: "p", attributes: {"class": "tip"}}));
});
+124
View File
@@ -0,0 +1,124 @@
var hamjest = require("hamjest");
var assertThat = hamjest.assertThat;
var contains = hamjest.contains;
var hasProperties = hamjest.hasProperties;
var tokenise = require("../../../lib/styles/parser/tokeniser").tokenise;
var test = require("../../test")(module);
test("unknown tokens are tokenised", function() {
assertTokens("~", [isToken("unrecognisedCharacter", "~")]);
});
test("empty string is tokenised to end of file token", function() {
assertTokens("", []);
});
test("whitespace is tokenised", function() {
assertTokens(" \t\t ", [isToken("whitespace")]);
});
test("identifiers are tokenised", function() {
assertTokens("Overture", [isToken("identifier", "Overture")]);
});
test("integers are tokenised", function() {
assertTokens("123", [isToken("integer", "123")]);
});
test("strings are tokenised", function() {
assertTokens("'Tristan'", [isToken("string", "Tristan")]);
});
test("escaped string terminators in strings are tokenised", function() {
assertTokens("'Tristan\\''", [isToken("string", "Tristan\\'")]);
});
test("escape sequences in strings are tokenised", function() {
assertTokens("'Tristan\\\\'", [isToken("string", "Tristan\\\\")]);
});
test("unterminated strings are tokenised", function() {
assertTokens("'Tristan", [isToken("unterminated-string", "Tristan")]);
});
test("unterminated strings ending with escaped string terminator are tokenised", function() {
assertTokens("'Tristan\\'", [isToken("unterminated-string", "Tristan\\'")]);
});
test("unterminated strings with unterminated escape are tokenised", function() {
assertTokens("'Tristan\\", [isToken("unterminated-string", "Tristan\\")]);
});
test("unterminated strings with many escape sequences do not cause excessive backtracking", function() {
assertTokens("'" + "\\a".repeat(50), [isToken("unterminated-string", "\\a".repeat(50))]);
});
test("arrows are tokenised", function() {
assertTokens("=>", [isToken("arrow")]);
});
test("classes are tokenised", function() {
assertTokens(".overture", [isToken("dot"), isToken("identifier", "overture")]);
});
test("colons are tokenised", function() {
assertTokens("::", [isToken("colon"), isToken("colon")]);
});
test("greater thans are tokenised", function() {
assertTokens(">>", [isToken("gt"), isToken("gt")]);
});
test("equals are tokenised", function() {
assertTokens("==", [isToken("equals"), isToken("equals")]);
});
test("startsWith symbols are tokenised", function() {
assertTokens("^=^=", [isToken("startsWith"), isToken("startsWith")]);
});
test("open parens are tokenised", function() {
assertTokens("((", [isToken("open-paren"), isToken("open-paren")]);
});
test("close parens are tokenised", function() {
assertTokens("))", [isToken("close-paren"), isToken("close-paren")]);
});
test("open square brackets are tokenised", function() {
assertTokens("[[", [isToken("open-square-bracket"), isToken("open-square-bracket")]);
});
test("close square brackets are tokenised", function() {
assertTokens("]]", [isToken("close-square-bracket"), isToken("close-square-bracket")]);
});
test("choices are tokenised", function() {
assertTokens("||", [isToken("choice"), isToken("choice")]);
});
test("can tokenise multiple tokens", function() {
assertTokens("The Magic Position", [
isToken("identifier", "The"),
isToken("whitespace"),
isToken("identifier", "Magic"),
isToken("whitespace"),
isToken("identifier", "Position")
]);
});
function assertTokens(input, expectedTokens) {
assertThat(
tokenise(input),
contains.apply(null, expectedTokens.concat([isToken("end", null)]))
);
}
function isToken(tokenType, value) {
return hasProperties({
name: tokenType,
value: value
});
}
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
@@ -0,0 +1,10 @@
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml" />
<Relationship Id="rId3" Type="http://schemas.microsoft.com/office/2007/relationships/stylesWithEffects" Target="stylesWithEffects.xml" />
<Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml" />
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" />
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml" />
<Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="http://www.example.com" TargetMode="External" />
<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml" />
<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml" />
</Relationships>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" ?>
<w:document mc:Ignorable="w14 wp14" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
<w:body>
<w:p w:rsidR="00E01ECE" w:rsidRDefault="000636A7">
<w:hyperlink r:id="rId6" w:history="1">
<w:r>
<w:t>coconuts</w:t>
</w:r>
</w:hyperlink>
</w:p>
<w:sectPr w:rsidR="00E01ECE">
<w:pgSz w:h="16838" w:w="11906"/>
<w:pgMar w:bottom="1440" w:footer="708" w:gutter="0" w:header="708" w:left="1440" w:right="1440" w:top="1440"/>
<w:cols w:space="708"/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>
BIN
View File
Binary file not shown.
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" ?>
<w:document mc:Ignorable="w14 wp14" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
<w:body>
<w:p w:rsidR="00E01ECE" w:rsidRDefault="000636A7">
<w:r>
<w:t>Hello.</w:t>
</w:r>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
<w:sectPr w:rsidR="00E01ECE">
<w:pgSz w:h="16838" w:w="11906"/>
<w:pgMar w:bottom="1440" w:footer="708" w:gutter="0" w:header="708" w:left="1440" w:right="1440" w:top="1440"/>
<w:cols w:space="708"/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
View File
Binary file not shown.
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
var path = require("path");
var root = path.dirname(__dirname);
module.exports = function(testModule) {
var tests = testModule.exports[path.relative(root, testModule.filename)] = {};
return function(name, func) {
tests[name] = func;
};
};
+56
View File
@@ -0,0 +1,56 @@
var path = require("path");
var _ = require("underscore");
var fs = require("../lib/fs");
var promises = require("../lib/promises");
exports.testPath = testPath;
exports.testData = testData;
exports.createFakeDocxFile = createFakeDocxFile;
exports.createFakeFiles = createFakeFiles;
function testPath(filename) {
return path.join(__dirname, "test-data", filename);
}
function testData(testDataPath) {
var fullPath = testPath(testDataPath);
return fs.readFile(fullPath, "utf-8");
}
function createFakeDocxFile(files) {
function exists(path) {
return !!files[path];
}
return {
read: createRead(files),
exists: exists
};
}
function createFakeFiles(files) {
return {
read: createRead(files)
};
}
function createRead(files) {
function read(path, encoding) {
return promises.resolve(files[path], function(buffer) {
if (_.isString(buffer)) {
buffer = new Buffer(buffer);
}
if (!Buffer.isBuffer(buffer)) {
return promises.reject(new Error("file was not a buffer"));
} else if (encoding) {
return promises.resolve(buffer.toString(encoding));
} else {
return promises.resolve(buffer.buffer);
}
});
}
return read;
}
+125
View File
@@ -0,0 +1,125 @@
var assert = require("assert");
var _ = require("underscore");
var documents = require("../lib/documents");
var transforms = require("../lib/transforms");
var test = require("./test")(module);
test("paragraph()", {
"paragraph is transformed": function() {
var paragraph = documents.paragraph([]);
var result = transforms.paragraph(function() {
return documents.tab();
})(paragraph);
assert.deepEqual(result, documents.tab());
},
"non-paragraph elements are not transformed": function() {
var run = documents.run([]);
var result = transforms.paragraph(function() {
return documents.tab();
})(run);
assert.deepEqual(result, documents.run([]));
}
});
test("run()", {
"run is transformed": function() {
var run = documents.run([]);
var result = transforms.run(function() {
return documents.tab();
})(run);
assert.deepEqual(result, documents.tab());
},
"non-run elements are not transformed": function() {
var paragraph = documents.paragraph([]);
var result = transforms.run(function() {
return documents.tab();
})(paragraph);
assert.deepEqual(result, documents.paragraph([]));
}
});
test("elements()", {
"all descendants are transformed": function() {
var root = {
children: [
{
children: [
{}
]
}
]
};
var currentCount = 0;
function setCount(node) {
currentCount++;
return _.extend(node, {count: currentCount});
}
var result = transforms._elements(setCount)(root);
assert.deepEqual(result, {
count: 3,
children: [
{
count: 2,
children: [
{count: 1}
]
}
]
});
}
});
test("getDescendants()", {
"returns nothing if element has no children property": function() {
assert.deepEqual(transforms.getDescendants({}), []);
},
"returns nothing if element has empty children": function() {
assert.deepEqual(transforms.getDescendants({children: []}), []);
},
"includes children": function() {
var element = {
children: [{name: "child 1"}, {name: "child 2"}]
};
assert.deepEqual(
transforms.getDescendants(element),
[{name: "child 1"}, {name: "child 2"}]
);
},
"includes indirect descendants": function() {
var grandchild = {name: "grandchild"};
var child = {name: "child", children: [grandchild]};
var element = {children: [child]};
assert.deepEqual(
transforms.getDescendants(element),
[grandchild, child]
);
}
});
test("getDescendantsOfType()", {
"filters descendants to type": function() {
var paragraph = {type: "paragraph"};
var run = {type: "run"};
var element = {
children: [paragraph, run]
};
assert.deepEqual(
transforms.getDescendantsOfType(element, "run"),
[run]
);
}
});
+37
View File
@@ -0,0 +1,37 @@
var assert = require("assert");
var path = require("path");
var test = require("./test")(module);
var fs = require("../lib/fs");
var unzip = require("../lib/unzip");
test("unzip fails if given empty object", function() {
return unzip.openZip({}).then(function() {
assert.ok(false, "Expected failure");
}, function(error) {
assert.equal("Could not find file in options", error.message);
});
});
test("unzip can open local zip file", function() {
var zipPath = path.join(__dirname, "test-data/hello.zip");
return unzip.openZip({path: zipPath}).then(function(zipFile) {
return zipFile.read("hello", "utf8");
}).then(function(contents) {
assert.equal(contents, "Hello world\n");
});
});
test('unzip can open Buffer', function() {
var zipPath = path.join(__dirname, "test-data/hello.zip");
return fs.readFile(zipPath)
.then(function(buffer) {
return unzip.openZip({buffer: buffer});
})
.then(function(zipFile) {
return zipFile.read("hello", "utf8");
})
.then(function(contents) {
assert.equal(contents, "Hello world\n");
});
});
+133
View File
@@ -0,0 +1,133 @@
var assert = require("assert");
var test = require("../test")(module);
var htmlWriter = require("../../lib/writers/html-writer");
test('can generate simple paragraph', function() {
var writer = htmlWriter.writer();
writer.open("p");
writer.text("Hello");
writer.close("p");
return assert.equal(writer.asString(), "<p>Hello</p>");
});
test('can nest elements', function() {
var writer = htmlWriter.writer();
writer.open("ul");
writer.open("li");
writer.text("One");
writer.close("li");
writer.open("li");
writer.text("Two");
writer.close("li");
writer.close("ul");
return assert.equal(writer.asString(), "<ul><li>One</li><li>Two</li></ul>");
});
test('indents text if prettyPrint is true', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("p");
writer.text("One");
writer.close("p");
writer.open("p");
writer.text("Two");
writer.close("p");
return assert.equal(writer.asString(), "<p>\n One\n</p>\n<p>\n Two\n</p>");
});
test('indents element if prettyPrint is true', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("ul");
writer.open("li");
writer.text("One");
writer.close("li");
writer.open("li");
writer.text("Two");
writer.close("li");
writer.close("ul");
return assert.equal(writer.asString(), "<ul>\n <li>\n One\n </li>\n <li>\n Two\n </li>\n</ul>");
});
test('when prettyPrint is true inner elements do not have new lines', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("p");
writer.text("One");
writer.open("em");
writer.text("Two");
writer.close("em");
writer.close("p");
return assert.equal(writer.asString(), "<p>\n One<em>Two</em>\n</p>");
});
test('indents closing element correctly when nested inside another indented element', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("div");
writer.open("div");
writer.open("div");
writer.text("Hello");
writer.close("div");
writer.close("div");
writer.close("div");
return assert.equal(writer.asString(), "<div>\n <div>\n <div>\n Hello\n </div>\n </div>\n</div>");
});
test('newlines in text are indented', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("p");
writer.text("One\nTwo");
writer.close("p");
return assert.equal(writer.asString(), "<p>\n One\n Two\n</p>");
});
test('run of text has only one new line', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("p");
writer.text("One");
writer.text("Two");
writer.close("p");
return assert.equal(writer.asString(), "<p>\n OneTwo\n</p>");
});
test('run of html has only one new line', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("p");
writer.text("One");
writer.text("Two");
writer.close("p");
return assert.equal(writer.asString(), "<p>\n OneTwo\n</p>");
});
test('self closing elements are indented', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("p");
writer.selfClosing("br");
writer.close("p");
return assert.equal(writer.asString(), "<p>\n <br />\n</p>");
});
test('newlines in appended HTML are indented', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("p");
writer.text("One\nTwo");
writer.close("p");
return assert.equal(writer.asString(), "<p>\n One\n Two\n</p>");
});
test('newlines in <pre> are not indented', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("pre");
writer.text("One\nTwo");
writer.close("pre");
return assert.equal(writer.asString(), "<pre>One\nTwo</pre>");
});
test('newlines in element in <pre> are not indented', function() {
var writer = htmlWriter.writer({prettyPrint: true});
writer.open("pre");
writer.open("p");
writer.text("One\nTwo");
writer.close("p");
writer.close("pre");
return assert.equal(writer.asString(), "<pre><p>One\nTwo</p></pre>");
});
+332
View File
@@ -0,0 +1,332 @@
var assert = require("assert");
var test = require("../test")(module);
var mdWriter = require("../../lib/writers/markdown-writer");
test('special markdown characters in text are escaped', function() {
var writer = mdWriter.writer();
writer.text("\\*");
return assert.equal(writer.asString(), "\\\\\\*");
});
test('unrecognised elements are treated as normal text', function() {
var writer = mdWriter.writer();
writer.open("blah");
writer.text("Hello");
writer.close("blah");
return assert.equal(writer.asString(), "Hello");
});
test('paragraphs are terminated with double new line', function() {
var writer = mdWriter.writer();
writer.open("p");
writer.text("Hello");
writer.close("p");
return assert.equal(writer.asString(), "Hello\n\n");
});
test('h1 elements are converted to heading with leading hash', function() {
var writer = mdWriter.writer();
writer.open("h1");
writer.text("Hello");
writer.close("h1");
return assert.equal(writer.asString(), "# Hello\n\n");
});
test('h6 elements are converted to heading with six leading hashes', function() {
var writer = mdWriter.writer();
writer.open("h6");
writer.text("Hello");
writer.close("h6");
return assert.equal(writer.asString(), "###### Hello\n\n");
});
test('br is written as two spaces followed by new line', function() {
var writer = mdWriter.writer();
writer.text("Hello World");
writer.selfClosing("br");
return assert.equal(writer.asString(), "Hello World \n");
});
test('strong text is surrounded by two underscores', function() {
var writer = mdWriter.writer();
writer.text("Hello ");
writer.open("strong");
writer.text("World");
writer.close("strong");
return assert.equal(writer.asString(), "Hello __World__");
});
test('emphasised text is surrounded by one asterix', function() {
var writer = mdWriter.writer();
writer.text("Hello ");
writer.open("em");
writer.text("World");
writer.close("em");
return assert.equal(writer.asString(), "Hello *World*");
});
test('anchor tags are written as hyperlinks', function() {
var writer = mdWriter.writer();
writer.open("a", {"href": "http://example.com"});
writer.text("Hello");
writer.close("a");
return assert.equal(writer.asString(), "[Hello](http://example\\.com)");
});
test('special markdown characters in anchor href are escaped', function() {
var writer = mdWriter.writer();
writer.open("a", {"href": "http://example.com)Oops"});
writer.text("Hello");
writer.close("a");
return assert.equal(writer.asString(), "[Hello](http://example\\.com\\)Oops)");
});
test('anchor tags without href attribute are treated as ordinary text', function() {
var writer = mdWriter.writer();
writer.open("a");
writer.text("Hello");
writer.close("a");
return assert.equal(writer.asString(), "Hello");
});
test('elements with IDs have anchor tags with IDs appended to start of markdown element', function() {
var writer = mdWriter.writer();
writer.open("h1", {id: "start"});
writer.text("Hello");
writer.close("h1");
return assert.equal(writer.asString(), '# <a id="start"></a>Hello\n\n');
});
test('anchor tag IDs are escaped', function() {
var writer = mdWriter.writer();
writer.open("h1", {id: "\"start\""});
writer.text("Hello");
writer.close("h1");
return assert.equal(writer.asString(), '# <a id="&quot;start&quot;"></a>Hello\n\n');
});
test('links have anchors before opening square bracket', function() {
var writer = mdWriter.writer();
writer.open("a", {href: "http://example.com", id: "start"});
writer.text("Hello");
writer.close("a");
return assert.equal(writer.asString(), '<a id="start"></a>[Hello](http://example\\.com)');
});
test('can generate images', function() {
var writer = mdWriter.writer();
writer.selfClosing("img", {"src": "http://example.com/image.jpg", "alt": "Alt Text"});
return assert.equal(writer.asString(), "![Alt Text](http://example\\.com/image\\.jpg)");
});
test('image alt text is escaped', function() {
var writer = mdWriter.writer();
writer.selfClosing("img", {"src": "http://example.com/image.jpg", "alt": "Alt Text]Oops"});
return assert.equal(writer.asString(), "![Alt Text\\]Oops](http://example\\.com/image\\.jpg)");
});
test('image src is escaped', function() {
var writer = mdWriter.writer();
writer.selfClosing("img", {"src": "http://example.com/image.jpg)Oops", "alt": "Alt Text"});
return assert.equal(writer.asString(), "![Alt Text](http://example\\.com/image\\.jpg\\)Oops)");
});
test('can generate images with missing alt attribute', function() {
var writer = mdWriter.writer();
writer.selfClosing("img", {"src": "http://example.com/image.jpg"});
return assert.equal(writer.asString(), "![](http://example\\.com/image\\.jpg)");
});
test('can generate images with missing src attribute', function() {
var writer = mdWriter.writer();
writer.selfClosing("img", {"alt": "Alt Text"});
return assert.equal(writer.asString(), "![Alt Text]()");
});
test("doesn't display empty images", function() {
var writer = mdWriter.writer();
writer.selfClosing("img");
return assert.equal(writer.asString(), "");
});
test('list item outside of list is treated as unordered list', function() {
var writer = mdWriter.writer();
writer.open("li");
writer.text("Hello");
writer.close("li");
return assert.equal(writer.asString(), "- Hello\n");
});
test('can generate an ordered list', function() {
var writer = mdWriter.writer();
writer.open("ol");
writer.open("li");
writer.text("Hello");
writer.close("li");
writer.open("li");
writer.text("World");
writer.close("li");
writer.close("ol");
return assert.equal(writer.asString(), "1. Hello\n2. World\n\n");
});
test('can generate an unordered list', function() {
var writer = mdWriter.writer();
writer.open("ul");
writer.open("li");
writer.text("Hello");
writer.close("li");
writer.open("li");
writer.text("World");
writer.close("li");
writer.close("ul");
return assert.equal(writer.asString(), "- Hello\n- World\n\n");
});
test('can generate a nested ordered list with correct numbering', function() {
var writer = mdWriter.writer();
writer.open("ol");
writer.open("li");
writer.text("Outer One");
writer.open("ol");
writer.open("li");
writer.text("Nested One");
writer.close("li");
writer.open("li");
writer.text("Nested Two");
writer.close("li");
writer.close("ol");
writer.close("li");
writer.open("li");
writer.text("Outer Two");
writer.close("li");
writer.close("ol");
return assert.equal(writer.asString(), "1. Outer One\n\t1. Nested One\n\t2. Nested Two\n2. Outer Two\n\n");
});
test('can generate a multi-level nested ordered list', function() {
var writer = mdWriter.writer();
writer.open("ol");
writer.open("li");
writer.text("Outer One");
writer.open("ol");
writer.open("li");
writer.text("Nested One");
writer.open("ol");
writer.open("li");
writer.text("Inner One");
writer.close("li");
writer.close("ol");
writer.close("li");
writer.close("ol");
writer.close("li");
writer.close("ol");
return assert.equal(writer.asString(), "1. Outer One\n\t1. Nested One\n\t\t1. Inner One\n\n");
});
test('new ordered list resets numbering', function() {
var writer = mdWriter.writer();
writer.open("ol");
writer.open("li");
writer.text("First");
writer.close("li");
writer.close("ol");
writer.open("p");
writer.text("Hello");
writer.close("p");
writer.open("ol");
writer.open("li");
writer.text("Second");
writer.close("li");
writer.close("ol");
return assert.equal(writer.asString(), "1. First\n\nHello\n\n1. Second\n\n");
});
test('can generate a nested unordered list', function() {
var writer = mdWriter.writer();
writer.open("ul");
writer.open("li");
writer.text("Outer One");
writer.open("ul");
writer.open("li");
writer.text("Nested One");
writer.close("li");
writer.open("li");
writer.text("Nested Two");
writer.close("li");
writer.close("ul");
writer.close("li");
writer.open("li");
writer.text("Outer Two");
writer.close("li");
writer.close("ul");
return assert.equal(writer.asString(), "- Outer One\n\t- Nested One\n\t- Nested Two\n- Outer Two\n\n");
});
test('can nest inline elements', function() {
var writer = mdWriter.writer();
writer.open("p");
writer.text("Lorem ");
writer.open("strong");
writer.text("ipsum ");
writer.open("em");
writer.text("dolor");
writer.close("em");
writer.text(" sit");
writer.close("strong");
writer.text(" amet");
writer.close("p");
return assert.equal(writer.asString(), "Lorem __ipsum *dolor* sit__ amet\n\n");
});
test('can emphasise list text', function() {
var writer = mdWriter.writer();
writer.open("ol");
writer.open("li");
writer.text("Hello ");
writer.open("strong");
writer.text("Strong");
writer.close("strong");
writer.text(" World");
writer.close("li");
writer.open("li");
writer.text("Hello ");
writer.open("em");
writer.text("Emphasis");
writer.close("em");
writer.text(" World");
writer.close("li");
writer.close("ol");
return assert.equal(writer.asString(), "1. Hello __Strong__ World\n2. Hello *Emphasis* World\n\n");
});
test('generates correct spacing between paragraphs and lists', function() {
var writer = mdWriter.writer();
writer.open("p");
writer.text("Hello World");
writer.close("p");
writer.open("ul");
writer.open("li");
writer.text("First Item");
writer.close("li");
writer.open("li");
writer.text("Second Item");
writer.close("li");
writer.close("ul");
writer.open("p");
writer.text("Hello World");
writer.close("p");
return assert.equal(writer.asString(), "Hello World\n\n- First Item\n- Second Item\n\nHello World\n\n");
});
+85
View File
@@ -0,0 +1,85 @@
var assert = require("assert");
var xmlreader = require("../../lib/xml/reader");
var test = require("../test")(module);
test('should read self-closing element', function() {
return xmlreader.readString("<body/>").then(function(result) {
assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result);
});
});
test('should read empty element with separate closing tag', function() {
return xmlreader.readString("<body></body>").then(function(result) {
assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result);
});
});
test('should read attributes of tags', function() {
return xmlreader.readString('<body name="bob"/>').then(function(result) {
assert.deepEqual({name: "bob"}, result.attributes);
});
});
test('can read text element', function() {
return xmlreader.readString('<body>Hello!</body>').then(function(result) {
assert.deepEqual({type: "text", value: "Hello!"}, result.children[0]);
});
});
test('should read element with children', function() {
return xmlreader.readString("<body><a/><b/></body>").then(function(root) {
assert.equal(2, root.children.length);
assert.equal("a", root.children[0].name);
assert.equal("b", root.children[1].name);
});
});
test('unmapped namespaces URIs are included in braces as prefix', function() {
return xmlreader.readString('<w:body xmlns:w="word"/>').then(function(result) {
assert.deepEqual(result.name, "{word}body");
});
});
test('mapped namespaces URIs are translated using map', function() {
var namespaceMap = {
"word": "x"
};
return xmlreader.readString('<w:body xmlns:w="word"/>', namespaceMap).then(function(result) {
assert.deepEqual(result.name, "x:body");
});
});
test('namespace of attributes is mapped to prefix', function() {
var namespaceMap = {
"word": "x"
};
var xmlString = '<w:body xmlns:w="word" w:val="Hello!"/>';
return xmlreader.readString(xmlString, namespaceMap).then(function(result) {
assert.deepEqual(result.attributes["x:val"], "Hello!");
});
});
test('can find first element with name', function() {
return xmlreader.readString('<body><a/><b index="1"/><b index="2"/></body>').then(function(result) {
var first = result.first("b");
assert.equal("1", first.attributes.index);
});
});
test('whitespace between xml declaration and root tag is ignored', function() {
return xmlreader.readString('<?xml version="1.0" ?>\n<body/>').then(function(result) {
assert.deepEqual("body", result.name);
});
});
test('error if XML is badly formed', function() {
return xmlreader.readString("<bo").then(function(result) {
throw new Error("Expected failure");
}, function(error) {
assert.ok(error);
return 1;
});
});
+81
View File
@@ -0,0 +1,81 @@
var assert = require("assert");
var xml = require("../../lib/xml");
var writer = require("../../lib/xml/writer");
var test = require("../test")(module);
test('writing empty root element writes out xml declaration and empty root element', function() {
assertXmlString(xml.element("root"), {}, '<root/>');
});
test('can write empty child elements', function() {
assertXmlString(xml.element("root", {}, [xml.element("album"), xml.element("single")]), {},
'<root><album/><single/></root>');
});
test('can write empty descendant elements', function() {
var element = xml.element("root", {}, [
xml.element("album", {}, [
xml.element("year"),
xml.element("song")
])
]);
assertXmlString(element, {},
'<root><album><year/><song/></album></root>');
});
test('can write element attributes', function() {
var element = xml.element("root", {}, [
xml.element("album", {"title": "Everything in Transit"})
]);
assertXmlString(element, {},
'<root><album title="Everything in Transit"/></root>');
});
test('can write text nodes', function() {
var element = xml.element("root", {}, [
xml.element("album", {}, [
xml.text("Everything in Transit")
])
]);
assertXmlString(element, {},
'<root><album>Everything in Transit</album></root>');
});
test('can write root element with long-form prefix when URI is namespace', function() {
var element = xml.element("{music}root");
assertXmlString(element, {"m": "music"},
'<m:root xmlns:m="music"/>');
});
test('can write child elements with long-form prefix when URI is namespace', function() {
var element = xml.element("root", {}, [
xml.element("{music}album")
]);
assertXmlString(element, {"m": "music"},
'<root xmlns:m="music"><m:album/></root>');
});
test('can write child elements with short-form prefix when URI is namespace', function() {
var element = xml.element("root", {}, [
xml.element("m:album")
]);
assertXmlString(element, {"m": "music"},
'<root xmlns:m="music"><m:album/></root>');
});
test('default namespace has key of empty string', function() {
var element = xml.element("root", {}, [
xml.element("{music}album")
]);
assertXmlString(element, {"": "music"},
'<root xmlns="music"><album/></root>');
});
function assertXmlString(element, namespaces, expectedString) {
assert.equal(writer.writeString(element, namespaces),
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
expectedString);
}
+61
View File
@@ -0,0 +1,61 @@
var assert = require("assert");
var JSZip = require("jszip");
var zipfile = require("../lib/zipfile");
var test = require("./test")(module);
test('file in zip can be read after being written', function() {
return emptyZipFile().then(function(zip) {
assert(!zip.exists("song/title"));
zip.write("song/title", "Dark Blue");
assert(zip.exists("song/title"));
return zip.read("song/title", "utf8").then(function(contents) {
assert.equal(contents, "Dark Blue");
});
});
});
function emptyZipFile() {
var zip = new JSZip();
return zip.generateAsync({type: "arraybuffer"}).then(function(arrayBuffer) {
return zipfile.openArrayBuffer(arrayBuffer);
});
}
test("splitPath splits zip paths on last forward slash", function() {
assert.deepEqual(zipfile.splitPath("a/b"), {dirname: "a", basename: "b"});
assert.deepEqual(zipfile.splitPath("a/b/c"), {dirname: "a/b", basename: "c"});
assert.deepEqual(zipfile.splitPath("/a/b/c"), {dirname: "/a/b", basename: "c"});
});
test("when path has no forward slashes then splitPath returns empty dirname", function() {
assert.deepEqual(zipfile.splitPath("name"), {dirname: "", basename: "name"});
});
test("joinPath joins arguments with forward slashes", function() {
assert.equal(zipfile.joinPath("a", "b"), "a/b");
assert.equal(zipfile.joinPath("a/b", "c"), "a/b/c");
assert.equal(zipfile.joinPath("a", "b/c"), "a/b/c");
assert.equal(zipfile.joinPath("/a/b", "c"), "/a/b/c");
});
test("empty parts are ignored when joining paths", function() {
assert.equal(zipfile.joinPath("a", ""), "a");
assert.equal(zipfile.joinPath("", "b"), "b");
assert.equal(zipfile.joinPath("a", "", "b"), "a/b");
});
test("when joining paths then absolute paths ignore earlier paths", function() {
assert.equal(zipfile.joinPath("a", "/b"), "/b");
assert.equal(zipfile.joinPath("a", "/b", "c"), "/b/c");
assert.equal(zipfile.joinPath("/a", "/b"), "/b");
assert.equal(zipfile.joinPath("/a"), "/a");
});