From 45f302766a3a5ca99338fd8e5fef07dec42c0e56 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Tue, 18 Mar 2014 14:15:33 +0000 Subject: Move modules for docx reading into separate directory --- lib/content-types-reader.js | 50 ------ lib/document-xml-reader.js | 195 --------------------- lib/docx-reader.js | 27 --- lib/docx/content-types-reader.js | 50 ++++++ lib/docx/document-xml-reader.js | 195 +++++++++++++++++++++ lib/docx/docx-reader.js | 27 +++ lib/docx/numbering-xml.js | 63 +++++++ lib/docx/office-xml-reader.js | 29 +++ lib/docx/relationships-reader.js | 25 +++ lib/index.js | 2 +- lib/numbering-xml.js | 63 ------- lib/office-xml-reader.js | 30 ---- lib/relationships-reader.js | 25 --- package.json | 2 +- tests/content-types-reader.tests.js | 27 --- tests/document-xml-reader.tests.js | 291 ------------------------------- tests/docx-reader.tests.js | 43 ----- tests/docx/content-types-reader.tests.js | 27 +++ tests/docx/document-xml-reader.tests.js | 291 +++++++++++++++++++++++++++++++ tests/docx/docx-reader.tests.js | 43 +++++ tests/docx/numbering-xml.tests.js | 34 ++++ tests/numbering-xml.tests.js | 34 ---- 22 files changed, 786 insertions(+), 787 deletions(-) delete mode 100644 lib/content-types-reader.js delete mode 100644 lib/document-xml-reader.js delete mode 100644 lib/docx-reader.js create mode 100644 lib/docx/content-types-reader.js create mode 100644 lib/docx/document-xml-reader.js create mode 100644 lib/docx/docx-reader.js create mode 100644 lib/docx/numbering-xml.js create mode 100644 lib/docx/office-xml-reader.js create mode 100644 lib/docx/relationships-reader.js delete mode 100644 lib/numbering-xml.js delete mode 100644 lib/office-xml-reader.js delete mode 100644 lib/relationships-reader.js delete mode 100644 tests/content-types-reader.tests.js delete mode 100644 tests/document-xml-reader.tests.js delete mode 100644 tests/docx-reader.tests.js create mode 100644 tests/docx/content-types-reader.tests.js create mode 100644 tests/docx/document-xml-reader.tests.js create mode 100644 tests/docx/docx-reader.tests.js create mode 100644 tests/docx/numbering-xml.tests.js delete mode 100644 tests/numbering-xml.tests.js diff --git a/lib/content-types-reader.js b/lib/content-types-reader.js deleted file mode 100644 index 0a74eb9..0000000 --- a/lib/content-types-reader.js +++ /dev/null @@ -1,50 +0,0 @@ -var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; - -exports.readContentTypesFromZipFile = readContentTypesFromZipFile; -exports.readContentTypesFromXml = readContentTypesFromXml; - - -function readContentTypesFromZipFile(file) { - return readXmlFromZipFile(file, "[Content_Types].xml") - .then(function(xml) { - return xml ? readContentTypesFromXml(xml) : defaultContentTypes; - }); -} - -function readContentTypesFromXml(xml) { - var extensionDefaults = {}; - var overrides = {}; - - xml.root.children.forEach(function(child) { - if (child.name === "content-types:Default") { - extensionDefaults[child.attributes.Extension] = child.attributes.ContentType; - } - if (child.name === "content-types:Override") { - var name = child.attributes.PartName; - if (name.charAt(0) === "/") { - name = name.substring(1); - } - overrides[name] = child.attributes.ContentType; - } - }); - - return { - findContentType: function(path) { - var overrideContentType = overrides[path]; - if (overrideContentType) { - return overrideContentType; - } else { - var pathParts = path.split("."); - var extension = pathParts[pathParts.length - 1]; - return extensionDefaults[extension]; - } - } - } -} - - -var defaultContentTypes = { - findContentType: function(path) { - return null; - } -}; diff --git a/lib/document-xml-reader.js b/lib/document-xml-reader.js deleted file mode 100644 index 8937374..0000000 --- a/lib/document-xml-reader.js +++ /dev/null @@ -1,195 +0,0 @@ -exports.DocumentXmlReader = DocumentXmlReader; - -var promises = require("./promises"); -var _ = require("underscore"); - -var documents = require("./documents"); -var Result = require("./results").Result; -var warning = require("./results").warning; - - -function DocumentXmlReader(relationships, contentTypes, docxFile, numbering) { - 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); - return Result.combine(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)) { - return new Result( - [], - [warning("An unrecognised element was ignored: " + element.name)] - ); - } - } - return new Result([]); - } - - 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 - ); - }); - }, - "w:pPr": function(element) { - var properties = { - type: "paragraphProperties" - }; - - var styleElement = element.first("w:pStyle"); - if (styleElement) { - properties.styleName = styleElement.attributes["w:val"]; - } - 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 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:tab": function(element) { - return new Result(new documents.Tab()); - }, - "w:hyperlink": function(element) { - var relationshipId = element.attributes["r:id"]; - return readXmlElements(element.children).map(function(children) { - if (relationshipId) { - var href = relationships[relationshipId].target; - return new documents.Hyperlink(children, {href: href}); - } else { - return children; - } - }); - }, - - "w:ins": readChildElements, - "w:smartTag": readChildElements, - "w:drawing": readChildElements, - "wp:inline": readDrawingElement, - "wp:anchor": readDrawingElement - }; - return { - convertXmlToDocument: convertXmlToDocument, - readXmlElement: readXmlElement - }; - - function readDrawingElement(element) { - var blips = element - .getElementsByTagName("a:graphic") - .getElementsByTagName("a:graphicData") - .getElementsByTagName("pic:pic") - .getElementsByTagName("pic:blipFill") - .getElementsByTagName("a:blip"); - - var results = blips.map(function(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 image = documents.Image({ - readImage: readImage, - altText: altText, - contentType: contentTypes.findContentType(imagePath) - }); - return new Result(image); - }); - - return Result.combine(results); - } -} - -var ignoreElements = { - "w:bookmarkStart": true, - "w:bookmarkEnd": true, - "w:sectPr": true, - "w:proofErr": true, - "w:lastRenderedPageBreak": true, - "w:commentRangeStart": true, - "w:commentRangeEnd": true, - "w:commentReference": true, - "w:del": 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); - }; -} - -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; -} diff --git a/lib/docx-reader.js b/lib/docx-reader.js deleted file mode 100644 index e36ab10..0000000 --- a/lib/docx-reader.js +++ /dev/null @@ -1,27 +0,0 @@ -exports.read = read; - -var promises = require("./promises"); -var unzip = require("./unzip"); - -var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; -var documents = require("./documents"); -var DocumentXmlReader = require("./document-xml-reader").DocumentXmlReader; -var readRelationshipsFromZipFile = require("./relationships-reader").readRelationshipsFromZipFile; -var readContentTypesFromZipFile = require("./content-types-reader").readContentTypesFromZipFile; -var readNumberingFromZipFile = require("./numbering-xml").readNumberingFromZipFile; - - -function read(options) { - var docxFile = unzip.openZip(options); - return promises.when(docxFile).then(function(docxFile) { - return promises.all([ - readRelationshipsFromZipFile(docxFile), - readContentTypesFromZipFile(docxFile), - readNumberingFromZipFile(docxFile), - readXmlFromZipFile(docxFile, "word/document.xml") - ]).spread(function(relationships, contentTypes, numbering, documentXml) { - var reader = new DocumentXmlReader(relationships, contentTypes, docxFile, numbering); - return reader.convertXmlToDocument(documentXml); - }); - }); -} diff --git a/lib/docx/content-types-reader.js b/lib/docx/content-types-reader.js new file mode 100644 index 0000000..0a74eb9 --- /dev/null +++ b/lib/docx/content-types-reader.js @@ -0,0 +1,50 @@ +var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; + +exports.readContentTypesFromZipFile = readContentTypesFromZipFile; +exports.readContentTypesFromXml = readContentTypesFromXml; + + +function readContentTypesFromZipFile(file) { + return readXmlFromZipFile(file, "[Content_Types].xml") + .then(function(xml) { + return xml ? readContentTypesFromXml(xml) : defaultContentTypes; + }); +} + +function readContentTypesFromXml(xml) { + var extensionDefaults = {}; + var overrides = {}; + + xml.root.children.forEach(function(child) { + if (child.name === "content-types:Default") { + extensionDefaults[child.attributes.Extension] = child.attributes.ContentType; + } + if (child.name === "content-types:Override") { + var name = child.attributes.PartName; + if (name.charAt(0) === "/") { + name = name.substring(1); + } + overrides[name] = child.attributes.ContentType; + } + }); + + return { + findContentType: function(path) { + var overrideContentType = overrides[path]; + if (overrideContentType) { + return overrideContentType; + } else { + var pathParts = path.split("."); + var extension = pathParts[pathParts.length - 1]; + return extensionDefaults[extension]; + } + } + } +} + + +var defaultContentTypes = { + findContentType: function(path) { + return null; + } +}; diff --git a/lib/docx/document-xml-reader.js b/lib/docx/document-xml-reader.js new file mode 100644 index 0000000..1825006 --- /dev/null +++ b/lib/docx/document-xml-reader.js @@ -0,0 +1,195 @@ +exports.DocumentXmlReader = DocumentXmlReader; + +var _ = require("underscore"); + +var promises = require("../promises"); +var documents = require("../documents"); +var Result = require("../results").Result; +var warning = require("../results").warning; + + +function DocumentXmlReader(relationships, contentTypes, docxFile, numbering) { + 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); + return Result.combine(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)) { + return new Result( + [], + [warning("An unrecognised element was ignored: " + element.name)] + ); + } + } + return new Result([]); + } + + 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 + ); + }); + }, + "w:pPr": function(element) { + var properties = { + type: "paragraphProperties" + }; + + var styleElement = element.first("w:pStyle"); + if (styleElement) { + properties.styleName = styleElement.attributes["w:val"]; + } + 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 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:tab": function(element) { + return new Result(new documents.Tab()); + }, + "w:hyperlink": function(element) { + var relationshipId = element.attributes["r:id"]; + return readXmlElements(element.children).map(function(children) { + if (relationshipId) { + var href = relationships[relationshipId].target; + return new documents.Hyperlink(children, {href: href}); + } else { + return children; + } + }); + }, + + "w:ins": readChildElements, + "w:smartTag": readChildElements, + "w:drawing": readChildElements, + "wp:inline": readDrawingElement, + "wp:anchor": readDrawingElement + }; + return { + convertXmlToDocument: convertXmlToDocument, + readXmlElement: readXmlElement + }; + + function readDrawingElement(element) { + var blips = element + .getElementsByTagName("a:graphic") + .getElementsByTagName("a:graphicData") + .getElementsByTagName("pic:pic") + .getElementsByTagName("pic:blipFill") + .getElementsByTagName("a:blip"); + + var results = blips.map(function(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 image = documents.Image({ + readImage: readImage, + altText: altText, + contentType: contentTypes.findContentType(imagePath) + }); + return new Result(image); + }); + + return Result.combine(results); + } +} + +var ignoreElements = { + "w:bookmarkStart": true, + "w:bookmarkEnd": true, + "w:sectPr": true, + "w:proofErr": true, + "w:lastRenderedPageBreak": true, + "w:commentRangeStart": true, + "w:commentRangeEnd": true, + "w:commentReference": true, + "w:del": 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); + }; +} + +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; +} diff --git a/lib/docx/docx-reader.js b/lib/docx/docx-reader.js new file mode 100644 index 0000000..787c0e0 --- /dev/null +++ b/lib/docx/docx-reader.js @@ -0,0 +1,27 @@ +exports.read = read; + +var promises = require("../promises"); +var unzip = require("../unzip"); + +var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; +var documents = require("../documents"); +var DocumentXmlReader = require("./document-xml-reader").DocumentXmlReader; +var readRelationshipsFromZipFile = require("./relationships-reader").readRelationshipsFromZipFile; +var readContentTypesFromZipFile = require("./content-types-reader").readContentTypesFromZipFile; +var readNumberingFromZipFile = require("./numbering-xml").readNumberingFromZipFile; + + +function read(options) { + var docxFile = unzip.openZip(options); + return promises.when(docxFile).then(function(docxFile) { + return promises.all([ + readRelationshipsFromZipFile(docxFile), + readContentTypesFromZipFile(docxFile), + readNumberingFromZipFile(docxFile), + readXmlFromZipFile(docxFile, "word/document.xml") + ]).spread(function(relationships, contentTypes, numbering, documentXml) { + var reader = new DocumentXmlReader(relationships, contentTypes, docxFile, numbering); + return reader.convertXmlToDocument(documentXml); + }); + }); +} diff --git a/lib/docx/numbering-xml.js b/lib/docx/numbering-xml.js new file mode 100644 index 0000000..7e64dc2 --- /dev/null +++ b/lib/docx/numbering-xml.js @@ -0,0 +1,63 @@ +var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; + +exports.readNumberingXml = readNumberingXml; +exports.readNumberingFromZipFile = readNumberingFromZipFile; +exports.Numbering = Numbering; + +function readNumberingFromZipFile(file) { + return readXmlFromZipFile(file, "word/numbering.xml") + .then(function(xml) { + return xml ? readNumberingXml(xml) : new Numbering({}); + }); +} + +function Numbering(nums) { + return { + findLevel: function(numId, level) { + var num = nums[numId]; + if (num) { + return num[level]; + } else { + return null; + } + } + }; +} + +function readNumberingXml(xml) { + var abstractNums = readAbstractNums(xml); + var nums = readNums(xml, abstractNums); + return new Numbering(nums); +} + +function readAbstractNums(xml) { + var abstractNums = {}; + xml.root.getElementsByTagName("w:abstractNum").forEach(function(element) { + var id = element.attributes["w:abstractNumId"]; + abstractNums[id] = readAbstractNum(element); + }); + return abstractNums; +} + +function readAbstractNum(element) { + var levels = {}; + element.getElementsByTagName("w:lvl").forEach(function(levelElement) { + var levelIndex = levelElement.attributes["w:ilvl"]; + var numFmt = levelElement.first("w:numFmt").attributes["w:val"]; + levels[levelIndex] = { + isOrdered: numFmt !== "bullet", + level: levelIndex + }; + }); + return levels; +} + +function readNums(xml, abstractNums) { + var nums = {}; + xml.root.getElementsByTagName("w:num").forEach(function(element) { + var id = element.attributes["w:numId"]; + var abstractNumId = element.first("w:abstractNumId").attributes["w:val"]; + nums[id] = abstractNums[abstractNumId]; + }); + return nums; +} diff --git a/lib/docx/office-xml-reader.js b/lib/docx/office-xml-reader.js new file mode 100644 index 0000000..39b700e --- /dev/null +++ b/lib/docx/office-xml-reader.js @@ -0,0 +1,29 @@ +var promises = require("../promises"); +var xmlreader = require("../xmlreader"); + + +exports.readXmlFromZipFile = readXmlFromZipFile; + +var xmlNamespaceMap = { + "http://schemas.openxmlformats.org/wordprocessingml/2006/main": "w", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships": "r", + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing": "wp", + "http://schemas.openxmlformats.org/drawingml/2006/main": "a", + "http://schemas.openxmlformats.org/drawingml/2006/picture": "pic", + "http://schemas.openxmlformats.org/package/2006/content-types": "content-types" +}; + + +function read(xmlString) { + return xmlreader.read(xmlString, xmlNamespaceMap); +} + + +function readXmlFromZipFile(docxFile, path) { + if (docxFile.exists(path)) { + return docxFile.read(path, "utf-8") + .then(read); + } else { + return promises.resolve(null); + } +} diff --git a/lib/docx/relationships-reader.js b/lib/docx/relationships-reader.js new file mode 100644 index 0000000..fe6c7f1 --- /dev/null +++ b/lib/docx/relationships-reader.js @@ -0,0 +1,25 @@ +var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; + + +exports.readRelationshipsFromZipFile = readRelationshipsFromZipFile; + + +function readRelationshipsFromZipFile(file) { + return readXmlFromZipFile(file, "word/_rels/document.xml.rels") + .then(function(relationshipsXml) { + return relationshipsXml ? readRelationships(relationshipsXml) : {}; + }); +} + + +function readRelationships(relationshipsXml) { + var relationships = {}; + relationshipsXml.root.children.forEach(function(child) { + if (child.name === "{http://schemas.openxmlformats.org/package/2006/relationships}:Relationship") { + relationships[child.attributes.Id] = { + target: child.attributes.Target + }; + } + }); + return relationships; +} diff --git a/lib/index.js b/lib/index.js index d42acd6..6ccfd85 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,4 @@ -var docxReader = require("./docx-reader"); +var docxReader = require("./docx/docx-reader"); var DocumentConverter = require("./document-to-html").DocumentConverter; var styleMapping = require("./style-reader").readStyle; var readOptions = require("./options-reader").readOptions; diff --git a/lib/numbering-xml.js b/lib/numbering-xml.js deleted file mode 100644 index 7e64dc2..0000000 --- a/lib/numbering-xml.js +++ /dev/null @@ -1,63 +0,0 @@ -var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; - -exports.readNumberingXml = readNumberingXml; -exports.readNumberingFromZipFile = readNumberingFromZipFile; -exports.Numbering = Numbering; - -function readNumberingFromZipFile(file) { - return readXmlFromZipFile(file, "word/numbering.xml") - .then(function(xml) { - return xml ? readNumberingXml(xml) : new Numbering({}); - }); -} - -function Numbering(nums) { - return { - findLevel: function(numId, level) { - var num = nums[numId]; - if (num) { - return num[level]; - } else { - return null; - } - } - }; -} - -function readNumberingXml(xml) { - var abstractNums = readAbstractNums(xml); - var nums = readNums(xml, abstractNums); - return new Numbering(nums); -} - -function readAbstractNums(xml) { - var abstractNums = {}; - xml.root.getElementsByTagName("w:abstractNum").forEach(function(element) { - var id = element.attributes["w:abstractNumId"]; - abstractNums[id] = readAbstractNum(element); - }); - return abstractNums; -} - -function readAbstractNum(element) { - var levels = {}; - element.getElementsByTagName("w:lvl").forEach(function(levelElement) { - var levelIndex = levelElement.attributes["w:ilvl"]; - var numFmt = levelElement.first("w:numFmt").attributes["w:val"]; - levels[levelIndex] = { - isOrdered: numFmt !== "bullet", - level: levelIndex - }; - }); - return levels; -} - -function readNums(xml, abstractNums) { - var nums = {}; - xml.root.getElementsByTagName("w:num").forEach(function(element) { - var id = element.attributes["w:numId"]; - var abstractNumId = element.first("w:abstractNumId").attributes["w:val"]; - nums[id] = abstractNums[abstractNumId]; - }); - return nums; -} diff --git a/lib/office-xml-reader.js b/lib/office-xml-reader.js deleted file mode 100644 index fa67c41..0000000 --- a/lib/office-xml-reader.js +++ /dev/null @@ -1,30 +0,0 @@ -var promises = require("./promises"); - -var xmlreader = require("./xmlreader"); - - -exports.readXmlFromZipFile = readXmlFromZipFile; - -var xmlNamespaceMap = { - "http://schemas.openxmlformats.org/wordprocessingml/2006/main": "w", - "http://schemas.openxmlformats.org/officeDocument/2006/relationships": "r", - "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing": "wp", - "http://schemas.openxmlformats.org/drawingml/2006/main": "a", - "http://schemas.openxmlformats.org/drawingml/2006/picture": "pic", - "http://schemas.openxmlformats.org/package/2006/content-types": "content-types" -}; - - -function read(xmlString) { - return xmlreader.read(xmlString, xmlNamespaceMap); -} - - -function readXmlFromZipFile(docxFile, path) { - if (docxFile.exists(path)) { - return docxFile.read(path, "utf-8") - .then(read); - } else { - return promises.resolve(null); - } -} diff --git a/lib/relationships-reader.js b/lib/relationships-reader.js deleted file mode 100644 index fe6c7f1..0000000 --- a/lib/relationships-reader.js +++ /dev/null @@ -1,25 +0,0 @@ -var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; - - -exports.readRelationshipsFromZipFile = readRelationshipsFromZipFile; - - -function readRelationshipsFromZipFile(file) { - return readXmlFromZipFile(file, "word/_rels/document.xml.rels") - .then(function(relationshipsXml) { - return relationshipsXml ? readRelationships(relationshipsXml) : {}; - }); -} - - -function readRelationships(relationshipsXml) { - var relationships = {}; - relationshipsXml.root.children.forEach(function(child) { - if (child.name === "{http://schemas.openxmlformats.org/package/2006/relationships}:Relationship") { - relationships[child.attributes.Id] = { - target: child.attributes.Target - }; - } - }); - return relationships; -} diff --git a/package.json b/package.json index 109c85f..a05c17c 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "./lib/unzip.js": "./browser/unzip.js" }, "scripts": { - "test": "mocha tests" + "test": "mocha tests --recursive" }, "licenses": [ { diff --git a/tests/content-types-reader.tests.js b/tests/content-types-reader.tests.js deleted file mode 100644 index df7ed47..0000000 --- a/tests/content-types-reader.tests.js +++ /dev/null @@ -1,27 +0,0 @@ -var assert = require("assert"); - -var readContentTypesFromXml = require("../lib/content-types-reader").readContentTypesFromXml; -var XmlElement = require("../lib/xmlreader").Element; -var test = require("./testing").test; - - -describe('readContentTypesFromXml', function() { - test('reads default-per-extension from XML', function() { - var contentTypes = readContentTypesFromXml({ - root: new XmlElement("content-types:Types", {}, [ - new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}) - ]) - }); - assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png"); - }); - - test('reads overrides in preference to defaults', function() { - var contentTypes = readContentTypesFromXml({ - root: new XmlElement("content-types:Types", {}, [ - new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}), - new XmlElement("content-types:Override", {PartName: "/word/media/hat.png", ContentType: "image/hat"}) - ]) - }); - assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/hat"); - }); -}); diff --git a/tests/document-xml-reader.tests.js b/tests/document-xml-reader.tests.js deleted file mode 100644 index 14a9cbe..0000000 --- a/tests/document-xml-reader.tests.js +++ /dev/null @@ -1,291 +0,0 @@ -var assert = require("assert"); - -var DocumentXmlReader = require("../lib/document-xml-reader").DocumentXmlReader; -var documents = require("../lib/documents"); -var XmlElement = require("../lib/xmlreader").Element; -var Numbering = require("../lib/numbering-xml").Numbering; - -var testing = require("./testing"); -var test = testing.test; -var createFakeDocxFile = testing.createFakeDocxFile; - -function readXmlElement(element, options) { - options = options || {} - return new DocumentXmlReader(options.relationships || {}).readXmlElement(element); -} - -function readXmlElementValue(element) { - var result = readXmlElement(element); - assert.deepEqual(result.messages, []); - return result.value; -} - -var fakeContentTypes = { - findContentType: function(path) { - return ""; - } -}; - -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.styleName, null); - }); - - test("paragraph has style name 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 paragraph = readXmlElementValue(paragraphXml); - assert.deepEqual(paragraph.styleName, "Heading1"); - }); - - 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( - {}, - null, - null, - 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.styleName, null); - }); - - test("run has style name read from run properties if present", function() { - var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Emphasis"}); - var runXml = runWithProperties([runStyleXml]); - var run = readXmlElementValue(runXml); - assert.deepEqual(run.styleName, "Emphasis"); - }); - - 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("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 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("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("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.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( - { - "rId5": {target: "media/hat.png"} - }, - fakeContentTypes, - 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, ""); - 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( - { - "rId5": {target: "media/hat.png"} - }, - fakeContentTypes, - 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("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 ignored if it does not have a relationship ID", 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"); - }); -}); - -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-reader.tests.js b/tests/docx-reader.tests.js deleted file mode 100644 index 4fc487f..0000000 --- a/tests/docx-reader.tests.js +++ /dev/null @@ -1,43 +0,0 @@ -var assert = require("assert"); - -var docxReader = require("../lib/docx-reader"); -var documents = require("../lib/documents"); -var XmlElement = require("../lib/xmlreader").Element; - -var testing = require("./testing"); -var test = testing.test; -var testData = testing.testData; -var createFakeDocxFile = testing.createFakeDocxFile; - - -describe("docx-reader", function() { - test("can read document with single paragraph with single run of text", function() { - var expectedDocument = documents.Document([ - documents.Paragraph([ - documents.Run([ - documents.Text("Hello.") - ]) - ]) - ]); - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("simple/word/document.xml") - }); - return docxReader.read({file: docxFile}).then(function(result) { - assert.deepEqual(expectedDocument, result.document); - }); - }); - - test("hyperlink hrefs are read from relationships file", function() { - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("hyperlinks/word/document.xml"), - "word/_rels/document.xml.rels": testData("hyperlinks/word/_rels/document.xml.rels") - }); - return docxReader.read({file: docxFile}).then(function(result) { - var paragraph = result.document.children[0]; - assert.equal(1, paragraph.children.length); - var hyperlink = paragraph.children[0]; - assert.equal(hyperlink.href, "http://www.example.com"); - assert.equal(hyperlink.children.length, 1); - }); - }); -}); diff --git a/tests/docx/content-types-reader.tests.js b/tests/docx/content-types-reader.tests.js new file mode 100644 index 0000000..b0f135e --- /dev/null +++ b/tests/docx/content-types-reader.tests.js @@ -0,0 +1,27 @@ +var assert = require("assert"); + +var readContentTypesFromXml = require("../../lib/docx/content-types-reader").readContentTypesFromXml; +var XmlElement = require("../../lib/xmlreader").Element; +var test = require("../testing").test; + + +describe('readContentTypesFromXml', function() { + test('reads default-per-extension from XML', function() { + var contentTypes = readContentTypesFromXml({ + root: new XmlElement("content-types:Types", {}, [ + new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}) + ]) + }); + assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png"); + }); + + test('reads overrides in preference to defaults', function() { + var contentTypes = readContentTypesFromXml({ + root: new XmlElement("content-types:Types", {}, [ + new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}), + new XmlElement("content-types:Override", {PartName: "/word/media/hat.png", ContentType: "image/hat"}) + ]) + }); + assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/hat"); + }); +}); diff --git a/tests/docx/document-xml-reader.tests.js b/tests/docx/document-xml-reader.tests.js new file mode 100644 index 0000000..7cdbefc --- /dev/null +++ b/tests/docx/document-xml-reader.tests.js @@ -0,0 +1,291 @@ +var assert = require("assert"); + +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 testing = require("../testing"); +var test = testing.test; +var createFakeDocxFile = testing.createFakeDocxFile; + +function readXmlElement(element, options) { + options = options || {} + return new DocumentXmlReader(options.relationships || {}).readXmlElement(element); +} + +function readXmlElementValue(element) { + var result = readXmlElement(element); + assert.deepEqual(result.messages, []); + return result.value; +} + +var fakeContentTypes = { + findContentType: function(path) { + return ""; + } +}; + +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.styleName, null); + }); + + test("paragraph has style name 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 paragraph = readXmlElementValue(paragraphXml); + assert.deepEqual(paragraph.styleName, "Heading1"); + }); + + 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( + {}, + null, + null, + 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.styleName, null); + }); + + test("run has style name read from run properties if present", function() { + var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Emphasis"}); + var runXml = runWithProperties([runStyleXml]); + var run = readXmlElementValue(runXml); + assert.deepEqual(run.styleName, "Emphasis"); + }); + + 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("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 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("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("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.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( + { + "rId5": {target: "media/hat.png"} + }, + fakeContentTypes, + 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, ""); + 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( + { + "rId5": {target: "media/hat.png"} + }, + fakeContentTypes, + 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("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 ignored if it does not have a relationship ID", 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"); + }); +}); + +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/docx-reader.tests.js b/tests/docx/docx-reader.tests.js new file mode 100644 index 0000000..9dc1921 --- /dev/null +++ b/tests/docx/docx-reader.tests.js @@ -0,0 +1,43 @@ +var assert = require("assert"); + +var docxReader = require("../../lib/docx/docx-reader"); +var documents = require("../../lib/documents"); +var XmlElement = require("../../lib/xmlreader").Element; + +var testing = require("../testing"); +var test = testing.test; +var testData = testing.testData; +var createFakeDocxFile = testing.createFakeDocxFile; + + +describe("docx-reader", function() { + test("can read document with single paragraph with single run of text", function() { + var expectedDocument = documents.Document([ + documents.Paragraph([ + documents.Run([ + documents.Text("Hello.") + ]) + ]) + ]); + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("simple/word/document.xml") + }); + return docxReader.read({file: docxFile}).then(function(result) { + assert.deepEqual(expectedDocument, result.document); + }); + }); + + test("hyperlink hrefs are read from relationships file", function() { + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("hyperlinks/word/document.xml"), + "word/_rels/document.xml.rels": testData("hyperlinks/word/_rels/document.xml.rels") + }); + return docxReader.read({file: docxFile}).then(function(result) { + var paragraph = result.document.children[0]; + assert.equal(1, paragraph.children.length); + var hyperlink = paragraph.children[0]; + assert.equal(hyperlink.href, "http://www.example.com"); + assert.equal(hyperlink.children.length, 1); + }); + }); +}); diff --git a/tests/docx/numbering-xml.tests.js b/tests/docx/numbering-xml.tests.js new file mode 100644 index 0000000..a101806 --- /dev/null +++ b/tests/docx/numbering-xml.tests.js @@ -0,0 +1,34 @@ +var assert = require("assert"); + +var duck = require("duck"); + +var readNumberingXml = require("../../lib/docx/numbering-xml").readNumberingXml; +var XmlElement = require("../../lib/xmlreader").Element; +var test = require("../testing").test; + + +describe('readNumberingXml', function() { + test('w:num element inherits levels from w:abstractNum', function() { + var numbering = readNumberingXml({ + root: new XmlElement("w:numbering", {}, [ + new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [ + new XmlElement("w:lvl", {"w:ilvl": "0"}, [ + new XmlElement("w:numFmt", {"w:val": "bullet"}) + ]), + new XmlElement("w:lvl", {"w:ilvl": "1"}, [ + new XmlElement("w:numFmt", {"w:val": "decimal"}) + ]) + ]), + new XmlElement("w:num", {"w:numId": "47"}, [ + new XmlElement("w:abstractNumId", {"w:val": "42"}) + ]) + ]) + }); + duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({ + isOrdered: false + })); + duck.assertThat(numbering.findLevel("47", "1"), duck.hasProperties({ + isOrdered: true + })); + }); +}); diff --git a/tests/numbering-xml.tests.js b/tests/numbering-xml.tests.js deleted file mode 100644 index 3312dfe..0000000 --- a/tests/numbering-xml.tests.js +++ /dev/null @@ -1,34 +0,0 @@ -var assert = require("assert"); - -var duck = require("duck"); - -var readNumberingXml = require("../lib/numbering-xml").readNumberingXml; -var XmlElement = require("../lib/xmlreader").Element; -var test = require("./testing").test; - - -describe('readNumberingXml', function() { - test('w:num element inherits levels from w:abstractNum', function() { - var numbering = readNumberingXml({ - root: new XmlElement("w:numbering", {}, [ - new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [ - new XmlElement("w:lvl", {"w:ilvl": "0"}, [ - new XmlElement("w:numFmt", {"w:val": "bullet"}) - ]), - new XmlElement("w:lvl", {"w:ilvl": "1"}, [ - new XmlElement("w:numFmt", {"w:val": "decimal"}) - ]) - ]), - new XmlElement("w:num", {"w:numId": "47"}, [ - new XmlElement("w:abstractNumId", {"w:val": "42"}) - ]) - ]) - }); - duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({ - isOrdered: false - })); - duck.assertThat(numbering.findLevel("47", "1"), duck.hasProperties({ - isOrdered: true - })); - }); -}); -- cgit v1.2.3