aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2021-01-09 13:12:32 +0000
committerMichael Williamson <mike@zwobble.org>2021-01-09 13:12:32 +0000
commit83431a6feb6c4bb402b5c4f7c5f6e9b6d7123426 (patch)
treebbb693a8371ead102be0ac48c7d136724fa717b0
parent9e7ca2e4a9020a488a1d7b9f371cf09f7aa69c02 (diff)
Support numbering defined by paragraph style
-rw-r--r--NEWS4
-rw-r--r--lib/docx/body-reader.js9
-rw-r--r--lib/docx/numbering-xml.js27
-rw-r--r--test/docx/body-reader.tests.js55
-rw-r--r--test/docx/numbering-xml.tests.js25
5 files changed, 105 insertions, 15 deletions
diff --git a/NEWS b/NEWS
index 28e58e8..b458827 100644
--- a/NEWS
+++ b/NEWS
@@ -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", {}, []));