diff options
| author | Michael Williamson <mike@zwobble.org> | 2013-04-08 20:16:05 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2013-04-08 20:16:05 +0100 |
| commit | 2f72e903f5f8bb7ac00c69778be4cb199017617d (patch) | |
| tree | 0e24cac9cd0f6f3bddc72d6c32f4a2d9192f53dc | |
| parent | 2ea1d69fe5821a1e4f28e7c411e5bf41a498b2fa (diff) | |
Move DocumentReader to DocumentXmlReader in its own module
| -rw-r--r-- | lib/document-xml-reader.js | 158 | ||||
| -rw-r--r-- | lib/docx-reader.js | 176 | ||||
| -rw-r--r-- | lib/results.js | 26 | ||||
| -rw-r--r-- | tests/document-xml-reader.tests.js | 122 | ||||
| -rw-r--r-- | tests/docx-reader.tests.js | 113 |
5 files changed, 309 insertions, 286 deletions
diff --git a/lib/document-xml-reader.js b/lib/document-xml-reader.js new file mode 100644 index 0000000..ea15204 --- /dev/null +++ b/lib/document-xml-reader.js @@ -0,0 +1,158 @@ +exports.DocumentXmlReader = DocumentXmlReader; + +var q = require("q"); +var _ = require("underscore"); +var path = require("path"); + +var documents = require("./documents"); +var Result = require("./results").Result; +var warning = require("./results").warning; + + +function DocumentXmlReader(relationships, docxFile) { + function convertXmlToDocument(documentXml) { + var body = documentXml.root.first("w:body"); + + var result = readXmlElements(body.children) + .map(function(children) { + return new documents.Document(children); + }); + result.document = result.value; + return result; + } + + function readXmlElements(elements) { + var results = elements.map(readXmlElement); + var values = _.pluck(results, "value").filter(notNull); + var messages = _.flatten(_.pluck(results, "messages"), true); + return new Result(values, messages); + } + + 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)) { + return new Result( + null, + [warning("An unrecognised element was ignored: " + element.name)] + ); + } + } + return new Result(null); + } + + 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 + ); + }); + }, + "w:pPr": function(element) { + var properties = { + type: "paragraphProperties" + }; + + var styleElement = element.first("w:pStyle"); + if (styleElement) { + properties.styleName = styleElement.attributes["w:val"]; + } + + return new Result(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": function(element) { + var properties = { + type: "runProperties" + }; + + var styleElement = element.first("w:rStyle"); + if (styleElement) { + properties.styleName = styleElement.attributes["w:val"]; + } + properties.isBold = !!element.first("w:b"); + properties.isItalic = !!element.first("w:i"); + + return new Result(properties); + }, + "w:t": function(element) { + return new Result(new documents.Text(element.children[0].value)); + }, + "w:hyperlink": function(element) { + var relationshipId = element.attributes["r:id"]; + var href = relationships[relationshipId].target; + return readXmlElements(element.children) + .map(function(children) { + return new documents.Hyperlink(children, {href: href}); + }); + }, + + "w:drawing": function(element) { + return readXmlElement(element.first("wp:inline")); + }, + "wp:inline": function(element) { + var relationshipId = element + .first("a:graphic") + .first("a:graphicData") + .first("pic:pic") + .first("pic:blipFill") + .first("a:blip") + .attributes["r:embed"]; + var imagePath = relationships[relationshipId].target; + var readImage = function() { + return docxFile.read(path.join("word", imagePath)); + }; + var altText = element.first("wp:docPr").attributes.descr; + + var image = documents.Image(readImage, altText) + return new Result(image); + } + }; + return { + convertXmlToDocument: convertXmlToDocument, + readXmlElement: readXmlElement + }; +} + +var ignoreElements = { + "w:bookmarkStart": true, + "w:bookmarkEnd": true, + "w:sectPr": true, + "w:proofErr": true, + "w:lastRenderedPageBreak": true +}; + +function notNull(value) { + return value !== null; +} + +function isParagraphProperties(element) { + return element.type === "paragraphProperties"; +} + +function isRunProperties(element) { + return element.type === "runProperties"; +} + +function negate(predicate) { + return function(value) { + return !predicate(value); + }; +} diff --git a/lib/docx-reader.js b/lib/docx-reader.js index 23a3e67..ecaa10c 100644 --- a/lib/docx-reader.js +++ b/lib/docx-reader.js @@ -1,5 +1,4 @@ exports.read = read; -exports.DocumentReader = DocumentReader; var q = require("q"); var _ = require("underscore"); @@ -8,6 +7,8 @@ var path = require("path"); var xmlreader = require("./xmlreader"); var documents = require("./documents"); +var DocumentXmlReader = require("./document-xml-reader").DocumentXmlReader; +var Result = require("./results").Result; function read(docxFile) { @@ -40,7 +41,7 @@ function read(docxFile) { readXmlFile(docxFile, "word/document.xml") ]).spread(function(relationshipsXml, documentXml) { var relationships = relationshipsXml ? readRelationships(relationshipsXml) : {}; - var reader = new DocumentReader(relationships, docxFile); + var reader = new DocumentXmlReader(relationships, docxFile); return reader.convertXmlToDocument(documentXml); }); } @@ -66,177 +67,6 @@ function readRelationships(relationshipsXml) { return relationships; } -function DocumentReader(relationships, docxFile) { - function convertXmlToDocument(documentXml) { - var body = documentXml.root.first("w:body"); - - var result = readXmlElements(body.children) - .map(function(children) { - return new documents.Document(children); - }); - result.document = result.value; - return result; - } - - function readXmlElements(elements) { - var results = elements.map(readXmlElement); - var values = _.pluck(results, "value").filter(notNull); - var messages = _.flatten(_.pluck(results, "messages"), true); - return new Result(values, messages); - } - - 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)) { - return new Result( - null, - [warning("An unrecognised element was ignored: " + element.name)] - ); - } - } - return new Result(null); - } - - 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 - ); - }); - }, - "w:pPr": function(element) { - var properties = { - type: "paragraphProperties" - }; - - var styleElement = element.first("w:pStyle"); - if (styleElement) { - properties.styleName = styleElement.attributes["w:val"]; - } - - return new Result(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": function(element) { - var properties = { - type: "runProperties" - }; - - var styleElement = element.first("w:rStyle"); - if (styleElement) { - properties.styleName = styleElement.attributes["w:val"]; - } - properties.isBold = !!element.first("w:b"); - properties.isItalic = !!element.first("w:i"); - - return new Result(properties); - }, - "w:t": function(element) { - return new Result(new documents.Text(element.children[0].value)); - }, - "w:hyperlink": function(element) { - var relationshipId = element.attributes["r:id"]; - var href = relationships[relationshipId].target; - return readXmlElements(element.children) - .map(function(children) { - return new documents.Hyperlink(children, {href: href}); - }); - }, - - "w:drawing": function(element) { - return readXmlElement(element.first("wp:inline")); - }, - "wp:inline": function(element) { - var relationshipId = element - .first("a:graphic") - .first("a:graphicData") - .first("pic:pic") - .first("pic:blipFill") - .first("a:blip") - .attributes["r:embed"]; - var imagePath = relationships[relationshipId].target; - var readImage = function() { - return docxFile.read(path.join("word", imagePath)); - }; - var altText = element.first("wp:docPr").attributes.descr; - - var image = documents.Image(readImage, altText) - return new Result(image); - } - }; - return { - convertXmlToDocument: convertXmlToDocument, - readXmlElement: readXmlElement - }; -} - -var ignoreElements = { - "w:bookmarkStart": true, - "w:bookmarkEnd": true, - "w:sectPr": true, - "w:proofErr": true, - "w:lastRenderedPageBreak": true -}; - function parseXml(string) { return xmlreader.read(string); } - -function notNull(value) { - return value !== null; -} - -function isParagraphProperties(element) { - return element.type === "paragraphProperties"; -} - -function isRunProperties(element) { - return element.type === "runProperties"; -} - -function negate(predicate) { - return function(value) { - return !predicate(value); - }; -} - -function Result(value, messages) { - this.value = value; - this.messages = messages || []; -} - -Result.prototype.map = function(func) { - return new Result(func(this.value), this.messages); -}; - -Result.prototype.mapThen = function(func) { - var messages = this.messages; - return func(this.value).then(function(resultValue) { - return new Result(resultValue, messages); - }); -}; - -function warning(message) { - return { - type: "warning", - message: message - }; -} diff --git a/lib/results.js b/lib/results.js new file mode 100644 index 0000000..5bd81c0 --- /dev/null +++ b/lib/results.js @@ -0,0 +1,26 @@ +exports.Result = Result; +exports.warning = warning; + + +function Result(value, messages) { + this.value = value; + this.messages = messages || []; +} + +Result.prototype.map = function(func) { + return new Result(func(this.value), this.messages); +}; + +Result.prototype.mapThen = function(func) { + var messages = this.messages; + return func(this.value).then(function(resultValue) { + return new Result(resultValue, messages); + }); +}; + +function warning(message) { + return { + type: "warning", + message: message + }; +} diff --git a/tests/document-xml-reader.tests.js b/tests/document-xml-reader.tests.js new file mode 100644 index 0000000..2f406f8 --- /dev/null +++ b/tests/document-xml-reader.tests.js @@ -0,0 +1,122 @@ +var assert = require("assert"); + +var DocumentXmlReader = require("../lib/document-xml-reader").DocumentXmlReader; +var documents = require("../lib/documents"); +var XmlElement = require("../lib/xmlreader").Element; + +var testing = require("./testing"); +var test = testing.test; +var createFakeDocxFile = testing.createFakeDocxFile; + +function readXmlElement(element) { + return new DocumentXmlReader({}).readXmlElement(element); +} + +describe("readElement", function() { + test("reads styles from run properties", function() { + var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Emphasis"}); + var runPropertiesXml = createRunPropertiesXml([runStyleXml]); + var result = readXmlElement(runPropertiesXml); + assert.equal(result.value.styleName, "Emphasis"); + }); + + test("isBold is false if bold element is not present", function() { + var runPropertiesXml = createRunPropertiesXml([]); + var result = readXmlElement(runPropertiesXml); + assert.equal(result.value.isBold, false); + }); + + test("isBold is true if bold element is present", function() { + var boldXml = new XmlElement("w:b"); + var runPropertiesXml = createRunPropertiesXml([boldXml]); + var result = readXmlElement(runPropertiesXml); + assert.equal(result.value.isBold, true); + }); + + test("isItalic is false if bold element is not present", function() { + var runPropertiesXml = createRunPropertiesXml([]); + var result = readXmlElement(runPropertiesXml); + assert.equal(result.value.isItalic, false); + }); + + test("isItalic is true if bold element is present", function() { + var italicXml = new XmlElement("w:i"); + var runPropertiesXml = createRunPropertiesXml([italicXml]); + var result = readXmlElement(runPropertiesXml); + assert.equal(result.value.isItalic, true); + }); + + test("run properties are attached to run", function() { + var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Emphasis"}); + var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]); + var runXml = new XmlElement("w:r", {}, [runPropertiesXml]); + var result = readXmlElement(runXml); + assert.equal(result.value.properties.styleName, "Emphasis"); + }); + + test("run properties not included as child of run", function() { + var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Emphasis"}); + var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]); + var runXml = new XmlElement("w:r", {}, [runPropertiesXml]); + var result = readXmlElement(runXml); + assert.deepEqual(result.value.children, []); + }); + + 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 ignored without warning", function() { + var ignoredElement = new XmlElement("w:bookmarkStart"); + var result = readXmlElement(ignoredElement); + assert.deepEqual(result.messages, []); + assert.equal(null, result.value); + }); + + test("can read inline pictures", function() { + var drawing = new XmlElement("w:drawing", {}, [ + new XmlElement("wp:inline", {}, [ + 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( + { + "rId5": {target: "media/hat.png"} + }, + createFakeDocxFile({ + "word/media/hat.png": imageBuffer + }) + ); + var result = reader.readXmlElement(drawing); + assert.deepEqual(result.messages, []); + assert.equal("image", result.value.type); + assert.equal(result.value.altText, "It's a hat"); + return result.value.read() + .then(function(readValue) { + assert.equal(readValue, imageBuffer) + }); + }); +}); + +function createRunPropertiesXml(children) { + return new XmlElement("w:rPr", {}, children); +} diff --git a/tests/docx-reader.tests.js b/tests/docx-reader.tests.js index 7211f1a..0436d87 100644 --- a/tests/docx-reader.tests.js +++ b/tests/docx-reader.tests.js @@ -51,116 +51,3 @@ describe("docx-reader", function() { }); }); }); - -function readXmlElement(element) { - return new docxReader.DocumentReader({}).readXmlElement(element); -} - -describe("readElement", function() { - test("reads styles from run properties", function() { - var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Emphasis"}); - var runPropertiesXml = createRunPropertiesXml([runStyleXml]); - var result = readXmlElement(runPropertiesXml); - assert.equal(result.value.styleName, "Emphasis"); - }); - - test("isBold is false if bold element is not present", function() { - var runPropertiesXml = createRunPropertiesXml([]); - var result = readXmlElement(runPropertiesXml); - assert.equal(result.value.isBold, false); - }); - - test("isBold is true if bold element is present", function() { - var boldXml = new XmlElement("w:b"); - var runPropertiesXml = createRunPropertiesXml([boldXml]); - var result = readXmlElement(runPropertiesXml); - assert.equal(result.value.isBold, true); - }); - - test("isItalic is false if bold element is not present", function() { - var runPropertiesXml = createRunPropertiesXml([]); - var result = readXmlElement(runPropertiesXml); - assert.equal(result.value.isItalic, false); - }); - - test("isItalic is true if bold element is present", function() { - var italicXml = new XmlElement("w:i"); - var runPropertiesXml = createRunPropertiesXml([italicXml]); - var result = readXmlElement(runPropertiesXml); - assert.equal(result.value.isItalic, true); - }); - - test("run properties are attached to run", function() { - var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Emphasis"}); - var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]); - var runXml = new XmlElement("w:r", {}, [runPropertiesXml]); - var result = readXmlElement(runXml); - assert.equal(result.value.properties.styleName, "Emphasis"); - }); - - test("run properties not included as child of run", function() { - var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Emphasis"}); - var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]); - var runXml = new XmlElement("w:r", {}, [runPropertiesXml]); - var result = readXmlElement(runXml); - assert.deepEqual(result.value.children, []); - }); - - 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 ignored without warning", function() { - var ignoredElement = new XmlElement("w:bookmarkStart"); - var result = readXmlElement(ignoredElement); - assert.deepEqual(result.messages, []); - assert.equal(null, result.value); - }); - - test("can read inline pictures", function() { - var drawing = new XmlElement("w:drawing", {}, [ - new XmlElement("wp:inline", {}, [ - 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 docxReader.DocumentReader( - { - "rId5": {target: "media/hat.png"} - }, - createFakeDocxFile({ - "word/media/hat.png": imageBuffer - }) - ); - var result = reader.readXmlElement(drawing); - assert.deepEqual(result.messages, []); - assert.equal("image", result.value.type); - assert.equal(result.value.altText, "It's a hat"); - return result.value.read() - .then(function(readValue) { - assert.equal(readValue, imageBuffer) - }); - }); -}); - -function createRunPropertiesXml(children) { - return new XmlElement("w:rPr", {}, children); -} |
