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

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"
);
}
});