From ee0baa36779e3ae2f2da9abe7e1c1c83fdcdf8fb Mon Sep 17 00:00:00 2001 From: Jonathan Lehman Date: Fri, 3 Feb 2017 17:00:21 -0500 Subject: Enable hyperlinks that are not defined with w:hyperlink --- lib/docx/body-reader.js | 32 +++++++++++++++++++++++++++----- test/docx/body-reader.tests.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index 529d6f2..8e71aa6 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -6,6 +6,7 @@ var documents = require("../documents"); var Result = require("../results").Result; var warning = require("../results").warning; var uriToZipEntryName = require("./uris").uriToZipEntryName; +var lastInstrText = null; function BodyReader(options) { @@ -133,14 +134,27 @@ function BodyReader(options) { return readXmlElements(element.children) .map(function(children) { var properties = _.find(children, isRunProperties); - - return new documents.Run( - children.filter(negate(isRunProperties)), - properties - ); + var filteredChildren = children.filter(negate(isRunProperties)); + + if (properties && properties.styleName === 'Hyperlink' && lastInstrText) { + return new documents.Hyperlink(filteredChildren, {href: parseHyperlink(lastInstrText)}); + } + + return new documents.Run(filteredChildren, properties); }); }, "w:rPr": readRunProperties, + "w:fldChar": function(element) { + var type = element.attributes["w:fldCharType"]; + if (type === 'end') { + clearState(); + } + return emptyResult(); + }, + "w:instrText": function(element) { + lastInstrText = (element.children[0] || {}).value || ''; + return emptyResult(); + }, "w:t": function(element) { return elementResult(new documents.Text(element.text())); }, @@ -491,3 +505,11 @@ function joinElements(first, second) { function identity(value) { return value; } + +function clearState() { + lastInstrText = null; +} + +function parseHyperlink(instrText) { + return (instrText.match(/HYPERLINK "(.*)"/) || [])[1]; +} diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index f605c2d..d1f0311 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -141,6 +141,18 @@ test("numbering properties are ignored if w:numId is missing", function() { assert.equal(numberingLevel, null); }); +test("stores instrText returns empty result", function() { + var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "http://example.com"')]); + var instrText = readXmlElementValue(instrTextXml); + assert.deepEqual(instrText, []); +}); + +test("run is wrapped in a hyperlink if the runStyle is Hyperlink", function() { + var hyperlink = createHyperlink("http://example.com"); + assert.equal(hyperlink.type, 'hyperlink'); + assert.equal(hyperlink.href, 'http://example.com'); +}); + test("run has no style if it has no properties", function() { var runXml = runWithProperties([]); var run = readXmlElementValue(runXml); @@ -861,6 +873,23 @@ function createLinkedBlip(relationshipId) { return new XmlElement("a:blip", {"r:link": relationshipId}); } +function createHyperlink(link) { + var beginXml = new XmlElement("w:r", {}, [ + new XmlElement("w:fldChar", {"w:fldCharType": "begin"}) + ]); + var endXml = new XmlElement("w:r", {}, [ + new XmlElement("w:fldChar", {"w:fldCharType": "end"}) + ]); + var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "' + link + '"')]); + var instrRunXml = new XmlElement("w:r", {}, [instrTextXml]); + var styleXml = new XmlElement("w:rStyle", {"w:val": "Hyperlink"}); + var runStyleXml = runWithProperties([styleXml]); + var parXml = new XmlElement("w:p", {}, [beginXml, instrRunXml, runStyleXml, endXml]); + var styles = new Styles({}, {"Hyperlink": {name: "Hyperlink"}}); + var par = readXmlElementValue(parXml, {styles: styles}); + return par.children[2]; +} + function assertImageBuffer(element, expectedImageBuffer) { return element.read() .then(function(readValue) { -- cgit v1.2.3 From 66e054e0f686dad8cd282dd01660e17b156b58e3 Mon Sep 17 00:00:00 2001 From: Jonathan Lehman Date: Mon, 6 Feb 2017 15:35:05 -0500 Subject: Refactor creating hyperlinks from complex fields --- lib/docx/body-reader.js | 21 +++++++++-------- test/docx/body-reader.tests.js | 51 +++++++++++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index 8e71aa6..a247e93 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -6,10 +6,10 @@ var documents = require("../documents"); var Result = require("../results").Result; var warning = require("../results").warning; var uriToZipEntryName = require("./uris").uriToZipEntryName; -var lastInstrText = null; function BodyReader(options) { + var complexFieldData = []; var relationships = options.relationships; var contentTypes = options.contentTypes; var docxFile = options.docxFile; @@ -136,8 +136,11 @@ function BodyReader(options) { var properties = _.find(children, isRunProperties); var filteredChildren = children.filter(negate(isRunProperties)); - if (properties && properties.styleName === 'Hyperlink' && lastInstrText) { - return new documents.Hyperlink(filteredChildren, {href: parseHyperlink(lastInstrText)}); + if (properties && properties.styleId === 'Hyperlink' && complexFieldData.length === 1) { + return new documents.Run( + [new documents.Hyperlink(filteredChildren, {href: parseHyperlinkFieldCode(complexFieldData)})], + properties + ); } return new documents.Run(filteredChildren, properties); @@ -147,12 +150,12 @@ function BodyReader(options) { "w:fldChar": function(element) { var type = element.attributes["w:fldCharType"]; if (type === 'end') { - clearState(); + complexFieldData = []; } return emptyResult(); }, "w:instrText": function(element) { - lastInstrText = (element.children[0] || {}).value || ''; + complexFieldData.push(element.children[0].value); return emptyResult(); }, "w:t": function(element) { @@ -506,10 +509,6 @@ function identity(value) { return value; } -function clearState() { - lastInstrText = null; -} - -function parseHyperlink(instrText) { - return (instrText.match(/HYPERLINK "(.*)"/) || [])[1]; +function parseHyperlinkFieldCode(complexFieldData) { + return (complexFieldData[0].match(/HYPERLINK "(.*)"/) || [])[1]; } diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index d1f0311..e0878a6 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -148,9 +148,39 @@ test("stores instrText returns empty result", function() { }); test("run is wrapped in a hyperlink if the runStyle is Hyperlink", function() { - var hyperlink = createHyperlink("http://example.com"); + var uri = "http://example.com"; + var beginXml = new XmlElement("w:r", {}, [ + new XmlElement("w:fldChar", {"w:fldCharType": "begin"}) + ]); + var endXml = new XmlElement("w:r", {}, [ + new XmlElement("w:fldChar", {"w:fldCharType": "end"}) + ]); + var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "' + uri + '"')]); + var hyperlinkRunXml = createHyperlinkRunXml('this is a hyperlink'); + var afterEndXml = createHyperlinkRunXml('this will not be a hyperlink'); + var parXml = new XmlElement("w:p", {}, [ + beginXml, + instrTextXml, + hyperlinkRunXml, + endXml, + afterEndXml + ]); + var styles = new Styles({}, {"Hyperlink": {name: "Hyperlink"}}); + var paragraph = readXmlElementValue(parXml, {styles: styles}); + + var hyperlinkRun = paragraph.children[1]; + var hyperlink = hyperlinkRun.children[0]; assert.equal(hyperlink.type, 'hyperlink'); - assert.equal(hyperlink.href, 'http://example.com'); + assert.equal(hyperlink.href, uri); + var hyperlinkText = hyperlink.children[0]; + assert.equal(hyperlinkText.type, 'text'); + assert.equal(hyperlinkText.value, 'this is a hyperlink'); + + // runs after fldCharType "end" do not become hyperlinks + var run = paragraph.children[3]; + var text = run.children[0]; + assert.equal(text.type, 'text'); + assert.equal(text.value, 'this will not be a hyperlink'); }); test("run has no style if it has no properties", function() { @@ -873,21 +903,10 @@ function createLinkedBlip(relationshipId) { return new XmlElement("a:blip", {"r:link": relationshipId}); } -function createHyperlink(link) { - var beginXml = new XmlElement("w:r", {}, [ - new XmlElement("w:fldChar", {"w:fldCharType": "begin"}) - ]); - var endXml = new XmlElement("w:r", {}, [ - new XmlElement("w:fldChar", {"w:fldCharType": "end"}) - ]); - var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "' + link + '"')]); - var instrRunXml = new XmlElement("w:r", {}, [instrTextXml]); +function createHyperlinkRunXml(text) { var styleXml = new XmlElement("w:rStyle", {"w:val": "Hyperlink"}); - var runStyleXml = runWithProperties([styleXml]); - var parXml = new XmlElement("w:p", {}, [beginXml, instrRunXml, runStyleXml, endXml]); - var styles = new Styles({}, {"Hyperlink": {name: "Hyperlink"}}); - var par = readXmlElementValue(parXml, {styles: styles}); - return par.children[2]; + var textXml = new XmlElement("w:t", {}, [xml.text(text)]); + return new XmlElement("w:r", {}, [createRunPropertiesXml([styleXml]), textXml]); } function assertImageBuffer(element, expectedImageBuffer) { -- cgit v1.2.3 From 0087555748ea139703ebc905e3ea7591615235f2 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 16 Feb 2017 11:01:49 +0000 Subject: Don't rely on styles to parse hyperlinks as complex fields --- lib/docx/body-reader.js | 31 ++++++++++++++++++------------- test/docx/body-reader.tests.js | 14 ++++++-------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index a247e93..08e1861 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -9,7 +9,7 @@ var uriToZipEntryName = require("./uris").uriToZipEntryName; function BodyReader(options) { - var complexFieldData = []; + var fieldHyperlink = null; var relationships = options.relationships; var contentTypes = options.contentTypes; var docxFile = options.docxFile; @@ -134,28 +134,28 @@ function BodyReader(options) { return readXmlElements(element.children) .map(function(children) { var properties = _.find(children, isRunProperties); - var filteredChildren = children.filter(negate(isRunProperties)); + children = children.filter(negate(isRunProperties)); - if (properties && properties.styleId === 'Hyperlink' && complexFieldData.length === 1) { - return new documents.Run( - [new documents.Hyperlink(filteredChildren, {href: parseHyperlinkFieldCode(complexFieldData)})], - properties - ); + if (fieldHyperlink !== null) { + children = [new documents.Hyperlink(children, {href: fieldHyperlink})]; } - return new documents.Run(filteredChildren, properties); + return new documents.Run(children, properties); }); }, "w:rPr": readRunProperties, "w:fldChar": function(element) { var type = element.attributes["w:fldCharType"]; - if (type === 'end') { - complexFieldData = []; + if (type === "end") { + fieldHyperlink = null; } return emptyResult(); }, "w:instrText": function(element) { - complexFieldData.push(element.children[0].value); + var hyperlink = parseHyperlinkFieldCode(element.text()); + if (hyperlink !== null) { + fieldHyperlink = hyperlink; + } return emptyResult(); }, "w:t": function(element) { @@ -509,6 +509,11 @@ function identity(value) { return value; } -function parseHyperlinkFieldCode(complexFieldData) { - return (complexFieldData[0].match(/HYPERLINK "(.*)"/) || [])[1]; +function parseHyperlinkFieldCode(code) { + var result = /HYPERLINK "(.*)"/.exec(code); + if (result) { + return result[1]; + } else { + return null; + } } diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index e0878a6..147684f 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -147,7 +147,7 @@ test("stores instrText returns empty result", function() { assert.deepEqual(instrText, []); }); -test("run is wrapped in a hyperlink if the runStyle is Hyperlink", function() { +test("runs in a complex field for hyperlinks are read as hyperlinks", function() { var uri = "http://example.com"; var beginXml = new XmlElement("w:r", {}, [ new XmlElement("w:fldChar", {"w:fldCharType": "begin"}) @@ -156,8 +156,8 @@ test("run is wrapped in a hyperlink if the runStyle is Hyperlink", function() { new XmlElement("w:fldChar", {"w:fldCharType": "end"}) ]); var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "' + uri + '"')]); - var hyperlinkRunXml = createHyperlinkRunXml('this is a hyperlink'); - var afterEndXml = createHyperlinkRunXml('this will not be a hyperlink'); + var hyperlinkRunXml = runOfText("this is a hyperlink"); + var afterEndXml = runOfText("this will not be a hyperlink"); var parXml = new XmlElement("w:p", {}, [ beginXml, instrTextXml, @@ -165,8 +165,7 @@ test("run is wrapped in a hyperlink if the runStyle is Hyperlink", function() { endXml, afterEndXml ]); - var styles = new Styles({}, {"Hyperlink": {name: "Hyperlink"}}); - var paragraph = readXmlElementValue(parXml, {styles: styles}); + var paragraph = readXmlElementValue(parXml); var hyperlinkRun = paragraph.children[1]; var hyperlink = hyperlinkRun.children[0]; @@ -903,10 +902,9 @@ function createLinkedBlip(relationshipId) { return new XmlElement("a:blip", {"r:link": relationshipId}); } -function createHyperlinkRunXml(text) { - var styleXml = new XmlElement("w:rStyle", {"w:val": "Hyperlink"}); +function runOfText(text) { var textXml = new XmlElement("w:t", {}, [xml.text(text)]); - return new XmlElement("w:r", {}, [createRunPropertiesXml([styleXml]), textXml]); + return new XmlElement("w:r", {}, [textXml]); } function assertImageBuffer(element, expectedImageBuffer) { -- cgit v1.2.3 From fdd4a4d329a647912fd48991472ff61996684862 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 16 Feb 2017 11:05:02 +0000 Subject: Tighten regex for hyperlink field code --- lib/docx/body-reader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index 08e1861..a0a90fd 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -510,7 +510,7 @@ function identity(value) { } function parseHyperlinkFieldCode(code) { - var result = /HYPERLINK "(.*)"/.exec(code); + var result = /\s*HYPERLINK "(.*)"/.exec(code); if (result) { return result[1]; } else { -- cgit v1.2.3 From 7a738f5b619de07eca4bd5e49259912a839b15d2 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 16 Feb 2017 11:20:25 +0000 Subject: Remove explicit indices in test --- test/docx/body-reader.tests.js | 42 ++++++++++++++++++++++++++++-------------- test/docx/document-matchers.js | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 test/docx/document-matchers.js diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 147684f..9bd2e34 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -3,6 +3,7 @@ var path = require("path"); var _ = require("underscore"); var hamjest = require("hamjest"); +var assertThat = hamjest.assertThat; var promiseThat = hamjest.promiseThat; var allOf = hamjest.allOf; var contains = hamjest.contains; @@ -10,6 +11,12 @@ var hasProperties = hamjest.hasProperties; var willBe = hamjest.willBe; var FeatureMatcher = hamjest.FeatureMatcher; +var documentMatchers = require("./document-matchers"); +var isEmptyRun = documentMatchers.isEmptyRun; +var isHyperlink = documentMatchers.isHyperlink; +var isRun = documentMatchers.isRun; +var isText = documentMatchers.isText; + var BodyReader = require("../../lib/docx/body-reader").BodyReader; var documents = require("../../lib/documents"); var xml = require("../../lib/xml"); @@ -166,20 +173,27 @@ test("runs in a complex field for hyperlinks are read as hyperlinks", function() afterEndXml ]); var paragraph = readXmlElementValue(parXml); - - var hyperlinkRun = paragraph.children[1]; - var hyperlink = hyperlinkRun.children[0]; - assert.equal(hyperlink.type, 'hyperlink'); - assert.equal(hyperlink.href, uri); - var hyperlinkText = hyperlink.children[0]; - assert.equal(hyperlinkText.type, 'text'); - assert.equal(hyperlinkText.value, 'this is a hyperlink'); - - // runs after fldCharType "end" do not become hyperlinks - var run = paragraph.children[3]; - var text = run.children[0]; - assert.equal(text.type, 'text'); - assert.equal(text.value, 'this will not be a hyperlink'); + + assertThat(paragraph.children, contains( + isEmptyRun, + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: contains( + isText("this is a hyperlink") + ) + }) + ) + }), + isEmptyRun, + isRun({ + // runs after fldCharType "end" do not become hyperlinks + children: contains( + isText("this will not be a hyperlink") + ) + }) + )); }); test("run has no style if it has no properties", function() { diff --git a/test/docx/document-matchers.js b/test/docx/document-matchers.js new file mode 100644 index 0000000..b750bed --- /dev/null +++ b/test/docx/document-matchers.js @@ -0,0 +1,27 @@ +var hamjest = require("hamjest"); +var _ = require("underscore"); + +var documents = require("../../lib/documents"); + + +exports.isEmptyRun = isRun({children: []}); +exports.isRun = isRun; +exports.isText = isText; +exports.isHyperlink = isHyperlink; + + +function isRun(properties) { + return isDocumentElement(documents.types.run, properties); +} + +function isText(text) { + return isDocumentElement(documents.types.text, {value: text}); +} + +function isHyperlink(properties) { + return isDocumentElement(documents.types.hyperlink, properties); +} + +function isDocumentElement(type, properties) { + return hamjest.hasProperties(_.extend({type: hamjest.equalTo(type)}, properties)); +} -- cgit v1.2.3 From fef4b00a315d85d061ab3218380283345e7d1aa1 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 16 Feb 2017 11:22:42 +0000 Subject: Group together complex field tests --- test/docx/body-reader.tests.js | 79 ++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 9bd2e34..b1aab77 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -148,13 +148,7 @@ test("numbering properties are ignored if w:numId is missing", function() { assert.equal(numberingLevel, null); }); -test("stores instrText returns empty result", function() { - var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "http://example.com"')]); - var instrText = readXmlElementValue(instrTextXml); - assert.deepEqual(instrText, []); -}); - -test("runs in a complex field for hyperlinks are read as hyperlinks", function() { +test("complex fields", (function() { var uri = "http://example.com"; var beginXml = new XmlElement("w:r", {}, [ new XmlElement("w:fldChar", {"w:fldCharType": "begin"}) @@ -162,39 +156,50 @@ test("runs in a complex field for hyperlinks are read as hyperlinks", function() var endXml = new XmlElement("w:r", {}, [ new XmlElement("w:fldChar", {"w:fldCharType": "end"}) ]); - var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "' + uri + '"')]); - var hyperlinkRunXml = runOfText("this is a hyperlink"); - var afterEndXml = runOfText("this will not be a hyperlink"); - var parXml = new XmlElement("w:p", {}, [ - beginXml, - instrTextXml, - hyperlinkRunXml, - endXml, - afterEndXml - ]); - var paragraph = readXmlElementValue(parXml); - - assertThat(paragraph.children, contains( - isEmptyRun, - isRun({ - children: contains( - isHyperlink({ - href: uri, + + return { + "stores instrText returns empty result": function() { + var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "http://example.com"')]); + var instrText = readXmlElementValue(instrTextXml); + assert.deepEqual(instrText, []); + }, + + "runs in a complex field for hyperlinks are read as hyperlinks": function() { + var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "' + uri + '"')]); + var hyperlinkRunXml = runOfText("this is a hyperlink"); + var afterEndXml = runOfText("this will not be a hyperlink"); + var parXml = new XmlElement("w:p", {}, [ + beginXml, + instrTextXml, + hyperlinkRunXml, + endXml, + afterEndXml + ]); + var paragraph = readXmlElementValue(parXml); + + assertThat(paragraph.children, contains( + isEmptyRun, + isRun({ children: contains( - isText("this is a hyperlink") + isHyperlink({ + href: uri, + children: contains( + isText("this is a hyperlink") + ) + }) + ) + }), + isEmptyRun, + isRun({ + // runs after fldCharType "end" do not become hyperlinks + children: contains( + isText("this will not be a hyperlink") ) }) - ) - }), - isEmptyRun, - isRun({ - // runs after fldCharType "end" do not become hyperlinks - children: contains( - isText("this will not be a hyperlink") - ) - }) - )); -}); + )); + } + }; +})()); test("run has no style if it has no properties", function() { var runXml = runWithProperties([]); -- cgit v1.2.3 From 56020bc35f1e7f93989a5b38686cee515ba31b36 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 16 Feb 2017 11:24:08 +0000 Subject: Extract hyperlinkInstrText --- test/docx/body-reader.tests.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index b1aab77..0aad67a 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -156,21 +156,22 @@ test("complex fields", (function() { var endXml = new XmlElement("w:r", {}, [ new XmlElement("w:fldChar", {"w:fldCharType": "end"}) ]); + var hyperlinkInstrText = new XmlElement("w:instrText", {}, [ + xml.text(' HYPERLINK "' + uri + '"') + ]); return { "stores instrText returns empty result": function() { - var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "http://example.com"')]); - var instrText = readXmlElementValue(instrTextXml); + var instrText = readXmlElementValue(hyperlinkInstrText); assert.deepEqual(instrText, []); }, "runs in a complex field for hyperlinks are read as hyperlinks": function() { - var instrTextXml = new XmlElement("w:instrText", {}, [xml.text(' HYPERLINK "' + uri + '"')]); var hyperlinkRunXml = runOfText("this is a hyperlink"); var afterEndXml = runOfText("this will not be a hyperlink"); var parXml = new XmlElement("w:p", {}, [ beginXml, - instrTextXml, + hyperlinkInstrText, hyperlinkRunXml, endXml, afterEndXml -- cgit v1.2.3 From 0504e5eeea18906d69ecdb3f832fd928f749f026 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 16 Feb 2017 11:25:29 +0000 Subject: Separate test for runs after complex fields --- test/docx/body-reader.tests.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 0aad67a..588d26a 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -173,8 +173,7 @@ test("complex fields", (function() { beginXml, hyperlinkInstrText, hyperlinkRunXml, - endXml, - afterEndXml + endXml ]); var paragraph = readXmlElementValue(parXml); @@ -190,9 +189,24 @@ test("complex fields", (function() { }) ) }), + isEmptyRun + )); + }, + + "runs after a complex field for hyperlinks are not read as hyperlinks": function() { + var afterEndXml = runOfText("this will not be a hyperlink"); + var parXml = new XmlElement("w:p", {}, [ + beginXml, + hyperlinkInstrText, + endXml, + afterEndXml + ]); + var paragraph = readXmlElementValue(parXml); + + assertThat(paragraph.children, contains( + isEmptyRun, isEmptyRun, isRun({ - // runs after fldCharType "end" do not become hyperlinks children: contains( isText("this will not be a hyperlink") ) -- cgit v1.2.3 From 82d37396cdb2c9dce47fd1686e1219d67db03faf Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 16 Feb 2017 11:26:12 +0000 Subject: Rename parXml to paragraphXml --- test/docx/body-reader.tests.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 588d26a..2528812 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -169,13 +169,13 @@ test("complex fields", (function() { "runs in a complex field for hyperlinks are read as hyperlinks": function() { var hyperlinkRunXml = runOfText("this is a hyperlink"); var afterEndXml = runOfText("this will not be a hyperlink"); - var parXml = new XmlElement("w:p", {}, [ + var paragraphXml = new XmlElement("w:p", {}, [ beginXml, hyperlinkInstrText, hyperlinkRunXml, endXml ]); - var paragraph = readXmlElementValue(parXml); + var paragraph = readXmlElementValue(paragraphXml); assertThat(paragraph.children, contains( isEmptyRun, @@ -195,13 +195,13 @@ test("complex fields", (function() { "runs after a complex field for hyperlinks are not read as hyperlinks": function() { var afterEndXml = runOfText("this will not be a hyperlink"); - var parXml = new XmlElement("w:p", {}, [ + var paragraphXml = new XmlElement("w:p", {}, [ beginXml, hyperlinkInstrText, endXml, afterEndXml ]); - var paragraph = readXmlElementValue(parXml); + var paragraph = readXmlElementValue(paragraphXml); assertThat(paragraph.children, contains( isEmptyRun, -- cgit v1.2.3 From c626ef8e1047fb9f27fc4db8c17783a711bd566a Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 16 Feb 2017 11:43:57 +0000 Subject: Hide state in body reader from callers --- lib/docx/body-reader.js | 39 +++++++++++++++++++++++++------------- lib/docx/docx-reader.js | 4 ++-- test/docx/body-reader.tests.js | 20 ++++++------------- test/docx/comments-reader.tests.js | 4 ++-- test/docx/notes-reader.tests.js | 4 ++-- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index a0a90fd..e92f8b4 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -1,4 +1,5 @@ -exports.BodyReader = BodyReader; +exports.createBodyReader = createBodyReader; +exports._readNumberingProperties = readNumberingProperties; var _ = require("underscore"); @@ -7,6 +8,16 @@ var Result = require("../results").Result; var warning = require("../results").warning; var uriToZipEntryName = require("./uris").uriToZipEntryName; +function createBodyReader(options) { + return { + readXmlElement: function(element) { + return new BodyReader(options).readXmlElement(element); + }, + readXmlElements: function(elements) { + return new BodyReader(options).readXmlElements(elements); + } + }; +} function BodyReader(options) { var fieldHyperlink = null; @@ -126,7 +137,7 @@ function BodyReader(options) { styleId: style.styleId, styleName: style.name, alignment: element.firstOrEmpty("w:jc").attributes["w:val"], - numbering: readNumberingProperties(element.firstOrEmpty("w:numPr")) + numbering: readNumberingProperties(element.firstOrEmpty("w:numPr"), numbering) }; }); }, @@ -227,21 +238,12 @@ function BodyReader(options) { "wp:anchor": readDrawingElement, "v:imagedata": readImageData }; + return { readXmlElement: readXmlElement, - readXmlElements: readXmlElements, - _readNumberingProperties: readNumberingProperties + readXmlElements: readXmlElements }; - function readNumberingProperties(element) { - var level = element.firstOrEmpty("w:ilvl").attributes["w:val"]; - var numId = element.firstOrEmpty("w:numId").attributes["w:val"]; - if (level === undefined || numId === undefined) { - return null; - } else { - return numbering.findLevel(numId, level); - } - } function readTable(element) { return readXmlElements(element.children) @@ -396,6 +398,17 @@ function BodyReader(options) { type + " style with ID " + styleId + " was referenced but not defined in the document"); } } + + +function readNumberingProperties(element, numbering) { + var level = element.firstOrEmpty("w:ilvl").attributes["w:val"]; + var numId = element.firstOrEmpty("w:numId").attributes["w:val"]; + if (level === undefined || numId === undefined) { + return null; + } else { + return numbering.findLevel(numId, level); + } +} var supportedImageTypes = { "image/png": true, diff --git a/lib/docx/docx-reader.js b/lib/docx/docx-reader.js index 0c7d57f..5aa95ca 100644 --- a/lib/docx/docx-reader.js +++ b/lib/docx/docx-reader.js @@ -7,7 +7,7 @@ var documents = require("../documents"); var Result = require("../results").Result; var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile; -var BodyReader = require("./body-reader").BodyReader; +var createBodyReader = require("./body-reader").createBodyReader; var DocumentXmlReader = require("./document-xml-reader").DocumentXmlReader; var relationshipsReader = require("./relationships-reader"); var contentTypesReader = require("./content-types-reader"); @@ -95,7 +95,7 @@ function readXmlFileWithBody(name, options, func) { }); return readRelationshipsFromZipFile(options.docxFile).then(function(relationships) { - var bodyReader = new BodyReader({ + var bodyReader = new createBodyReader({ relationships: relationships, contentTypes: options.contentTypes, docxFile: options.docxFile, diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 2528812..ab4995c 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -17,7 +17,8 @@ var isHyperlink = documentMatchers.isHyperlink; var isRun = documentMatchers.isRun; var isText = documentMatchers.isText; -var BodyReader = require("../../lib/docx/body-reader").BodyReader; +var createBodyReader = require("../../lib/docx/body-reader").createBodyReader; +var _readNumberingProperties = require("../../lib/docx/body-reader")._readNumberingProperties; var documents = require("../../lib/documents"); var xml = require("../../lib/xml"); var XmlElement = xml.Element; @@ -32,7 +33,7 @@ var createFakeDocxFile = testing.createFakeDocxFile; function readXmlElement(element, options) { options = Object.create(options || {}); options.styles = options.styles || new Styles({}, {}); - return new BodyReader(options).readXmlElement(element); + return createBodyReader(options).readXmlElement(element); } function readXmlElementValue(element, options) { @@ -113,10 +114,7 @@ test("numbering properties are converted to numbering at specified level", funct var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - var reader = new BodyReader({ - numbering: numbering - }); - var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); + var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering); assert.deepEqual(numberingLevel, {level: "1", isOrdered: true}); }); @@ -127,10 +125,7 @@ test("numbering properties are ignored if w:ilvl is missing", function() { var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - var reader = new BodyReader({ - numbering: numbering - }); - var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); + var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering); assert.equal(numberingLevel, null); }); @@ -141,10 +136,7 @@ test("numbering properties are ignored if w:numId is missing", function() { var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - var reader = new BodyReader({ - numbering: numbering - }); - var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); + var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering); assert.equal(numberingLevel, null); }); diff --git a/test/docx/comments-reader.tests.js b/test/docx/comments-reader.tests.js index 4bb0e9b..0b3c0ce 100644 --- a/test/docx/comments-reader.tests.js +++ b/test/docx/comments-reader.tests.js @@ -1,14 +1,14 @@ var assert = require("assert"); var createCommentsReader = require("../../lib/docx/comments-reader").createCommentsReader; -var BodyReader = require("../../lib/docx/body-reader").BodyReader; +var createBodyReader = require("../../lib/docx/body-reader").createBodyReader; var documents = require("../../lib/documents"); var xml = require("../../lib/xml"); var test = require("../test")(module); function readComment(element) { - var bodyReader = new BodyReader({}); + var bodyReader = createBodyReader({}); var commentsReader = createCommentsReader(bodyReader); var comments = commentsReader(element); assert.equal(comments.value.length, 1); diff --git a/test/docx/notes-reader.tests.js b/test/docx/notes-reader.tests.js index 1d4cf9c..71bb794 100644 --- a/test/docx/notes-reader.tests.js +++ b/test/docx/notes-reader.tests.js @@ -1,14 +1,14 @@ var assert = require("assert"); var createFootnotesReader = require("../../lib/docx/notes-reader").createFootnotesReader; -var BodyReader = require("../../lib/docx/body-reader").BodyReader; +var createBodyReader = require("../../lib/docx/body-reader").createBodyReader; var documents = require("../../lib/documents"); var XmlElement = require("../../lib/xml").Element; var test = require("../test")(module); test('ID and body of footnote are read', function() { - var bodyReader = new BodyReader({}); + var bodyReader = new createBodyReader({}); var footnoteBody = [new XmlElement("w:p", {}, [])]; var footnotes = createFootnotesReader(bodyReader)( new XmlElement("w:footnotes", {}, [ -- cgit v1.2.3 From 75e327ddfff69d7bdbaff994e4662f7c37cc2b85 Mon Sep 17 00:00:00 2001 From: Jonathan Lehman Date: Fri, 17 Feb 2017 11:59:38 -0500 Subject: Handle split instrText elements --- lib/docx/body-reader.js | 12 +++++--- test/docx/body-reader.tests.js | 64 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index e92f8b4..9345e02 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -21,6 +21,7 @@ function createBodyReader(options) { function BodyReader(options) { var fieldHyperlink = null; + var currentInstrText = []; var relationships = options.relationships; var contentTypes = options.contentTypes; var docxFile = options.docxFile; @@ -159,14 +160,17 @@ function BodyReader(options) { var type = element.attributes["w:fldCharType"]; if (type === "end") { fieldHyperlink = null; + } else if (type === "separate") { + var hyperlink = parseHyperlinkFieldCode(currentInstrText.join('')); + if (hyperlink !== null) { + fieldHyperlink = hyperlink; + } + currentInstrText = []; } return emptyResult(); }, "w:instrText": function(element) { - var hyperlink = parseHyperlinkFieldCode(element.text()); - if (hyperlink !== null) { - fieldHyperlink = hyperlink; - } + currentInstrText.push(element.text()); return emptyResult(); }, "w:t": function(element) { diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index ab4995c..4cb7bb3 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -148,6 +148,9 @@ test("complex fields", (function() { var endXml = new XmlElement("w:r", {}, [ new XmlElement("w:fldChar", {"w:fldCharType": "end"}) ]); + var separateXml = new XmlElement("w:r", {}, [ + new XmlElement("w:fldChar", {"w:fldCharType": "separate"}) + ]); var hyperlinkInstrText = new XmlElement("w:instrText", {}, [ xml.text(' HYPERLINK "' + uri + '"') ]); @@ -160,10 +163,10 @@ test("complex fields", (function() { "runs in a complex field for hyperlinks are read as hyperlinks": function() { var hyperlinkRunXml = runOfText("this is a hyperlink"); - var afterEndXml = runOfText("this will not be a hyperlink"); var paragraphXml = new XmlElement("w:p", {}, [ beginXml, hyperlinkInstrText, + separateXml, hyperlinkRunXml, endXml ]); @@ -171,6 +174,14 @@ test("complex fields", (function() { assertThat(paragraph.children, contains( isEmptyRun, + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: [] + }) + ) + }), isRun({ children: contains( isHyperlink({ @@ -190,6 +201,7 @@ test("complex fields", (function() { var paragraphXml = new XmlElement("w:p", {}, [ beginXml, hyperlinkInstrText, + separateXml, endXml, afterEndXml ]); @@ -197,6 +209,14 @@ test("complex fields", (function() { assertThat(paragraph.children, contains( isEmptyRun, + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: [] + }) + ) + }), isEmptyRun, isRun({ children: contains( @@ -204,6 +224,48 @@ test("complex fields", (function() { ) }) )); + }, + + "can handle split instrText elements": function() { + var hyperlinkRunXml = runOfText("this is a hyperlink"); + var hyperlinkInstrTextPart1 = new XmlElement("w:instrText", {}, [ + xml.text(" HYPE") + ]); + var hyperlinkInstrTextPart2 = new XmlElement("w:instrText", {}, [ + xml.text('RLINK "' + uri + '"') + ]); + var paragraphXml = new XmlElement("w:p", {}, [ + beginXml, + hyperlinkInstrTextPart1, + hyperlinkInstrTextPart2, + separateXml, + hyperlinkRunXml, + endXml + ]); + var paragraph = readXmlElementValue(paragraphXml); + + assertThat(paragraph.children, contains( + isEmptyRun, + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: [] + }) + ) + }), + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: contains( + isText("this is a hyperlink") + ) + }) + ) + }), + isEmptyRun + )); } }; })()); -- cgit v1.2.3 From c044f44f404bb4e38d38082d2989c5686604bb3e Mon Sep 17 00:00:00 2001 From: Jonathan Lehman Date: Fri, 17 Feb 2017 12:35:26 -0500 Subject: Handle nested complex fields --- lib/docx/body-reader.js | 13 ++++++------- test/docx/body-reader.tests.js | 44 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index 9345e02..e671f53 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -20,7 +20,7 @@ function createBodyReader(options) { } function BodyReader(options) { - var fieldHyperlink = null; + var fieldHyperlinks = []; var currentInstrText = []; var relationships = options.relationships; var contentTypes = options.contentTypes; @@ -148,8 +148,9 @@ function BodyReader(options) { var properties = _.find(children, isRunProperties); children = children.filter(negate(isRunProperties)); - if (fieldHyperlink !== null) { - children = [new documents.Hyperlink(children, {href: fieldHyperlink})]; + var validFieldHyperlinks = _.compact(fieldHyperlinks); + if (validFieldHyperlinks.length !== 0) { + children = [new documents.Hyperlink(children, {href: _.last(validFieldHyperlinks)})]; } return new documents.Run(children, properties); @@ -159,12 +160,10 @@ function BodyReader(options) { "w:fldChar": function(element) { var type = element.attributes["w:fldCharType"]; if (type === "end") { - fieldHyperlink = null; + fieldHyperlinks.pop(); } else if (type === "separate") { var hyperlink = parseHyperlinkFieldCode(currentInstrText.join('')); - if (hyperlink !== null) { - fieldHyperlink = hyperlink; - } + fieldHyperlinks.push(hyperlink); currentInstrText = []; } return emptyResult(); diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 4cb7bb3..1882f3a 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -154,6 +154,7 @@ test("complex fields", (function() { var hyperlinkInstrText = new XmlElement("w:instrText", {}, [ xml.text(' HYPERLINK "' + uri + '"') ]); + var hyperlinkRunXml = runOfText("this is a hyperlink"); return { "stores instrText returns empty result": function() { @@ -227,7 +228,6 @@ test("complex fields", (function() { }, "can handle split instrText elements": function() { - var hyperlinkRunXml = runOfText("this is a hyperlink"); var hyperlinkInstrTextPart1 = new XmlElement("w:instrText", {}, [ xml.text(" HYPE") ]); @@ -266,6 +266,48 @@ test("complex fields", (function() { }), isEmptyRun )); + }, + + "hyperlink with a nested complex field uses the outer hyperlink": function() { + var authorInstrText = new XmlElement("w:instrText", {}, [ + xml.text(' AUTHOR "John Doe"') + ]); + var paragraphXml = new XmlElement("w:p", {}, [ + beginXml, + beginXml, + authorInstrText, + endXml, + hyperlinkInstrText, + separateXml, + hyperlinkRunXml, + endXml + ]); + var paragraph = readXmlElementValue(paragraphXml); + + assertThat(paragraph.children, contains( + isEmptyRun, + isEmptyRun, + isEmptyRun, + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: [] + }) + ) + }), + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: contains( + isText("this is a hyperlink") + ) + }) + ) + }), + isEmptyRun + )); } }; })()); -- cgit v1.2.3 From 2555036e27441428666e08408a082346fbef3b62 Mon Sep 17 00:00:00 2001 From: Jonathan Lehman Date: Sat, 18 Feb 2017 11:40:43 -0500 Subject: Store all complex fields with types - Currently only stores hyperlink complex fields and all other fields are null --- lib/docx/body-reader.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index e671f53..11aeec5 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -20,7 +20,7 @@ function createBodyReader(options) { } function BodyReader(options) { - var fieldHyperlinks = []; + var complexFields = []; var currentInstrText = []; var relationships = options.relationships; var contentTypes = options.contentTypes; @@ -148,9 +148,9 @@ function BodyReader(options) { var properties = _.find(children, isRunProperties); children = children.filter(negate(isRunProperties)); - var validFieldHyperlinks = _.compact(fieldHyperlinks); - if (validFieldHyperlinks.length !== 0) { - children = [new documents.Hyperlink(children, {href: _.last(validFieldHyperlinks)})]; + var scopedHyperlink = scopedComplexFieldOfType(complexFields, "hyperlink"); + if (scopedHyperlink !== undefined) { + children = [new documents.Hyperlink(children, {href: scopedHyperlink.href})]; } return new documents.Run(children, properties); @@ -160,10 +160,14 @@ function BodyReader(options) { "w:fldChar": function(element) { var type = element.attributes["w:fldCharType"]; if (type === "end") { - fieldHyperlinks.pop(); + complexFields.pop(); } else if (type === "separate") { - var hyperlink = parseHyperlinkFieldCode(currentInstrText.join('')); - fieldHyperlinks.push(hyperlink); + var href = parseHyperlinkFieldCode(currentInstrText.join('')); + if (href !== null) { + complexFields.push({type: "hyperlink", href: href}); + } else { + complexFields.push(null); + } currentInstrText = []; } return emptyResult(); @@ -533,3 +537,13 @@ function parseHyperlinkFieldCode(code) { return null; } } + +function complexFieldsOfType(complexFields, type) { + return complexFields.filter(function(complexField) { + return complexField.type === type; + }); +} + +function scopedComplexFieldOfType(complexFields, type) { + return _.last(complexFieldsOfType(complexFields, type)); +} -- cgit v1.2.3 From 35df4eed2249ee7e582cca4d371a78ce92c44d2a Mon Sep 17 00:00:00 2001 From: Jonathan Lehman Date: Sat, 18 Feb 2017 11:48:22 -0500 Subject: Add test case for complex fields nested within a hyperlink complex field --- test/docx/body-reader.tests.js | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 1882f3a..a670202 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -308,6 +308,55 @@ test("complex fields", (function() { }), isEmptyRun )); + }, + + "complex field nested within a hyperlink complex field is wrapped with the hyperlink": function() { + var authorInstrText = new XmlElement("w:instrText", {}, [ + xml.text(' AUTHOR "John Doe"') + ]); + var paragraphXml = new XmlElement("w:p", {}, [ + beginXml, + hyperlinkInstrText, + separateXml, + hyperlinkRunXml, + beginXml, + authorInstrText, + endXml, + endXml + ]); + var paragraph = readXmlElementValue(paragraphXml); + + assertThat(paragraph.children, contains( + isEmptyRun, + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: [] + }) + ) + }), + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: contains( + isText("this is a hyperlink") + ) + }) + ) + }), + isRun({ + children: contains( + isHyperlink({ + href: uri, + children: [] + }) + ) + }), + isEmptyRun, + isEmptyRun + )); } }; })()); -- cgit v1.2.3 From f89115c9817b4cd4da91c7e3e9882b0eac4ae40d Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 19 Feb 2017 11:06:32 +0000 Subject: Reset currentInstrText on "begin" fldChar --- lib/docx/body-reader.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index 11aeec5..a19ecdb 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -159,7 +159,9 @@ function BodyReader(options) { "w:rPr": readRunProperties, "w:fldChar": function(element) { var type = element.attributes["w:fldCharType"]; - if (type === "end") { + if (type === "begin") { + currentInstrText = []; + } else if (type === "end") { complexFields.pop(); } else if (type === "separate") { var href = parseHyperlinkFieldCode(currentInstrText.join('')); @@ -168,7 +170,6 @@ function BodyReader(options) { } else { complexFields.push(null); } - currentInstrText = []; } return emptyResult(); }, -- cgit v1.2.3