diff options
| author | Michael Williamson <mike@zwobble.org> | 2015-07-03 21:31:00 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2015-07-03 21:31:00 +0100 |
| commit | 792aa9fb8cab1b68c585f09336758e2c76e3e8d5 (patch) | |
| tree | e492c0a1165fc08a70198d8927346a8ec6bbe15d | |
| parent | 419eb705048dae39807ffeac7bce8d52563d7013 (diff) | |
Extract reading of document body to separate module
| -rw-r--r-- | lib/docx/body-reader.js | 342 | ||||
| -rw-r--r-- | lib/docx/document-xml-reader.js | 338 | ||||
| -rw-r--r-- | lib/docx/docx-reader.js | 22 | ||||
| -rw-r--r-- | tests/docx/body-reader.tests.js | 481 | ||||
| -rw-r--r-- | tests/docx/document-xml-reader.tests.js | 483 |
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}) - ]) - ]) - ]) - ]) - ]) - ]); -} |
