aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2015-07-03 21:31:00 +0100
committerMichael Williamson <mike@zwobble.org>2015-07-03 21:31:00 +0100
commit792aa9fb8cab1b68c585f09336758e2c76e3e8d5 (patch)
treee492c0a1165fc08a70198d8927346a8ec6bbe15d
parent419eb705048dae39807ffeac7bce8d52563d7013 (diff)
Extract reading of document body to separate module
-rw-r--r--lib/docx/body-reader.js342
-rw-r--r--lib/docx/document-xml-reader.js338
-rw-r--r--lib/docx/docx-reader.js22
-rw-r--r--tests/docx/body-reader.tests.js481
-rw-r--r--tests/docx/document-xml-reader.tests.js483
5 files changed, 849 insertions, 817 deletions
diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js
new file mode 100644
index 0000000..e6d8857
--- /dev/null
+++ b/lib/docx/body-reader.js
@@ -0,0 +1,342 @@
+exports.BodyReader = BodyReader;
+
+var _ = require("underscore");
+
+var documents = require("../documents");
+var Result = require("../results").Result;
+var warning = require("../results").warning;
+
+
+function BodyReader(options) {
+ var relationships = options.relationships;
+ var contentTypes = options.contentTypes;
+ var docxFile = options.docxFile;
+ var numbering = options.numbering;
+ var styles = options.styles;
+
+ function readXmlElements(elements) {
+ var results = elements.map(readXmlElement);
+ return combineResults(results);
+ }
+
+ function readXmlElement(element) {
+ if (element.type === "element") {
+ var handler = xmlElementReaders[element.name];
+ if (handler) {
+ return handler(element);
+ } else if (!Object.prototype.hasOwnProperty.call(ignoreElements, element.name)) {
+ var message = warning("An unrecognised element was ignored: " + element.name);
+ return emptyResultWithMessages([message]);
+ }
+ }
+ return emptyResult();
+ }
+
+ function readRunProperties(element) {
+ var properties = {
+ type: "runProperties"
+ };
+
+ readRunStyle(properties, element);
+
+ var verticalAlignmentElement = element.first("w:vertAlign");
+ if (verticalAlignmentElement) {
+ properties.verticalAlignment = verticalAlignmentElement.attributes["w:val"];
+ }
+
+ properties.isBold = !!element.first("w:b");
+ properties.isUnderline = !!element.first("w:u");
+ properties.isItalic = !!element.first("w:i");
+ properties.isStrikethrough = !!element.first("w:strike");
+
+ return elementResult(properties);
+ }
+
+ function readRunStyle(properties, element) {
+ var styleElement = element.first("w:rStyle");
+ if (styleElement) {
+ var styleId = styleElement.attributes["w:val"];
+ properties.styleId = styleId;
+ if (styleId) {
+ var style = styles.findCharacterStyleById(styleId);
+ properties.styleName = style.name;
+ }
+ }
+ }
+
+ function noteReferenceReader(noteType) {
+ return function(element) {
+ var noteId = element.attributes["w:id"];
+ return elementResult(new documents.NoteReference({
+ noteType: noteType,
+ noteId: noteId
+ }));
+ };
+ }
+
+ function readChildElements(element) {
+ return readXmlElements(element.children);
+ }
+
+ var xmlElementReaders = {
+ "w:p": function(element) {
+ return readXmlElements(element.children)
+ .map(function(children) {
+ var properties = _.find(children, isParagraphProperties);
+ return new documents.Paragraph(
+ children.filter(negate(isParagraphProperties)),
+ properties
+ );
+ })
+ .insertExtra();
+ },
+ "w:pPr": function(element) {
+ var properties = {
+ type: "paragraphProperties"
+ };
+
+ var styleElement = element.first("w:pStyle");
+ if (styleElement) {
+ var styleId = styleElement.attributes["w:val"];
+ properties.styleId = styleId;
+ if (styleId) {
+ var style = styles.findParagraphStyleById(styleId);
+ properties.styleName = style.name;
+ }
+ }
+ var alignElement = element.first("w:jc");
+ if (alignElement) {
+ properties.alignment = alignElement.attributes["w:val"];
+ }
+ var numberingPropertiesElement = element.first("w:numPr");
+ if (numberingPropertiesElement) {
+ var levelElement = numberingPropertiesElement.first("w:ilvl");
+ var level = levelElement.attributes["w:val"];
+ var numId = numberingPropertiesElement.first("w:numId").attributes["w:val"];
+ properties.numbering = numbering.findLevel(numId, level);
+ }
+
+ return elementResult(properties);
+ },
+ "w:r": function(element) {
+ return readXmlElements(element.children)
+ .map(function(children) {
+ var properties = _.find(children, isRunProperties);
+
+ return new documents.Run(
+ children.filter(negate(isRunProperties)),
+ properties
+ );
+ });
+ },
+ "w:rPr": readRunProperties,
+ "w:t": function(element) {
+ return elementResult(new documents.Text(element.text()));
+ },
+ "w:tab": function(element) {
+ return elementResult(new documents.Tab());
+ },
+ "w:hyperlink": function(element) {
+ var relationshipId = element.attributes["r:id"];
+ var anchor = element.attributes["w:anchor"];
+ return readXmlElements(element.children).map(function(children) {
+ if (relationshipId) {
+ var href = relationships[relationshipId].target;
+ return new documents.Hyperlink(children, {href: href});
+ } else if (anchor) {
+ return new documents.Hyperlink(children, {anchor: anchor});
+ } else {
+ return children;
+ }
+ });
+ },
+ "w:tbl": function(element) {
+ return readXmlElements(element.children).map(documents.Table);
+ },
+ "w:tr": function(element) {
+ return readXmlElements(element.children).map(documents.TableRow);
+ },
+ "w:tc": function(element) {
+ return readXmlElements(element.children).map(documents.TableCell);
+ },
+ "w:footnoteReference": noteReferenceReader("footnote"),
+ "w:endnoteReference": noteReferenceReader("endnote"),
+ "w:br": function(element) {
+ var breakType = element.attributes["w:type"];
+ if (breakType) {
+ return emptyResultWithMessages([warning("Unsupported break type: " + breakType)]);
+ } else {
+ return elementResult(new documents.LineBreak());
+ }
+ },
+ "w:bookmarkStart": function(element){
+ var name = element.attributes["w:name"];
+ if (name === "_GoBack") {
+ return emptyResult();
+ } else {
+ return elementResult(new documents.BookmarkStart({name: name}));
+ }
+ },
+
+ "mc:AlternateContent": function(element) {
+ return readChildElements(element.first("mc:Fallback"));
+ },
+
+ "w:ins": readChildElements,
+ "w:smartTag": readChildElements,
+ "w:drawing": readChildElements,
+ "w:pict": function(element) {
+ return readChildElements(element).toExtra();
+ },
+ "v:shape": readChildElements,
+ "v:textbox": readChildElements,
+ "w:txbxContent": readChildElements,
+ "wp:inline": readDrawingElement,
+ "wp:anchor": readDrawingElement
+ };
+ return {
+ readXmlElement: readXmlElement,
+ readXmlElements: readXmlElements
+ };
+
+ function readDrawingElement(element) {
+ var blips = element
+ .getElementsByTagName("a:graphic")
+ .getElementsByTagName("a:graphicData")
+ .getElementsByTagName("pic:pic")
+ .getElementsByTagName("pic:blipFill")
+ .getElementsByTagName("a:blip");
+
+ return combineResults(blips.map(readBlip.bind(null, element)));
+ }
+
+ function readBlip(element, blip) {
+ var relationshipId = blip.attributes["r:embed"];
+ var imagePath = joinZipPath("word", relationships[relationshipId].target);
+ var readImage = docxFile.read.bind(docxFile, imagePath);
+ var altText = element.first("wp:docPr").attributes.descr;
+ var contentType = contentTypes.findContentType(imagePath);
+
+ var image = documents.Image({
+ readImage: readImage,
+ altText: altText,
+ contentType: contentType
+ });
+ var warnings = supportedImageTypes[contentType] ?
+ [] : warning("Image of type " + contentType + " is unlikely to display in web browsers");
+ return elementResultWithMessages(image, warnings);
+ }
+}
+
+var supportedImageTypes = {
+ "image/png": true,
+ "image/gif": true,
+ "image/jpeg": true,
+ "image/svg+xml": true,
+ "image/tiff": true
+};
+
+var ignoreElements = {
+ "w:bookmarkEnd": true,
+ "w:sectPr": true,
+ "w:proofErr": true,
+ "w:lastRenderedPageBreak": true,
+ "w:commentRangeStart": true,
+ "w:commentRangeEnd": true,
+ "w:commentReference": true,
+ "w:del": true,
+ "w:footnoteRef": true,
+ "w:endnoteRef": true,
+ "w:tblPr": true,
+ "w:tblGrid": true,
+ "w:tcPr": true
+};
+
+function isParagraphProperties(element) {
+ return element.type === "paragraphProperties";
+}
+
+function isRunProperties(element) {
+ return element.type === "runProperties";
+}
+
+function negate(predicate) {
+ return function(value) {
+ return !predicate(value);
+ };
+}
+
+function joinZipPath(first, second) {
+ // In general, we should check first and second for trailing and leading slashes,
+ // but in our specific case this seems to be sufficient
+ return first + "/" + second;
+}
+
+
+
+function emptyResultWithMessages(messages) {
+ return new ReadResult(null, null, messages);
+}
+
+function emptyResult() {
+ return new ReadResult(null);
+}
+
+function elementResult(element) {
+ return new ReadResult(element);
+}
+
+function elementResultWithMessages(element, messages) {
+ return new ReadResult(element, null, messages);
+}
+
+function ReadResult(element, extra, messages) {
+ this.value = element || [];
+ this.extra = extra;
+ this._result = new Result({
+ element: this.value,
+ extra: extra
+ }, messages);
+ this.messages = this._result.messages;
+}
+
+ReadResult.prototype.toExtra = function() {
+ return new ReadResult(null, joinElements(this.extra, this.value), this.messages);
+};
+
+ReadResult.prototype.insertExtra = function() {
+ var extra = this.extra;
+ if (extra && extra.length) {
+ return new ReadResult(joinElements(this.value, extra), null, this.messages);
+ } else {
+ return this;
+ }
+};
+
+ReadResult.prototype.map = function(func) {
+ var result = this._result.map(function(value) {
+ return func(value.element);
+ });
+ return new ReadResult(result.value, this.extra, result.messages);
+};
+
+ReadResult.prototype.flatMap = function(func) {
+ var result = this._result.flatMap(function(value) {
+ var funcResult = func(value.element);
+ return funcResult._result || elementResultWithMessages(funcResult.value, funcResult.messages)._result;
+ });
+ return new ReadResult(result.value.element, joinElements(this.extra, result.value.extra), result.messages);
+};
+
+function combineResults(results) {
+ var result = Result.combine(_.pluck(results, "_result"));
+ return new ReadResult(
+ _.flatten(_.pluck(result.value, "element")),
+ _.filter(_.flatten(_.pluck(result.value, "extra")), function(x) { return x; }),
+ result.messages
+ );
+}
+
+function joinElements(first, second) {
+ return _.flatten([first, second]);
+}
diff --git a/lib/docx/document-xml-reader.js b/lib/docx/document-xml-reader.js
index 1d36d91..fdf451f 100644
--- a/lib/docx/document-xml-reader.js
+++ b/lib/docx/document-xml-reader.js
@@ -1,25 +1,18 @@
exports.DocumentXmlReader = DocumentXmlReader;
-var _ = require("underscore");
-
var documents = require("../documents");
var Result = require("../results").Result;
-var warning = require("../results").warning;
function DocumentXmlReader(options) {
- var relationships = options.relationships;
- var contentTypes = options.contentTypes;
- var docxFile = options.docxFile;
- var numbering = options.numbering;
- var styles = options.styles;
+ var bodyReader = options.bodyReader;
var rawNotes = (options.footnotes || []).concat(options.endnotes || []);
function convertXmlToDocument(documentXml) {
var body = documentXml.root.first("w:body");
- var result = readXmlElements(body.children)
+ var result = bodyReader.readXmlElements(body.children)
.flatMap(function(children) {
return readNotes().map(function(notes) {
return new documents.Document(children, {notes: notes});
@@ -29,8 +22,8 @@ function DocumentXmlReader(options) {
}
function readNotes() {
- return combineResults(rawNotes.map(function(rawNote) {
- return readXmlElements(rawNote.body).map(function(body) {
+ return Result.combine(rawNotes.map(function(rawNote) {
+ return bodyReader.readXmlElements(rawNote.body).map(function(body) {
return new documents.Note({
noteType: rawNote.noteType,
noteId: rawNote.id,
@@ -41,329 +34,8 @@ function DocumentXmlReader(options) {
return new documents.Notes(notes);
});
}
-
- function readXmlElements(elements) {
- var results = elements.map(readXmlElement);
- return combineResults(results);
- }
-
- function readXmlElement(element) {
- if (element.type === "element") {
- var handler = xmlElementReaders[element.name];
- if (handler) {
- return handler(element);
- } else if (!Object.prototype.hasOwnProperty.call(ignoreElements, element.name)) {
- var message = warning("An unrecognised element was ignored: " + element.name);
- return emptyResultWithMessages([message]);
- }
- }
- return emptyResult();
- }
-
- function readRunProperties(element) {
- var properties = {
- type: "runProperties"
- };
-
- readRunStyle(properties, element);
-
- var verticalAlignmentElement = element.first("w:vertAlign");
- if (verticalAlignmentElement) {
- properties.verticalAlignment = verticalAlignmentElement.attributes["w:val"];
- }
-
- properties.isBold = !!element.first("w:b");
- properties.isUnderline = !!element.first("w:u");
- properties.isItalic = !!element.first("w:i");
- properties.isStrikethrough = !!element.first("w:strike");
-
- return elementResult(properties);
- }
-
- function readRunStyle(properties, element) {
- var styleElement = element.first("w:rStyle");
- if (styleElement) {
- var styleId = styleElement.attributes["w:val"];
- properties.styleId = styleId;
- if (styleId) {
- var style = styles.findCharacterStyleById(styleId);
- properties.styleName = style.name;
- }
- }
- }
-
- function noteReferenceReader(noteType) {
- return function(element) {
- var noteId = element.attributes["w:id"];
- return elementResult(new documents.NoteReference({
- noteType: noteType,
- noteId: noteId
- }));
- };
- }
- function readChildElements(element) {
- return readXmlElements(element.children);
- }
-
- var xmlElementReaders = {
- "w:p": function(element) {
- return readXmlElements(element.children)
- .map(function(children) {
- var properties = _.find(children, isParagraphProperties);
- return new documents.Paragraph(
- children.filter(negate(isParagraphProperties)),
- properties
- );
- })
- .insertExtra();
- },
- "w:pPr": function(element) {
- var properties = {
- type: "paragraphProperties"
- };
-
- var styleElement = element.first("w:pStyle");
- if (styleElement) {
- var styleId = styleElement.attributes["w:val"];
- properties.styleId = styleId;
- if (styleId) {
- var style = styles.findParagraphStyleById(styleId);
- properties.styleName = style.name;
- }
- }
- var alignElement = element.first("w:jc");
- if (alignElement) {
- properties.alignment = alignElement.attributes["w:val"];
- }
- var numberingPropertiesElement = element.first("w:numPr");
- if (numberingPropertiesElement) {
- var levelElement = numberingPropertiesElement.first("w:ilvl");
- var level = levelElement.attributes["w:val"];
- var numId = numberingPropertiesElement.first("w:numId").attributes["w:val"];
- properties.numbering = numbering.findLevel(numId, level);
- }
-
- return elementResult(properties);
- },
- "w:r": function(element) {
- return readXmlElements(element.children)
- .map(function(children) {
- var properties = _.find(children, isRunProperties);
-
- return new documents.Run(
- children.filter(negate(isRunProperties)),
- properties
- );
- });
- },
- "w:rPr": readRunProperties,
- "w:t": function(element) {
- return elementResult(new documents.Text(element.text()));
- },
- "w:tab": function(element) {
- return elementResult(new documents.Tab());
- },
- "w:hyperlink": function(element) {
- var relationshipId = element.attributes["r:id"];
- var anchor = element.attributes["w:anchor"];
- return readXmlElements(element.children).map(function(children) {
- if (relationshipId) {
- var href = relationships[relationshipId].target;
- return new documents.Hyperlink(children, {href: href});
- } else if (anchor) {
- return new documents.Hyperlink(children, {anchor: anchor});
- } else {
- return children;
- }
- });
- },
- "w:tbl": function(element) {
- return readXmlElements(element.children).map(documents.Table);
- },
- "w:tr": function(element) {
- return readXmlElements(element.children).map(documents.TableRow);
- },
- "w:tc": function(element) {
- return readXmlElements(element.children).map(documents.TableCell);
- },
- "w:footnoteReference": noteReferenceReader("footnote"),
- "w:endnoteReference": noteReferenceReader("endnote"),
- "w:br": function(element) {
- var breakType = element.attributes["w:type"];
- if (breakType) {
- return emptyResultWithMessages([warning("Unsupported break type: " + breakType)]);
- } else {
- return elementResult(new documents.LineBreak());
- }
- },
- "w:bookmarkStart": function(element){
- var name = element.attributes["w:name"];
- if (name === "_GoBack") {
- return emptyResult();
- } else {
- return elementResult(new documents.BookmarkStart({name: name}));
- }
- },
-
- "mc:AlternateContent": function(element) {
- return readChildElements(element.first("mc:Fallback"));
- },
-
- "w:ins": readChildElements,
- "w:smartTag": readChildElements,
- "w:drawing": readChildElements,
- "w:pict": function(element) {
- return readChildElements(element).toExtra();
- },
- "v:shape": readChildElements,
- "v:textbox": readChildElements,
- "w:txbxContent": readChildElements,
- "wp:inline": readDrawingElement,
- "wp:anchor": readDrawingElement
- };
return {
- convertXmlToDocument: convertXmlToDocument,
- readXmlElement: readXmlElement
+ convertXmlToDocument: convertXmlToDocument
};
-
- function readDrawingElement(element) {
- var blips = element
- .getElementsByTagName("a:graphic")
- .getElementsByTagName("a:graphicData")
- .getElementsByTagName("pic:pic")
- .getElementsByTagName("pic:blipFill")
- .getElementsByTagName("a:blip");
-
- return combineResults(blips.map(readBlip.bind(null, element)));
- }
-
- function readBlip(element, blip) {
- var relationshipId = blip.attributes["r:embed"];
- var imagePath = joinZipPath("word", relationships[relationshipId].target);
- var readImage = docxFile.read.bind(docxFile, imagePath);
- var altText = element.first("wp:docPr").attributes.descr;
- var contentType = contentTypes.findContentType(imagePath);
-
- var image = documents.Image({
- readImage: readImage,
- altText: altText,
- contentType: contentType
- });
- var warnings = supportedImageTypes[contentType] ?
- [] : warning("Image of type " + contentType + " is unlikely to display in web browsers");
- return elementResultWithMessages(image, warnings);
- }
-}
-
-var supportedImageTypes = {
- "image/png": true,
- "image/gif": true,
- "image/jpeg": true,
- "image/svg+xml": true,
- "image/tiff": true
-};
-
-var ignoreElements = {
- "w:bookmarkEnd": true,
- "w:sectPr": true,
- "w:proofErr": true,
- "w:lastRenderedPageBreak": true,
- "w:commentRangeStart": true,
- "w:commentRangeEnd": true,
- "w:commentReference": true,
- "w:del": true,
- "w:footnoteRef": true,
- "w:endnoteRef": true,
- "w:tblPr": true,
- "w:tblGrid": true,
- "w:tcPr": true
-};
-
-function isParagraphProperties(element) {
- return element.type === "paragraphProperties";
-}
-
-function isRunProperties(element) {
- return element.type === "runProperties";
-}
-
-function negate(predicate) {
- return function(value) {
- return !predicate(value);
- };
-}
-
-function joinZipPath(first, second) {
- // In general, we should check first and second for trailing and leading slashes,
- // but in our specific case this seems to be sufficient
- return first + "/" + second;
-}
-
-
-
-function emptyResultWithMessages(messages) {
- return new ReadResult(null, null, messages);
-}
-
-function emptyResult() {
- return new ReadResult(null);
-}
-
-function elementResult(element) {
- return new ReadResult(element);
-}
-
-function elementResultWithMessages(element, messages) {
- return new ReadResult(element, null, messages);
-}
-
-function ReadResult(element, extra, messages) {
- this.value = element || [];
- this.extra = extra;
- this._result = new Result({
- element: this.value,
- extra: extra
- }, messages);
- this.messages = this._result.messages;
-}
-
-ReadResult.prototype.toExtra = function() {
- return new ReadResult(null, joinElements(this.extra, this.value), this.messages);
-};
-
-ReadResult.prototype.insertExtra = function() {
- var extra = this.extra;
- if (extra && extra.length) {
- return new ReadResult(joinElements(this.value, extra), null, this.messages);
- } else {
- return this;
- }
-};
-
-ReadResult.prototype.map = function(func) {
- var result = this._result.map(function(value) {
- return func(value.element);
- });
- return new ReadResult(result.value, this.extra, result.messages);
-};
-
-ReadResult.prototype.flatMap = function(func) {
- var result = this._result.flatMap(function(value) {
- return func(value.element)._result;
- });
- return new ReadResult(result.value.element, joinElements(this.extra, result.value.extra), result.messages);
-};
-
-function combineResults(results) {
- var result = Result.combine(_.pluck(results, "_result"));
- return new ReadResult(
- _.flatten(_.pluck(result.value, "element")),
- _.filter(_.flatten(_.pluck(result.value, "extra")), function(x) { return x; }),
- result.messages
- );
-}
-
-function joinElements(first, second) {
- return _.flatten([first, second]);
}
diff --git a/lib/docx/docx-reader.js b/lib/docx/docx-reader.js
index 23f821c..554e329 100644
--- a/lib/docx/docx-reader.js
+++ b/lib/docx/docx-reader.js
@@ -4,6 +4,7 @@ var promises = require("../promises");
var unzip = require("../unzip");
var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile;
+var BodyReader = require("./body-reader").BodyReader;
var DocumentXmlReader = require("./document-xml-reader").DocumentXmlReader;
var relationshipsReader = require("./relationships-reader");
var contentTypesReader = require("./content-types-reader");
@@ -19,17 +20,24 @@ function read(options) {
readRelationshipsFromZipFile(docxFile),
readContentTypesFromZipFile(docxFile),
readNumberingFromZipFile(docxFile),
- readStylesFromZipFile(docxFile),
- readFootnotesFromZipFile(docxFile),
- readEndnotesFromZipFile(docxFile),
- readXmlFromZipFile(docxFile, "word/document.xml"),
- ]).spread(function(relationships, contentTypes, numbering, styles, footnotes, endnotes, documentXml) {
- var reader = new DocumentXmlReader({
+ readStylesFromZipFile(docxFile)
+ ]).spread(function(relationships, contentTypes, numbering, styles) {
+ var bodyReader = new BodyReader({
relationships: relationships,
contentTypes: contentTypes,
docxFile: docxFile,
numbering: numbering,
- styles: styles,
+ styles: styles
+ });
+ return promises.all([
+ bodyReader,
+ readFootnotesFromZipFile(docxFile),
+ readEndnotesFromZipFile(docxFile),
+ readXmlFromZipFile(docxFile, "word/document.xml")
+ ]);
+ }).spread(function(bodyReader, footnotes, endnotes, documentXml) {
+ var reader = new DocumentXmlReader({
+ bodyReader: bodyReader,
footnotes: footnotes,
endnotes: endnotes
});
diff --git a/tests/docx/body-reader.tests.js b/tests/docx/body-reader.tests.js
new file mode 100644
index 0000000..acc8ca3
--- /dev/null
+++ b/tests/docx/body-reader.tests.js
@@ -0,0 +1,481 @@
+var assert = require("assert");
+var path = require("path");
+
+var BodyReader = require("../../lib/docx/body-reader").BodyReader;
+var documents = require("../../lib/documents");
+var XmlElement = require("../../lib/xmlreader").Element;
+var Numbering = require("../../lib/docx/numbering-xml").Numbering;
+var Styles = require("../../lib/docx/styles-reader").Styles;
+var warning = require("../../lib/results").warning;
+
+var testing = require("../testing");
+var test = testing.test;
+var createFakeDocxFile = testing.createFakeDocxFile;
+
+function readXmlElement(element, options) {
+ var styles = {
+ findParagraphStyleById: function(id) {
+ return {};
+ }
+ };
+ options = options || {styles: styles};
+ return new BodyReader(options).readXmlElement(element);
+}
+
+function readXmlElementValue(element, options) {
+ var result = readXmlElement(element, options);
+ assert.deepEqual(result.messages, []);
+ return result.value;
+}
+
+var fakeContentTypes = {
+ findContentType: function(filePath) {
+ var extensionTypes = {
+ ".png": "image/png",
+ ".emf": "image/x-emf"
+ };
+ return extensionTypes[path.extname(filePath)];
+ }
+};
+
+describe("readXmlElement: ", function() {
+ test("paragraph has no style if it has no properties", function() {
+ var paragraphXml = new XmlElement("w:p", {}, []);
+ var paragraph = readXmlElementValue(paragraphXml);
+ assert.deepEqual(paragraph.styleId, null);
+ });
+
+ test("paragraph has style ID read from paragraph properties if present", function() {
+ var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []);
+ var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]);
+ var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
+
+ var styles = new Styles({"Heading1": {name: "Heading 1"}}, {});
+
+ var paragraph = readXmlElementValue(paragraphXml, {styles: styles});
+ assert.deepEqual(paragraph.styleId, "Heading1");
+ });
+
+ test("paragraph has style name read from paragraph properties and styles", function() {
+ var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []);
+ var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]);
+ var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
+
+ var styles = new Styles({"Heading1": {name: "Heading 1"}}, {});
+
+ var paragraph = readXmlElementValue(paragraphXml, {styles: styles});
+ assert.deepEqual(paragraph.styleName, "Heading 1");
+ });
+
+ test("paragraph has justification read from paragraph properties if present", function() {
+ var justificationXml = new XmlElement("w:jc", {"w:val": "center"}, []);
+ var propertiesXml = new XmlElement("w:pPr", {}, [justificationXml]);
+ var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
+ var paragraph = readXmlElementValue(paragraphXml);
+ assert.deepEqual(paragraph.alignment, "center");
+ });
+
+ test("paragraph has numbering properties from paragraph properties if present", function() {
+ var numberingPropertiesXml = new XmlElement("w:numPr", {}, [
+ new XmlElement("w:ilvl", {"w:val": "1"}),
+ new XmlElement("w:numId", {"w:val": "42"})
+ ]);
+ var propertiesXml = new XmlElement("w:pPr", {}, [numberingPropertiesXml]);
+ var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
+
+ var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}});
+
+ var reader = new BodyReader({
+ numbering: numbering
+ });
+ var paragraph = reader.readXmlElement(paragraphXml).value;
+ assert.deepEqual(paragraph.numbering, {level: "1", isOrdered: true});
+ });
+
+ test("run has no style if it has no properties", function() {
+ var runXml = runWithProperties([]);
+ var run = readXmlElementValue(runXml);
+ assert.deepEqual(run.styleId, null);
+ });
+
+ test("run has style ID read from run properties if present", function() {
+ var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"});
+ var runXml = runWithProperties([runStyleXml]);
+
+ var styles = new Styles({}, {"Heading1Char": {name: "Heading 1 Char"}});
+
+ var run = readXmlElementValue(runXml, {styles: styles});
+ assert.deepEqual(run.styleId, "Heading1Char");
+ });
+
+ test("run has style name read from run properties and styles", function() {
+ var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"});
+ var runXml = runWithProperties([runStyleXml]);
+
+ var styles = new Styles({}, {"Heading1Char": {name: "Heading 1 Char"}});
+
+ var run = readXmlElementValue(runXml, {styles: styles});
+ assert.deepEqual(run.styleName, "Heading 1 Char");
+ });
+
+ test("isBold is false if bold element is not present", function() {
+ var runXml = runWithProperties([]);
+ var run = readXmlElementValue(runXml);
+ assert.deepEqual(run.isBold, false);
+ });
+
+ test("isBold is true if bold element is present", function() {
+ var boldXml = new XmlElement("w:b");
+ var runXml = runWithProperties([boldXml]);
+ var run = readXmlElementValue(runXml);
+ assert.equal(run.isBold, true);
+ });
+
+ test("isUnderline is false if underline element is not present", function() {
+ var runXml = runWithProperties([]);
+ var run = readXmlElementValue(runXml);
+ assert.deepEqual(run.isUnderline, false);
+ });
+
+ test("isUnderline is true if underline element is present", function() {
+ var underlineXml = new XmlElement("w:u");
+ var runXml = runWithProperties([underlineXml]);
+ var run = readXmlElementValue(runXml);
+ assert.equal(run.isUnderline, true);
+ });
+
+ test("isStrikethrough is false if strikethrough element is not present", function() {
+ var runXml = runWithProperties([]);
+ var run = readXmlElementValue(runXml);
+ assert.deepEqual(run.isStrikethrough, false);
+ });
+
+ test("isStrikethrough is true if strikethrough element is present", function() {
+ var strikethroughXml = new XmlElement("w:strike");
+ var runXml = runWithProperties([strikethroughXml]);
+ var run = readXmlElementValue(runXml);
+ assert.equal(run.isStrikethrough, true);
+ });
+
+ test("isItalic is false if bold element is not present", function() {
+ var runXml = runWithProperties([]);
+ var run = readXmlElementValue(runXml);
+ assert.deepEqual(run.isItalic, false);
+ });
+
+ test("isItalic is true if bold element is present", function() {
+ var italicXml = new XmlElement("w:i");
+ var runXml = runWithProperties([italicXml]);
+ var run = readXmlElementValue(runXml);
+ assert.equal(run.isItalic, true);
+ });
+
+ test("run has baseline vertical alignment by default", function() {
+ var runXml = runWithProperties([]);
+ var run = readXmlElementValue(runXml);
+ assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.baseline);
+ });
+
+ test("run has vertical alignment read from properties", function() {
+ var verticalAlignmentXml = new XmlElement("w:vertAlign", {"w:val": "superscript"});
+ var runXml = runWithProperties([verticalAlignmentXml]);
+
+ var run = readXmlElementValue(runXml);
+ assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.superscript);
+ });
+
+ test("run properties not included as child of run", function() {
+ var runStyleXml = new XmlElement("w:rStyle");
+ var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]);
+ var runXml = new XmlElement("w:r", {}, [runPropertiesXml]);
+ var result = readXmlElement(runXml);
+ assert.deepEqual(result.value.children, []);
+ });
+
+ test("w:tab is read as document tab element", function() {
+ var tabXml = new XmlElement("w:tab");
+ var result = readXmlElement(tabXml);
+ assert.deepEqual(result.value, new documents.Tab());
+ });
+
+ test("w:table is read as document table element", function() {
+ var tableXml = new XmlElement("w:tbl", {}, [
+ new XmlElement("w:tr", {}, [
+ new XmlElement("w:tc", {}, [
+ new XmlElement("w:p", {}, [])
+ ])
+ ])
+ ]);
+ var result = readXmlElement(tableXml);
+ assert.deepEqual(result.value, new documents.Table([
+ new documents.TableRow([
+ new documents.TableCell([
+ new documents.Paragraph([])
+ ])
+ ])
+ ]));
+ });
+
+ test("emits warning on unrecognised element", function() {
+ var unrecognisedElement = new XmlElement("w:not-an-element");
+ var result = readXmlElement(unrecognisedElement);
+ assert.deepEqual(
+ result.messages,
+ [{
+ type: "warning",
+ message: "An unrecognised element was ignored: w:not-an-element"
+ }]
+ );
+ });
+
+ test("w:bookmarkStart is read as a bookmarkStart", function() {
+ var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_Peter", "w:id": "42"});
+ var result = readXmlElement(bookmarkStart);
+ assert.deepEqual(result.value.name, "_Peter");
+ assert.deepEqual(result.value.type, "bookmarkStart");
+ });
+
+ test('_GoBack bookmark is ignored', function() {
+ var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_GoBack"});
+ var result = readXmlElement(bookmarkStart);
+ assert.deepEqual(result.value, []);
+ });
+
+ test("can read inline pictures", function() {
+ var drawing = createInlineImage({
+ relationshipId: "rId5",
+ description: "It's a hat"
+ });
+
+ var imageBuffer = new Buffer("Not an image at all!");
+ var reader = new BodyReader({
+ relationships: {
+ "rId5": {target: "media/hat.png"}
+ },
+ contentTypes: fakeContentTypes,
+ docxFile: createFakeDocxFile({
+ "word/media/hat.png": imageBuffer
+ })
+ });
+ var result = reader.readXmlElement(drawing);
+ assert.deepEqual(result.messages, []);
+ var element = single(result.value);
+ assert.equal("image", element.type);
+ assert.equal(element.altText, "It's a hat");
+ assert.equal(element.contentType, "image/png");
+ return element.read()
+ .then(function(readValue) {
+ assert.equal(readValue, imageBuffer);
+ });
+ });
+
+ test("can read anchored pictures", function() {
+ var drawing = new XmlElement("w:drawing", {}, [
+ new XmlElement("wp:anchor", {}, [
+ new XmlElement("wp:docPr", {descr: "It's a hat"}),
+ new XmlElement("a:graphic", {}, [
+ new XmlElement("a:graphicData", {}, [
+ new XmlElement("pic:pic", {}, [
+ new XmlElement("pic:blipFill", {}, [
+ new XmlElement("a:blip", {"r:embed": "rId5"})
+ ])
+ ])
+ ])
+ ])
+ ])
+ ]);
+
+ var imageBuffer = new Buffer("Not an image at all!");
+ var reader = new BodyReader({
+ relationships: {
+ "rId5": {target: "media/hat.png"}
+ },
+ contentTypes: fakeContentTypes,
+ docxFile: createFakeDocxFile({
+ "word/media/hat.png": imageBuffer
+ })
+ });
+ var result = reader.readXmlElement(drawing);
+ assert.deepEqual(result.messages, []);
+ var element = single(result.value);
+ assert.equal("image", element.type);
+ assert.equal(element.altText, "It's a hat");
+ return element.read()
+ .then(function(readValue) {
+ assert.equal(readValue, imageBuffer);
+ });
+ });
+
+ test("warning if unsupported image type", function() {
+ var drawing = createInlineImage({
+ relationshipId: "rId5",
+ description: "It's a hat"
+ });
+
+ var imageBuffer = new Buffer("Not an image at all!");
+ var reader = new BodyReader({
+ relationships: {
+ "rId5": {target: "media/hat.emf"}
+ },
+ contentTypes: fakeContentTypes,
+ docxFile: createFakeDocxFile({
+ "word/media/hat.emf": imageBuffer
+ })
+ });
+ var result = reader.readXmlElement(drawing);
+ assert.deepEqual(result.messages, [warning("Image of type image/x-emf is unlikely to display in web browsers")]);
+ var element = single(result.value);
+ assert.equal(element.contentType, "image/x-emf");
+ });
+
+ test("no elements created if image cannot be found in w:drawing", function() {
+ var drawing = new XmlElement("w:drawing", {}, []);
+
+ var reader = new BodyReader({});
+ var result = reader.readXmlElement(drawing);
+ assert.deepEqual(result.messages, []);
+ assert.deepEqual(result.value, []);
+ });
+
+ test("no elements created if image cannot be found in wp:inline", function() {
+ var drawing = new XmlElement("wp:inline", {}, []);
+
+ var reader = new BodyReader({});
+ var result = reader.readXmlElement(drawing);
+ assert.deepEqual(result.messages, []);
+ assert.deepEqual(result.value, []);
+ });
+
+ test("children of w:ins are converted normally", function() {
+ var runXml = new XmlElement("w:r", {}, []);
+ var insXml = new XmlElement("w:ins", {}, [runXml]);
+ var result = readXmlElement(insXml);
+ assert.deepEqual(result.value[0].type, "run");
+ });
+
+ test("children of w:smartTag are converted normally", function() {
+ var runXml = new XmlElement("w:r", {}, []);
+ var smartTagXml = new XmlElement("w:smartTag", {}, [runXml]);
+ var result = readXmlElement(smartTagXml);
+ assert.deepEqual(result.value[0].type, "run");
+ });
+
+ test("w:hyperlink is read as document hyperlink if it has a relationship ID", function() {
+ var runXml = new XmlElement("w:r", {}, []);
+ var hyperlinkXml = new XmlElement("w:hyperlink", {"r:id": "r42"}, [runXml]);
+ var relationships = {
+ "r42": {target: "http://example.com"}
+ };
+ var result = readXmlElement(hyperlinkXml, {relationships: relationships});
+ assert.deepEqual(result.value.href, "http://example.com");
+ assert.deepEqual(result.value.children[0].type, "run");
+ });
+
+ test("w:hyperlink is read as document hyperlink if it has an anchor", function() {
+ var runXml = new XmlElement("w:r", {}, []);
+ var hyperlinkXml = new XmlElement("w:hyperlink", {"w:anchor": "_Peter"}, [runXml]);
+ var result = readXmlElement(hyperlinkXml);
+ assert.deepEqual(result.value.anchor, "_Peter");
+ assert.deepEqual(result.value.children[0].type, "run");
+ });
+
+ test("w:hyperlink is ignored if it does not have a relationship ID nor anchor", function() {
+ var runXml = new XmlElement("w:r", {}, []);
+ var hyperlinkXml = new XmlElement("w:hyperlink", {}, [runXml]);
+ var result = readXmlElement(hyperlinkXml);
+ assert.deepEqual(result.value[0].type, "run");
+ });
+
+ test("w:br is read as line break", function() {
+ var breakXml = new XmlElement("w:br", {}, []);
+ var result = readXmlElement(breakXml);
+ assert.deepEqual(result.value.type, "lineBreak");
+ assert.deepEqual(result.messages, []);
+ });
+
+ test("w:bookmarkEnd is ignored without warning", function() {
+ var ignoredElement = new XmlElement("w:bookmarkEnd");
+ var result = readXmlElement(ignoredElement);
+ assert.deepEqual(result.messages, []);
+ assert.deepEqual([], result.value);
+ });
+
+ test("warning on breaks that aren't line breaks", function() {
+ var breakXml = new XmlElement("w:br", {"w:type": "page"}, []);
+ var result = readXmlElement(breakXml);
+ assert.deepEqual(result.value, []);
+ assert.deepEqual(result.messages, [warning("Unsupported break type: page")]);
+ });
+
+ test("text boxes have content appended after containing paragraph", function() {
+ var textbox = new XmlElement("w:pict", {}, [
+ new XmlElement("v:shape", {}, [
+ new XmlElement("v:textbox", {}, [
+ new XmlElement("w:txbxContent", {}, [
+ paragraphWithStyleId("textbox-content")
+ ])
+ ])
+ ])
+ ]);
+ var paragraph = new XmlElement("w:p", {}, [
+ new XmlElement("w:r", {}, [textbox])
+ ]);
+ var result = readXmlElement(paragraph);
+ assert.deepEqual(result.value[1].styleId, "textbox-content");
+ });
+
+ test("mc:Fallback is used when mc:AlternateContent is read", function() {
+ var styles = new Styles({"first": {name: "First"}, "second": {name: "Second"}}, {});
+ var textbox = new XmlElement("mc:AlternateContent", {}, [
+ new XmlElement("mc:Choice", {"Requires": "wps"}, [
+ paragraphWithStyleId("first")
+ ]),
+ new XmlElement("mc:Fallback", {}, [
+ paragraphWithStyleId("second")
+ ])
+ ]);
+ var result = readXmlElement(textbox, {styles: styles});
+ assert.deepEqual(result.value[0].styleId, "second");
+ });
+});
+
+function paragraphWithStyleId(styleId) {
+ return new XmlElement("w:p", {}, [
+ new XmlElement("w:pPr", {}, [
+ new XmlElement("w:pStyle", {"w:val": styleId}, [])
+ ])
+ ]);
+}
+
+function runWithProperties(children) {
+ return new XmlElement("w:r", {}, [createRunPropertiesXml(children)]);
+}
+
+function createRunPropertiesXml(children) {
+ return new XmlElement("w:rPr", {}, children);
+}
+
+function single(array) {
+ if (array.length === 1) {
+ return array[0];
+ } else {
+ throw new Error("Array has " + array.length + " elements");
+ }
+}
+
+function createInlineImage(options) {
+ return new XmlElement("w:drawing", {}, [
+ new XmlElement("wp:inline", {}, [
+ new XmlElement("wp:docPr", {descr: options.description}),
+ new XmlElement("a:graphic", {}, [
+ new XmlElement("a:graphicData", {}, [
+ new XmlElement("pic:pic", {}, [
+ new XmlElement("pic:blipFill", {}, [
+ new XmlElement("a:blip", {"r:embed": options.relationshipId})
+ ])
+ ])
+ ])
+ ])
+ ])
+ ]);
+}
diff --git a/tests/docx/document-xml-reader.tests.js b/tests/docx/document-xml-reader.tests.js
index dee31b0..81283d1 100644
--- a/tests/docx/document-xml-reader.tests.js
+++ b/tests/docx/document-xml-reader.tests.js
@@ -1,452 +1,19 @@
var assert = require("assert");
-var path = require("path");
+var BodyReader = require("../../lib/docx/body-reader").BodyReader;
var DocumentXmlReader = require("../../lib/docx/document-xml-reader").DocumentXmlReader;
-var documents = require("../../lib/documents");
var XmlElement = require("../../lib/xmlreader").Element;
-var Numbering = require("../../lib/docx/numbering-xml").Numbering;
-var Styles = require("../../lib/docx/styles-reader").Styles;
-var warning = require("../../lib/results").warning;
var testing = require("../testing");
var test = testing.test;
-var createFakeDocxFile = testing.createFakeDocxFile;
-
-function readXmlElement(element, options) {
- var styles = {
- findParagraphStyleById: function(id) {
- return {};
- }
- };
- options = options || {styles: styles};
- return new DocumentXmlReader(options).readXmlElement(element);
-}
-
-function readXmlElementValue(element, options) {
- var result = readXmlElement(element, options);
- assert.deepEqual(result.messages, []);
- return result.value;
-}
function convertXmlToDocumentValue(element, options) {
- options = options || {};
var reader = new DocumentXmlReader(options);
- var result = reader.convertXmlToDocument(element, options);
+ var result = reader.convertXmlToDocument(element);
assert.deepEqual(result.messages, []);
return result.value;
}
-var fakeContentTypes = {
- findContentType: function(filePath) {
- var extensionTypes = {
- ".png": "image/png",
- ".emf": "image/x-emf"
- };
- return extensionTypes[path.extname(filePath)];
- }
-};
-
-describe("readXmlElement: ", function() {
- test("paragraph has no style if it has no properties", function() {
- var paragraphXml = new XmlElement("w:p", {}, []);
- var paragraph = readXmlElementValue(paragraphXml);
- assert.deepEqual(paragraph.styleId, null);
- });
-
- test("paragraph has style ID read from paragraph properties if present", function() {
- var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []);
- var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]);
- var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
-
- var styles = new Styles({"Heading1": {name: "Heading 1"}}, {});
-
- var paragraph = readXmlElementValue(paragraphXml, {styles: styles});
- assert.deepEqual(paragraph.styleId, "Heading1");
- });
-
- test("paragraph has style name read from paragraph properties and styles", function() {
- var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []);
- var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]);
- var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
-
- var styles = new Styles({"Heading1": {name: "Heading 1"}}, {});
-
- var paragraph = readXmlElementValue(paragraphXml, {styles: styles});
- assert.deepEqual(paragraph.styleName, "Heading 1");
- });
-
- test("paragraph has justification read from paragraph properties if present", function() {
- var justificationXml = new XmlElement("w:jc", {"w:val": "center"}, []);
- var propertiesXml = new XmlElement("w:pPr", {}, [justificationXml]);
- var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
- var paragraph = readXmlElementValue(paragraphXml);
- assert.deepEqual(paragraph.alignment, "center");
- });
-
- test("paragraph has numbering properties from paragraph properties if present", function() {
- var numberingPropertiesXml = new XmlElement("w:numPr", {}, [
- new XmlElement("w:ilvl", {"w:val": "1"}),
- new XmlElement("w:numId", {"w:val": "42"})
- ]);
- var propertiesXml = new XmlElement("w:pPr", {}, [numberingPropertiesXml]);
- var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
-
- var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}});
-
- var reader = new DocumentXmlReader({
- numbering: numbering
- });
- var paragraph = reader.readXmlElement(paragraphXml).value;
- assert.deepEqual(paragraph.numbering, {level: "1", isOrdered: true});
- });
-
- test("run has no style if it has no properties", function() {
- var runXml = runWithProperties([]);
- var run = readXmlElementValue(runXml);
- assert.deepEqual(run.styleId, null);
- });
-
- test("run has style ID read from run properties if present", function() {
- var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"});
- var runXml = runWithProperties([runStyleXml]);
-
- var styles = new Styles({}, {"Heading1Char": {name: "Heading 1 Char"}});
-
- var run = readXmlElementValue(runXml, {styles: styles});
- assert.deepEqual(run.styleId, "Heading1Char");
- });
-
- test("run has style name read from run properties and styles", function() {
- var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"});
- var runXml = runWithProperties([runStyleXml]);
-
- var styles = new Styles({}, {"Heading1Char": {name: "Heading 1 Char"}});
-
- var run = readXmlElementValue(runXml, {styles: styles});
- assert.deepEqual(run.styleName, "Heading 1 Char");
- });
-
- test("isBold is false if bold element is not present", function() {
- var runXml = runWithProperties([]);
- var run = readXmlElementValue(runXml);
- assert.deepEqual(run.isBold, false);
- });
-
- test("isBold is true if bold element is present", function() {
- var boldXml = new XmlElement("w:b");
- var runXml = runWithProperties([boldXml]);
- var run = readXmlElementValue(runXml);
- assert.equal(run.isBold, true);
- });
-
- test("isUnderline is false if underline element is not present", function() {
- var runXml = runWithProperties([]);
- var run = readXmlElementValue(runXml);
- assert.deepEqual(run.isUnderline, false);
- });
-
- test("isUnderline is true if underline element is present", function() {
- var underlineXml = new XmlElement("w:u");
- var runXml = runWithProperties([underlineXml]);
- var run = readXmlElementValue(runXml);
- assert.equal(run.isUnderline, true);
- });
-
- test("isStrikethrough is false if strikethrough element is not present", function() {
- var runXml = runWithProperties([]);
- var run = readXmlElementValue(runXml);
- assert.deepEqual(run.isStrikethrough, false);
- });
-
- test("isStrikethrough is true if strikethrough element is present", function() {
- var strikethroughXml = new XmlElement("w:strike");
- var runXml = runWithProperties([strikethroughXml]);
- var run = readXmlElementValue(runXml);
- assert.equal(run.isStrikethrough, true);
- });
-
- test("isItalic is false if bold element is not present", function() {
- var runXml = runWithProperties([]);
- var run = readXmlElementValue(runXml);
- assert.deepEqual(run.isItalic, false);
- });
-
- test("isItalic is true if bold element is present", function() {
- var italicXml = new XmlElement("w:i");
- var runXml = runWithProperties([italicXml]);
- var run = readXmlElementValue(runXml);
- assert.equal(run.isItalic, true);
- });
-
- test("run has baseline vertical alignment by default", function() {
- var runXml = runWithProperties([]);
- var run = readXmlElementValue(runXml);
- assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.baseline);
- });
-
- test("run has vertical alignment read from properties", function() {
- var verticalAlignmentXml = new XmlElement("w:vertAlign", {"w:val": "superscript"});
- var runXml = runWithProperties([verticalAlignmentXml]);
-
- var run = readXmlElementValue(runXml);
- assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.superscript);
- });
-
- test("run properties not included as child of run", function() {
- var runStyleXml = new XmlElement("w:rStyle");
- var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]);
- var runXml = new XmlElement("w:r", {}, [runPropertiesXml]);
- var result = readXmlElement(runXml);
- assert.deepEqual(result.value.children, []);
- });
-
- test("w:tab is read as document tab element", function() {
- var tabXml = new XmlElement("w:tab");
- var result = readXmlElement(tabXml);
- assert.deepEqual(result.value, new documents.Tab());
- });
-
- test("w:table is read as document table element", function() {
- var tableXml = new XmlElement("w:tbl", {}, [
- new XmlElement("w:tr", {}, [
- new XmlElement("w:tc", {}, [
- new XmlElement("w:p", {}, [])
- ])
- ])
- ]);
- var result = readXmlElement(tableXml);
- assert.deepEqual(result.value, new documents.Table([
- new documents.TableRow([
- new documents.TableCell([
- new documents.Paragraph([])
- ])
- ])
- ]));
- });
-
- test("emits warning on unrecognised element", function() {
- var unrecognisedElement = new XmlElement("w:not-an-element");
- var result = readXmlElement(unrecognisedElement);
- assert.deepEqual(
- result.messages,
- [{
- type: "warning",
- message: "An unrecognised element was ignored: w:not-an-element"
- }]
- );
- });
-
- test("w:bookmarkStart is read as a bookmarkStart", function() {
- var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_Peter", "w:id": "42"});
- var result = readXmlElement(bookmarkStart);
- assert.deepEqual(result.value.name, "_Peter");
- assert.deepEqual(result.value.type, "bookmarkStart");
- });
-
- test('_GoBack bookmark is ignored', function() {
- var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_GoBack"});
- var result = readXmlElement(bookmarkStart);
- assert.deepEqual(result.value, []);
- });
-
- test("can read inline pictures", function() {
- var drawing = createInlineImage({
- relationshipId: "rId5",
- description: "It's a hat"
- });
-
- var imageBuffer = new Buffer("Not an image at all!");
- var reader = new DocumentXmlReader({
- relationships: {
- "rId5": {target: "media/hat.png"}
- },
- contentTypes: fakeContentTypes,
- docxFile: createFakeDocxFile({
- "word/media/hat.png": imageBuffer
- })
- });
- var result = reader.readXmlElement(drawing);
- assert.deepEqual(result.messages, []);
- var element = single(result.value);
- assert.equal("image", element.type);
- assert.equal(element.altText, "It's a hat");
- assert.equal(element.contentType, "image/png");
- return element.read()
- .then(function(readValue) {
- assert.equal(readValue, imageBuffer);
- });
- });
-
- test("can read anchored pictures", function() {
- var drawing = new XmlElement("w:drawing", {}, [
- new XmlElement("wp:anchor", {}, [
- new XmlElement("wp:docPr", {descr: "It's a hat"}),
- new XmlElement("a:graphic", {}, [
- new XmlElement("a:graphicData", {}, [
- new XmlElement("pic:pic", {}, [
- new XmlElement("pic:blipFill", {}, [
- new XmlElement("a:blip", {"r:embed": "rId5"})
- ])
- ])
- ])
- ])
- ])
- ]);
-
- var imageBuffer = new Buffer("Not an image at all!");
- var reader = new DocumentXmlReader({
- relationships: {
- "rId5": {target: "media/hat.png"}
- },
- contentTypes: fakeContentTypes,
- docxFile: createFakeDocxFile({
- "word/media/hat.png": imageBuffer
- })
- });
- var result = reader.readXmlElement(drawing);
- assert.deepEqual(result.messages, []);
- var element = single(result.value);
- assert.equal("image", element.type);
- assert.equal(element.altText, "It's a hat");
- return element.read()
- .then(function(readValue) {
- assert.equal(readValue, imageBuffer);
- });
- });
-
- test("warning if unsupported image type", function() {
- var drawing = createInlineImage({
- relationshipId: "rId5",
- description: "It's a hat"
- });
-
- var imageBuffer = new Buffer("Not an image at all!");
- var reader = new DocumentXmlReader({
- relationships: {
- "rId5": {target: "media/hat.emf"}
- },
- contentTypes: fakeContentTypes,
- docxFile: createFakeDocxFile({
- "word/media/hat.emf": imageBuffer
- })
- });
- var result = reader.readXmlElement(drawing);
- assert.deepEqual(result.messages, [warning("Image of type image/x-emf is unlikely to display in web browsers")]);
- var element = single(result.value);
- assert.equal(element.contentType, "image/x-emf");
- });
-
- test("no elements created if image cannot be found in w:drawing", function() {
- var drawing = new XmlElement("w:drawing", {}, []);
-
- var reader = new DocumentXmlReader({});
- var result = reader.readXmlElement(drawing);
- assert.deepEqual(result.messages, []);
- assert.deepEqual(result.value, []);
- });
-
- test("no elements created if image cannot be found in wp:inline", function() {
- var drawing = new XmlElement("wp:inline", {}, []);
-
- var reader = new DocumentXmlReader({});
- var result = reader.readXmlElement(drawing);
- assert.deepEqual(result.messages, []);
- assert.deepEqual(result.value, []);
- });
-
- test("children of w:ins are converted normally", function() {
- var runXml = new XmlElement("w:r", {}, []);
- var insXml = new XmlElement("w:ins", {}, [runXml]);
- var result = readXmlElement(insXml);
- assert.deepEqual(result.value[0].type, "run");
- });
-
- test("children of w:smartTag are converted normally", function() {
- var runXml = new XmlElement("w:r", {}, []);
- var smartTagXml = new XmlElement("w:smartTag", {}, [runXml]);
- var result = readXmlElement(smartTagXml);
- assert.deepEqual(result.value[0].type, "run");
- });
-
- test("w:hyperlink is read as document hyperlink if it has a relationship ID", function() {
- var runXml = new XmlElement("w:r", {}, []);
- var hyperlinkXml = new XmlElement("w:hyperlink", {"r:id": "r42"}, [runXml]);
- var relationships = {
- "r42": {target: "http://example.com"}
- };
- var result = readXmlElement(hyperlinkXml, {relationships: relationships});
- assert.deepEqual(result.value.href, "http://example.com");
- assert.deepEqual(result.value.children[0].type, "run");
- });
-
- test("w:hyperlink is read as document hyperlink if it has an anchor", function() {
- var runXml = new XmlElement("w:r", {}, []);
- var hyperlinkXml = new XmlElement("w:hyperlink", {"w:anchor": "_Peter"}, [runXml]);
- var result = readXmlElement(hyperlinkXml);
- assert.deepEqual(result.value.anchor, "_Peter");
- assert.deepEqual(result.value.children[0].type, "run");
- });
-
- test("w:hyperlink is ignored if it does not have a relationship ID nor anchor", function() {
- var runXml = new XmlElement("w:r", {}, []);
- var hyperlinkXml = new XmlElement("w:hyperlink", {}, [runXml]);
- var result = readXmlElement(hyperlinkXml);
- assert.deepEqual(result.value[0].type, "run");
- });
-
- test("w:br is read as line break", function() {
- var breakXml = new XmlElement("w:br", {}, []);
- var result = readXmlElement(breakXml);
- assert.deepEqual(result.value.type, "lineBreak");
- assert.deepEqual(result.messages, []);
- });
-
- test("w:bookmarkEnd is ignored without warning", function() {
- var ignoredElement = new XmlElement("w:bookmarkEnd");
- var result = readXmlElement(ignoredElement);
- assert.deepEqual(result.messages, []);
- assert.deepEqual([], result.value);
- });
-
- test("warning on breaks that aren't line breaks", function() {
- var breakXml = new XmlElement("w:br", {"w:type": "page"}, []);
- var result = readXmlElement(breakXml);
- assert.deepEqual(result.value, []);
- assert.deepEqual(result.messages, [warning("Unsupported break type: page")]);
- });
-
- test("text boxes have content appended after containing paragraph", function() {
- var textbox = new XmlElement("w:pict", {}, [
- new XmlElement("v:shape", {}, [
- new XmlElement("v:textbox", {}, [
- new XmlElement("w:txbxContent", {}, [
- paragraphWithStyleId("textbox-content")
- ])
- ])
- ])
- ]);
- var paragraph = new XmlElement("w:p", {}, [
- new XmlElement("w:r", {}, [textbox])
- ]);
- var result = readXmlElement(paragraph);
- assert.deepEqual(result.value[1].styleId, "textbox-content");
- });
-
- test("mc:Fallback is used when mc:AlternateContent is read", function() {
- var styles = new Styles({"first": {name: "First"}, "second": {name: "Second"}}, {});
- var textbox = new XmlElement("mc:AlternateContent", {}, [
- new XmlElement("mc:Choice", {"Requires": "wps"}, [
- paragraphWithStyleId("first")
- ]),
- new XmlElement("mc:Fallback", {}, [
- paragraphWithStyleId("second")
- ])
- ]);
- var result = readXmlElement(textbox, {styles: styles});
- assert.deepEqual(result.value[0].styleId, "second");
- });
-});
-
describe("convertXmlToDocument: ", function() {
test("notes of document are read", function() {
@@ -456,50 +23,12 @@ describe("convertXmlToDocument: ", function() {
var bodyXml = new XmlElement("w:body", {}, []);
var documentXml = new XmlElement("w:document", {}, [bodyXml]);
- var document = convertXmlToDocumentValue({root: documentXml}, {footnotes: footnotes});
+ var document = convertXmlToDocumentValue({root: documentXml}, {
+ footnotes: footnotes,
+ bodyReader: new BodyReader({})
+ });
var footnote = document.notes.resolve({noteType: "footnote", noteId: "4"});
assert.deepEqual(footnote.noteId, "4");
assert.deepEqual(footnote.body[0].type, "paragraph");
});
});
-
-function paragraphWithStyleId(styleId) {
- return new XmlElement("w:p", {}, [
- new XmlElement("w:pPr", {}, [
- new XmlElement("w:pStyle", {"w:val": styleId}, [])
- ])
- ]);
-}
-
-function runWithProperties(children) {
- return new XmlElement("w:r", {}, [createRunPropertiesXml(children)]);
-}
-
-function createRunPropertiesXml(children) {
- return new XmlElement("w:rPr", {}, children);
-}
-
-function single(array) {
- if (array.length === 1) {
- return array[0];
- } else {
- throw new Error("Array has " + array.length + " elements");
- }
-}
-
-function createInlineImage(options) {
- return new XmlElement("w:drawing", {}, [
- new XmlElement("wp:inline", {}, [
- new XmlElement("wp:docPr", {descr: options.description}),
- new XmlElement("a:graphic", {}, [
- new XmlElement("a:graphicData", {}, [
- new XmlElement("pic:pic", {}, [
- new XmlElement("pic:blipFill", {}, [
- new XmlElement("a:blip", {"r:embed": options.relationshipId})
- ])
- ])
- ])
- ])
- ])
- ]);
-}