aboutsummaryrefslogtreecommitdiff
path: root/lib/docx/document-xml-reader.js
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2014-03-18 14:15:33 +0000
committerMichael Williamson <mike@zwobble.org>2014-03-18 14:15:33 +0000
commit45f302766a3a5ca99338fd8e5fef07dec42c0e56 (patch)
treee5081b43310e6c3698c08ab021e0d61d0a84d09c /lib/docx/document-xml-reader.js
parentdca50ccd84f91a7db2a41a59e15b3ed95b2c3f46 (diff)
Move modules for docx reading into separate directory
Diffstat (limited to 'lib/docx/document-xml-reader.js')
-rw-r--r--lib/docx/document-xml-reader.js195
1 files changed, 195 insertions, 0 deletions
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;
+}