diff options
| author | Michael Williamson <mike@zwobble.org> | 2021-01-09 13:12:32 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2021-01-09 13:12:32 +0000 |
| commit | 83431a6feb6c4bb402b5c4f7c5f6e9b6d7123426 (patch) | |
| tree | bbb693a8371ead102be0ac48c7d136724fa717b0 | |
| parent | 9e7ca2e4a9020a488a1d7b9f371cf09f7aa69c02 (diff) | |
Support numbering defined by paragraph style
| -rw-r--r-- | NEWS | 4 | ||||
| -rw-r--r-- | lib/docx/body-reader.js | 9 | ||||
| -rw-r--r-- | lib/docx/numbering-xml.js | 27 | ||||
| -rw-r--r-- | test/docx/body-reader.tests.js | 55 | ||||
| -rw-r--r-- | test/docx/numbering-xml.tests.js | 25 |
5 files changed, 105 insertions, 15 deletions
@@ -1,3 +1,7 @@ +# 1.4.15 + +* Support numbering defined by paragraph style. + # 1.4.14 * Add style mapping for all caps. diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index 77f194b..2979a38 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -206,7 +206,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), + numbering: readNumberingProperties(style.styleId, element.firstOrEmpty("w:numPr"), numbering), indent: readParagraphIndent(element.firstOrEmpty("w:ind")) }; }); @@ -498,7 +498,12 @@ function BodyReader(options) { } -function readNumberingProperties(element, numbering) { +function readNumberingProperties(styleId, element, numbering) { + var levelByStyleId = numbering.findLevelByParagraphStyleId(styleId); + if (levelByStyleId != null) { + return levelByStyleId; + } + var level = element.firstOrEmpty("w:ilvl").attributes["w:val"]; var numId = element.firstOrEmpty("w:numId").attributes["w:val"]; if (level === undefined || numId === undefined) { diff --git a/lib/docx/numbering-xml.js b/lib/docx/numbering-xml.js index 90e479e..64c4210 100644 --- a/lib/docx/numbering-xml.js +++ b/lib/docx/numbering-xml.js @@ -1,8 +1,21 @@ +var _ = require("underscore"); + exports.readNumberingXml = readNumberingXml; exports.Numbering = Numbering; -exports.defaultNumbering = new Numbering({}); +exports.defaultNumbering = new Numbering({}, {}); function Numbering(nums, abstractNums, styles) { + var allLevels = _.flatten(_.values(abstractNums).map(function(abstractNum) { + return _.values(abstractNum.levels); + })); + + var levelsByParagraphStyleId = _.indexBy( + allLevels.filter(function(level) { + return level.paragraphStyleId != null; + }), + "paragraphStyleId" + ); + function findLevel(numId, level) { var num = nums[numId]; if (num) { @@ -18,8 +31,13 @@ function Numbering(nums, abstractNums, styles) { } } + function findLevelByParagraphStyleId(styleId) { + return levelsByParagraphStyleId[styleId] || null; + } + return { - findLevel: findLevel + findLevel: findLevel, + findLevelByParagraphStyleId: findLevelByParagraphStyleId }; } @@ -47,9 +65,12 @@ function readAbstractNum(element) { element.getElementsByTagName("w:lvl").forEach(function(levelElement) { var levelIndex = levelElement.attributes["w:ilvl"]; var numFmt = levelElement.first("w:numFmt").attributes["w:val"]; + var paragraphStyleId = levelElement.firstOrEmpty("w:pStyle").attributes["w:val"]; + levels[levelIndex] = { isOrdered: numFmt !== "bullet", - level: levelIndex + level: levelIndex, + paragraphStyleId: paragraphStyleId }; }); diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index adb32f8..331ab18 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -24,7 +24,7 @@ var _readNumberingProperties = require("../../lib/docx/body-reader")._readNumber var documents = require("../../lib/documents"); var xml = require("../../lib/xml"); var XmlElement = xml.Element; -var Numbering = require("../../lib/docx/numbering-xml").Numbering; +var defaultNumbering = require("../../lib/docx/numbering-xml").defaultNumbering; var Relationships = require("../../lib/docx/relationships-reader").Relationships; var Styles = require("../../lib/docx/styles-reader").Styles; var warning = require("../../lib/results").warning; @@ -36,6 +36,7 @@ var createFakeDocxFile = testing.createFakeDocxFile; function readXmlElement(element, options) { options = Object.create(options || {}); options.styles = options.styles || new Styles({}, {}); + options.numbering = options.numbering || defaultNumbering; return createBodyReader(options).readXmlElement(element); } @@ -156,21 +157,45 @@ test("paragraph has numbering properties from paragraph properties if present", var propertiesXml = new XmlElement("w:pPr", {}, [numberingPropertiesXml]); var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); - var numbering = new NumberingMap({"42": {"1": {isOrdered: true, level: "1"}}}); + var numbering = new NumberingMap({ + findLevel: {"42": {"1": {isOrdered: true, level: "1"}}} + }); var paragraph = readXmlElementValue(paragraphXml, {numbering: numbering}); assert.deepEqual(paragraph.numbering, {level: "1", isOrdered: true}); }); +test("numbering on paragraph style takes precedence over numPr", 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", {}, [ + new XmlElement("w:pStyle", {"w:val": "List"}), + numberingPropertiesXml + ]); + var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); + + var numbering = new NumberingMap({ + findLevelByParagraphStyleId: {"List": {isOrdered: true, level: "1"}} + }); + var styles = new Styles({"List": {name: "List"}}, {}); + + var paragraph = readXmlElementValue(paragraphXml, {numbering: numbering, styles: styles}); + assert.deepEqual(paragraph.numbering, {level: "1", isOrdered: true}); +}); + test("numbering properties are converted to numbering at specified level", function() { var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ new XmlElement("w:ilvl", {"w:val": "1"}), new XmlElement("w:numId", {"w:val": "42"}) ]); - var numbering = new NumberingMap({"42": {"1": {isOrdered: true, level: "1"}}}); + var numbering = new NumberingMap({ + findLevel: {"42": {"1": {isOrdered: true, level: "1"}}} + }); - var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering); + var numberingLevel = _readNumberingProperties(null, numberingPropertiesXml, numbering); assert.deepEqual(numberingLevel, {level: "1", isOrdered: true}); }); @@ -179,9 +204,11 @@ test("numbering properties are ignored if w:ilvl is missing", function() { new XmlElement("w:numId", {"w:val": "42"}) ]); - var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); + var numbering = new NumberingMap({ + findLevel: {"42": {"1": {isOrdered: true, level: "1"}}} + }); - var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering); + var numberingLevel = _readNumberingProperties(null, numberingPropertiesXml, numbering); assert.equal(numberingLevel, null); }); @@ -190,9 +217,11 @@ test("numbering properties are ignored if w:numId is missing", function() { new XmlElement("w:ilvl", {"w:val": "1"}) ]); - var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); + var numbering = new NumberingMap({ + findLevel: {"42": {"1": {isOrdered: true, level: "1"}}} + }); - var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering); + var numberingLevel = _readNumberingProperties(null, numberingPropertiesXml, numbering); assert.equal(numberingLevel, null); }); @@ -1324,10 +1353,16 @@ function imageRelationship(relationshipId, target) { }; } -function NumberingMap(nums) { +function NumberingMap(options) { + var findLevel = options.findLevel; + var findLevelByParagraphStyleId = options.findLevelByParagraphStyleId || {}; + return { findLevel: function(numId, level) { - return nums[numId][level]; + return findLevel[numId][level]; + }, + findLevelByParagraphStyleId: function(styleId) { + return findLevelByParagraphStyleId[styleId]; } }; } diff --git a/test/docx/numbering-xml.tests.js b/test/docx/numbering-xml.tests.js index dd400a7..1eefd37 100644 --- a/test/docx/numbering-xml.tests.js +++ b/test/docx/numbering-xml.tests.js @@ -58,6 +58,31 @@ test('when w:abstractNum has w:numStyleLink then style is used to find w:num', f })); }); + +// See: 17.9.23 pStyle (Paragraph Style's Associated Numbering Level) in ECMA-376, 4th Edition +test('numbering level can be found by paragraph style ID', function() { + var numbering = readNumberingXml( + 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:abstractNum", {"w:abstractNumId": "43"}, [ + new XmlElement("w:lvl", {"w:ilvl": "0"}, [ + new XmlElement("w:pStyle", {"w:val": "List"}), + new XmlElement("w:numFmt", {"w:val": "decimal"}) + ]) + ]) + ]), + {styles: stylesReader.defaultStyles} + ); + duck.assertThat(numbering.findLevelByParagraphStyleId("List"), duck.hasProperties({ + isOrdered: true + })); + duck.assertThat(numbering.findLevelByParagraphStyleId("Paragraph"), duck.equalTo(null)); +}); + test('when styles is missing then error is thrown', function() { assert.throws(function() { readNumberingXml(new XmlElement("w:numbering", {}, [])); |
