From 9ff57bb770e0296bb2f5b7a41711c53954eb8c4e Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 16 Aug 2015 14:21:25 +0100 Subject: Split xmlreader into nodes and reader modules --- lib/docx/office-xml-reader.js | 4 +- lib/xml/index.js | 2 + lib/xml/nodes.js | 60 +++++++++++++ lib/xml/reader.js | 87 +++++++++++++++++++ lib/xmlreader.js | 141 ------------------------------- tests/document-to-html.tests.js | 4 +- tests/docx/body-reader.tests.js | 2 +- tests/docx/content-types-reader.tests.js | 2 +- tests/docx/notes-reader.tests.js | 2 +- tests/docx/numbering-xml.tests.js | 2 +- tests/docx/styles-reader.tests.js | 2 +- tests/xml/reader.tests.js | 88 +++++++++++++++++++ tests/xmlreader.tests.js | 88 ------------------- 13 files changed, 246 insertions(+), 238 deletions(-) create mode 100644 lib/xml/index.js create mode 100644 lib/xml/nodes.js create mode 100644 lib/xml/reader.js delete mode 100644 lib/xmlreader.js create mode 100644 tests/xml/reader.tests.js delete mode 100644 tests/xmlreader.tests.js diff --git a/lib/docx/office-xml-reader.js b/lib/docx/office-xml-reader.js index 71f5b57..b5231eb 100644 --- a/lib/docx/office-xml-reader.js +++ b/lib/docx/office-xml-reader.js @@ -1,5 +1,5 @@ var promises = require("../promises"); -var xmlreader = require("../xmlreader"); +var xml = require("../xml"); exports.readXmlFromZipFile = readXmlFromZipFile; @@ -17,7 +17,7 @@ var xmlNamespaceMap = { function read(xmlString) { - return xmlreader.read(xmlString, xmlNamespaceMap); + return xml.readString(xmlString, xmlNamespaceMap); } diff --git a/lib/xml/index.js b/lib/xml/index.js new file mode 100644 index 0000000..6c35998 --- /dev/null +++ b/lib/xml/index.js @@ -0,0 +1,2 @@ +exports.Element = require("./nodes").Element; +exports.readString = require("./reader").readString; diff --git a/lib/xml/nodes.js b/lib/xml/nodes.js new file mode 100644 index 0000000..ed273cf --- /dev/null +++ b/lib/xml/nodes.js @@ -0,0 +1,60 @@ +var _ = require("underscore"); + + +exports.Element = Element; + + +var emptyElement = { + first: function() { + return null; + }, + firstOrEmpty: function() { + return emptyElement; + }, + attributes: {} +}; + +function Element(name, attributes, children) { + this.type = "element"; + this.name = name; + this.attributes = attributes || {}; + this.children = children || []; +} + +Element.prototype.first = function(name) { + return _.find(this.children, function(child) { + return child.name == name; + }); +}; + +Element.prototype.firstOrEmpty = function(name) { + return this.first(name) || emptyElement; +}; + +Element.prototype.getElementsByTagName = function(name) { + var elements = _.filter(this.children, function(child) { + return child.name == name; + }); + return toElementList(elements); +}; + +Element.prototype.text = function() { + if (this.children.length === 0) { + return ""; + } else if (this.children.length !== 1 || this.children[0].type !== "text") { + throw new Error("Not implemented"); + } + return this.children[0].value; +}; + +var elementListPrototype = { + getElementsByTagName: function(name) { + return toElementList(_.flatten(this.map(function(element) { + return element.getElementsByTagName(name); + }, true))); + } +}; + +function toElementList(array) { + return _.extend(array, elementListPrototype); +} diff --git a/lib/xml/reader.js b/lib/xml/reader.js new file mode 100644 index 0000000..ab175f2 --- /dev/null +++ b/lib/xml/reader.js @@ -0,0 +1,87 @@ +var promises = require("../promises"); +var sax = require("sax"); +var _ = require("underscore"); + +var Element = require("./nodes").Element; + +exports.readString = readString; + +function readString(xmlString, namespaceMap) { + namespaceMap = namespaceMap || {}; + + var finished = false; + var parser = sax.parser(true, {xmlns: true, position: false}); + + var rootElement = {children: []}; + var currentElement = rootElement; + var stack = []; + + var deferred = promises.defer(); + + parser.onopentag = function(node) { + var attributes = mapObject(node.attributes, function(attribute) { + return attribute.value; + }, mapName); + + var element = new Element(mapName(node), attributes); + currentElement.children.push(element); + stack.push(currentElement); + currentElement = element; + }; + + function mapName(node) { + if (node.uri) { + var mappedPrefix = namespaceMap[node.uri]; + var prefix; + if (mappedPrefix) { + prefix = mappedPrefix; + } else { + prefix = "{" + node.uri + "}"; + } + return prefix + ":" + node.local; + } else { + return node.local; + } + } + + parser.onclosetag = function(node) { + currentElement = stack.pop(); + }; + + parser.ontext = function(text) { + if (currentElement !== rootElement) { + currentElement.children.push({ + type: "text", + value: text + }); + } + }; + + parser.onend = function() { + if (!finished) { + finished = true; + deferred.resolve({ + root: rootElement.children[0] + }); + } + }; + + parser.onerror = function(error) { + if (!finished) { + finished = true; + deferred.reject(error); + } + }; + + parser.write(xmlString).close(); + + return deferred.promise; +} + +function mapObject(input, valueFunc, keyFunc) { + return _.reduce(input, function(result, value, key) { + var mappedKey = keyFunc.call(null, value, key, input); + result[mappedKey] = valueFunc.call(null, value, key, input); + return result; + }, {}); +} diff --git a/lib/xmlreader.js b/lib/xmlreader.js deleted file mode 100644 index 551366f..0000000 --- a/lib/xmlreader.js +++ /dev/null @@ -1,141 +0,0 @@ -var promises = require("./promises"); -var sax = require("sax"); -var _ = require("underscore"); - -exports.read = read; -exports.Element = Element; - -function read(xmlString, namespaceMap) { - namespaceMap = namespaceMap || {}; - - var finished = false; - var parser = sax.parser(true, {xmlns: true, position: false}); - - var rootElement = {children: []}; - var currentElement = rootElement; - var stack = []; - - var deferred = promises.defer(); - - parser.onopentag = function(node) { - var attributes = mapObject(node.attributes, function(attribute) { - return attribute.value; - }, mapName); - - var element = new Element(mapName(node), attributes); - currentElement.children.push(element); - stack.push(currentElement); - currentElement = element; - }; - - function mapName(node) { - if (node.uri) { - var mappedPrefix = namespaceMap[node.uri]; - var prefix; - if (mappedPrefix) { - prefix = mappedPrefix; - } else { - prefix = "{" + node.uri + "}"; - } - return prefix + ":" + node.local; - } else { - return node.local; - } - } - - parser.onclosetag = function(node) { - currentElement = stack.pop(); - }; - - parser.ontext = function(text) { - if (currentElement !== rootElement) { - currentElement.children.push({ - type: "text", - value: text - }); - } - }; - - parser.onend = function() { - if (!finished) { - finished = true; - deferred.resolve({ - root: rootElement.children[0] - }); - } - }; - - parser.onerror = function(error) { - if (!finished) { - finished = true; - deferred.reject(error); - } - }; - - parser.write(xmlString).close(); - - return deferred.promise; -} - -function mapObject(input, valueFunc, keyFunc) { - return _.reduce(input, function(result, value, key) { - var mappedKey = keyFunc.call(null, value, key, input); - result[mappedKey] = valueFunc.call(null, value, key, input); - return result; - }, {}); -} - -var emptyElement = { - first: function() { - return null; - }, - firstOrEmpty: function() { - return emptyElement; - }, - attributes: {} -}; - -function Element(name, attributes, children) { - this.type = "element"; - this.name = name; - this.attributes = attributes || {}; - this.children = children || []; -} - -Element.prototype.first = function(name) { - return _.find(this.children, function(child) { - return child.name == name; - }); -}; - -Element.prototype.firstOrEmpty = function(name) { - return this.first(name) || emptyElement; -}; - -Element.prototype.getElementsByTagName = function(name) { - var elements = _.filter(this.children, function(child) { - return child.name == name; - }); - return toElementList(elements); -}; - -Element.prototype.text = function() { - if (this.children.length === 0) { - return ""; - } else if (this.children.length !== 1 || this.children[0].type !== "text") { - throw new Error("Not implemented"); - } - return this.children[0].value; -}; - -var elementListPrototype = { - getElementsByTagName: function(name) { - return toElementList(_.flatten(this.map(function(element) { - return element.getElementsByTagName(name); - }, true))); - } -}; - -function toElementList(array) { - return _.extend(array, elementListPrototype); -} diff --git a/tests/document-to-html.tests.js b/tests/document-to-html.tests.js index aef6dfb..323efb6 100644 --- a/tests/document-to-html.tests.js +++ b/tests/document-to-html.tests.js @@ -6,7 +6,7 @@ var documents = require("../lib/documents"); var DocumentConverter = require("../lib/document-to-html").DocumentConverter; var test = require("./testing").test; var htmlPaths = require("../lib/html-paths"); -var xmlreader = require("../lib/xmlreader"); +var xml = require("../lib/xml"); var results = require("../lib/results"); var documentMatchers = require("../lib/document-matchers"); @@ -459,7 +459,7 @@ describe('DocumentConverter', function() { var converter = new DocumentConverter(); return converter.convertToHtml(image) .then(function(result) { - return xmlreader.read(result.value); + return xml.readString(result.value); }) .then(function(htmlImageElementDocument) { var htmlImageElement = htmlImageElementDocument.root; diff --git a/tests/docx/body-reader.tests.js b/tests/docx/body-reader.tests.js index 172da76..ad2caa2 100644 --- a/tests/docx/body-reader.tests.js +++ b/tests/docx/body-reader.tests.js @@ -3,7 +3,7 @@ var path = require("path"); var BodyReader = require("../../lib/docx/body-reader").BodyReader; var documents = require("../../lib/documents"); -var XmlElement = require("../../lib/xmlreader").Element; +var XmlElement = require("../../lib/xml").Element; var Numbering = require("../../lib/docx/numbering-xml").Numbering; var Styles = require("../../lib/docx/styles-reader").Styles; var warning = require("../../lib/results").warning; diff --git a/tests/docx/content-types-reader.tests.js b/tests/docx/content-types-reader.tests.js index b0f135e..3b8c1d3 100644 --- a/tests/docx/content-types-reader.tests.js +++ b/tests/docx/content-types-reader.tests.js @@ -1,7 +1,7 @@ var assert = require("assert"); var readContentTypesFromXml = require("../../lib/docx/content-types-reader").readContentTypesFromXml; -var XmlElement = require("../../lib/xmlreader").Element; +var XmlElement = require("../../lib/xml").Element; var test = require("../testing").test; diff --git a/tests/docx/notes-reader.tests.js b/tests/docx/notes-reader.tests.js index 0cfb626..79ffed3 100644 --- a/tests/docx/notes-reader.tests.js +++ b/tests/docx/notes-reader.tests.js @@ -3,7 +3,7 @@ var assert = require("assert"); var createFootnotesReader = require("../../lib/docx/notes-reader").createFootnotesReader; var BodyReader = require("../../lib/docx/body-reader").BodyReader; var documents = require("../../lib/documents"); -var XmlElement = require("../../lib/xmlreader").Element; +var XmlElement = require("../../lib/xml").Element; var test = require("../testing").test; diff --git a/tests/docx/numbering-xml.tests.js b/tests/docx/numbering-xml.tests.js index 55c94e5..2fea5fb 100644 --- a/tests/docx/numbering-xml.tests.js +++ b/tests/docx/numbering-xml.tests.js @@ -1,7 +1,7 @@ var duck = require("duck"); var readNumberingXml = require("../../lib/docx/numbering-xml").readNumberingXml; -var XmlElement = require("../../lib/xmlreader").Element; +var XmlElement = require("../../lib/xml").Element; var test = require("../testing").test; diff --git a/tests/docx/styles-reader.tests.js b/tests/docx/styles-reader.tests.js index 73a9050..2bdda33 100644 --- a/tests/docx/styles-reader.tests.js +++ b/tests/docx/styles-reader.tests.js @@ -1,7 +1,7 @@ var assert = require("assert"); var readStylesXml = require("../../lib/docx/styles-reader").readStylesXml; -var XmlElement = require("../../lib/xmlreader").Element; +var XmlElement = require("../../lib/xml").Element; var test = require("../testing").test; diff --git a/tests/xml/reader.tests.js b/tests/xml/reader.tests.js new file mode 100644 index 0000000..49bcab5 --- /dev/null +++ b/tests/xml/reader.tests.js @@ -0,0 +1,88 @@ +var assert = require("assert"); + +var xmlreader = require("../../lib/xml/reader"); +var test = require("../testing").test; + + +describe('xmlreader.readString', function() { + test('should read self-closing element', function() { + return xmlreader.readString("").then(function(result) { + assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result.root); + }); + }); + + test('should read empty element with separate closing tag', function() { + return xmlreader.readString("").then(function(result) { + assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result.root); + }); + }); + + test('should read attributes of tags', function() { + return xmlreader.readString('').then(function(result) { + assert.deepEqual({name: "bob"}, result.root.attributes); + }); + }); + + test('can read text element', function() { + return xmlreader.readString('Hello!').then(function(result) { + assert.deepEqual({type: "text", value: "Hello!"}, result.root.children[0]); + }); + }); + + test('should read element with children', function() { + return xmlreader.readString("").then(function(result) { + var root = result.root; + assert.equal(2, root.children.length); + assert.equal("a", root.children[0].name); + assert.equal("b", root.children[1].name); + }); + }); + + test('unmapped namespaces URIs are included in braces as prefix', function() { + return xmlreader.readString('').then(function(result) { + assert.deepEqual(result.root.name, "{word}:body"); + }); + }); + + test('mapped namespaces URIs are translated using map', function() { + var namespaceMap = { + "word": "x" + }; + + return xmlreader.readString('', namespaceMap).then(function(result) { + assert.deepEqual(result.root.name, "x:body"); + }); + }); + + test('namespace of attributes is mapped to prefix', function() { + var namespaceMap = { + "word": "x" + }; + var xmlString = ''; + return xmlreader.readString(xmlString, namespaceMap).then(function(result) { + assert.deepEqual(result.root.attributes["x:val"], "Hello!"); + }); + }); + + test('can find first element with name', function() { + return xmlreader.readString('').then(function(result) { + var first = result.root.first("b"); + assert.equal("1", first.attributes.index); + }); + }); + + test('whitespace between xml declaration and root tag is ignored', function() { + return xmlreader.readString('\n').then(function(result) { + assert.deepEqual("body", result.root.name); + }); + }); + + test('error if XML is badly formed', function() { + return xmlreader.readString("").then(function(result) { - assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result.root); - }); - }); - - test('should read empty element with separate closing tag', function() { - return xmlreader.read("").then(function(result) { - assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result.root); - }); - }); - - test('should read attributes of tags', function() { - return xmlreader.read('').then(function(result) { - assert.deepEqual({name: "bob"}, result.root.attributes); - }); - }); - - test('can read text element', function() { - return xmlreader.read('Hello!').then(function(result) { - assert.deepEqual({type: "text", value: "Hello!"}, result.root.children[0]); - }); - }); - - test('should read element with children', function() { - return xmlreader.read("").then(function(result) { - var root = result.root; - assert.equal(2, root.children.length); - assert.equal("a", root.children[0].name); - assert.equal("b", root.children[1].name); - }); - }); - - test('unmapped namespaces URIs are included in braces as prefix', function() { - return xmlreader.read('').then(function(result) { - assert.deepEqual(result.root.name, "{word}:body"); - }); - }); - - test('mapped namespaces URIs are translated using map', function() { - var namespaceMap = { - "word": "x" - }; - - return xmlreader.read('', namespaceMap).then(function(result) { - assert.deepEqual(result.root.name, "x:body"); - }); - }); - - test('namespace of attributes is mapped to prefix', function() { - var namespaceMap = { - "word": "x" - }; - var xmlString = ''; - return xmlreader.read(xmlString, namespaceMap).then(function(result) { - assert.deepEqual(result.root.attributes["x:val"], "Hello!"); - }); - }); - - test('can find first element with name', function() { - return xmlreader.read('').then(function(result) { - var first = result.root.first("b"); - assert.equal("1", first.attributes.index); - }); - }); - - test('whitespace between xml declaration and root tag is ignored', function() { - return xmlreader.read('\n').then(function(result) { - assert.deepEqual("body", result.root.name); - }); - }); - - test('error if XML is badly formed', function() { - return xmlreader.read("