easy-template-x 2.1.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -4
- package/dist/cjs/easy-template-x.js +63 -35
- package/dist/es/easy-template-x.js +57 -30
- package/dist/types/plugins/loop/loopPlugin.d.ts +2 -0
- package/package.json +27 -27
- package/src/@types/lodash.d.ts +12 -0
- package/src/compilation/scopeData.ts +1 -2
- package/src/plugins/loop/loopPlugin.ts +35 -6
- package/CHANGELOG.md +0 -292
package/README.md
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Generate docx documents from templates, in Node or in the browser.
|
|
4
4
|
|
|
5
|
-
[](https://github.com/alonrbar/easy-template-x/actions/workflows/ci.yaml)
|
|
6
6
|
[](https://www.npmjs.com/package/easy-template-x)
|
|
7
7
|
[](https://www.npmjs.com/package/easy-template-x)
|
|
8
|
-
[](https://github.com/alonrbar/easy-template-x)
|
|
9
8
|
|
|
10
9
|
- [Node Example](#node-example)
|
|
11
10
|
- [Browser Example](#browser-example)
|
|
@@ -14,10 +13,12 @@ Generate docx documents from templates, in Node or in the browser.
|
|
|
14
13
|
- [Text plugin](#text-plugin)
|
|
15
14
|
- [Loop plugin](#loop-plugin)
|
|
16
15
|
- [Conditions](#conditions)
|
|
16
|
+
- [Nested Conditions](#nested-conditions)
|
|
17
17
|
- [Image plugin](#image-plugin)
|
|
18
18
|
- [Link plugin](#link-plugin)
|
|
19
19
|
- [Raw xml plugin](#raw-xml-plugin)
|
|
20
20
|
- [Writing custom plugins](#writing-your-own-plugins)
|
|
21
|
+
- [Listing tags](#listing-tags)
|
|
21
22
|
- [Scope resolution](#scope-resolution)
|
|
22
23
|
- [Extensions](#extensions)
|
|
23
24
|
- [Community Extensions](#community-extensions)
|
|
@@ -186,7 +187,7 @@ Output document:
|
|
|
186
187
|
|
|
187
188
|
You can render content conditionally depending on a boolean value using the same syntax used for loops.
|
|
188
189
|
|
|
189
|
-
The example below shows two lines being rendered, each with different content
|
|
190
|
+
The example below shows two lines being rendered, each with different content depending on the truthy value.
|
|
190
191
|
|
|
191
192
|
Input template:
|
|
192
193
|
|
|
@@ -207,7 +208,50 @@ Output document:
|
|
|
207
208
|
|
|
208
209
|

|
|
209
210
|
|
|
210
|
-
|
|
211
|
+
#### Nested Conditions
|
|
212
|
+
|
|
213
|
+
Nested conditions are also supported, so you can nest other tags including loop tags and even other conditions in them. When doing so remember to format your data accordingly. See the example below for clarification:
|
|
214
|
+
|
|
215
|
+
Input template:
|
|
216
|
+
|
|
217
|
+

|
|
218
|
+
|
|
219
|
+
Input data:
|
|
220
|
+
|
|
221
|
+
Notice how even though `name` and `members` are nested in the template under the `show` condition their values are adjacent to it in the input data.
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
{
|
|
225
|
+
"teams": [
|
|
226
|
+
{
|
|
227
|
+
show: true,
|
|
228
|
+
name: "A-Team",
|
|
229
|
+
members: [
|
|
230
|
+
{ name: "Hannibal" },
|
|
231
|
+
{ name: "Face" },
|
|
232
|
+
{ name: "Murdock" },
|
|
233
|
+
{ name: "Baracus" },
|
|
234
|
+
]
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
show: false,
|
|
238
|
+
name: "B-Team",
|
|
239
|
+
members: [
|
|
240
|
+
{ name: "Alice" },
|
|
241
|
+
{ name: "Bob" },
|
|
242
|
+
{ name: "Charlie" },
|
|
243
|
+
{ name: "Dave" },
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Output document:
|
|
251
|
+
|
|
252
|
+

|
|
253
|
+
|
|
254
|
+
_If you are looking for a yet more powerful conditional syntax see the [alternative syntax](#advanced-syntax-and-custom-resolvers) section._
|
|
211
255
|
|
|
212
256
|
### Image plugin
|
|
213
257
|
|
|
@@ -340,6 +384,19 @@ export interface RawXmlContent extends PluginContent {
|
|
|
340
384
|
}
|
|
341
385
|
```
|
|
342
386
|
|
|
387
|
+
## Listing tags
|
|
388
|
+
|
|
389
|
+
You can get the list of [tags](https://github.com/alonrbar/easy-template-x/blob/8a88535ef090fc357cf3523411bef0d0729d10c8/src/compilation/tag.ts) in a template by calling the `parseTags` method as follows:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { TemplateHandler } from 'easy-template-x';
|
|
393
|
+
|
|
394
|
+
const templateFile = fs.readFileSync('myTemplate.docx');
|
|
395
|
+
|
|
396
|
+
const handler = new TemplateHandler();
|
|
397
|
+
const tags = await handler.parseTags(templateFile);
|
|
398
|
+
```
|
|
399
|
+
|
|
343
400
|
## Scope resolution
|
|
344
401
|
|
|
345
402
|
`easy-template-x` supports tag data scoping. That is, you can reference
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var xmldom = require('xmldom');
|
|
6
|
+
var getProp = require('lodash.get');
|
|
6
7
|
var JSZip = require('jszip');
|
|
7
8
|
|
|
9
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
|
+
|
|
8
11
|
function _interopNamespace(e) {
|
|
9
12
|
if (e && e.__esModule) return e;
|
|
10
13
|
var n = Object.create(null);
|
|
@@ -14,18 +17,17 @@ function _interopNamespace(e) {
|
|
|
14
17
|
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
18
|
Object.defineProperty(n, k, d.get ? d : {
|
|
16
19
|
enumerable: true,
|
|
17
|
-
get: function () {
|
|
18
|
-
return e[k];
|
|
19
|
-
}
|
|
20
|
+
get: function () { return e[k]; }
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
|
-
n[
|
|
25
|
+
n["default"] = e;
|
|
25
26
|
return Object.freeze(n);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
var xmldom__namespace = /*#__PURE__*/_interopNamespace(xmldom);
|
|
30
|
+
var getProp__default = /*#__PURE__*/_interopDefaultLegacy(getProp);
|
|
29
31
|
var JSZip__namespace = /*#__PURE__*/_interopNamespace(JSZip);
|
|
30
32
|
|
|
31
33
|
function _defineProperty(obj, key, value) {
|
|
@@ -1118,26 +1120,26 @@ class DelimiterSearcher {
|
|
|
1118
1120
|
}
|
|
1119
1121
|
} // no match
|
|
1120
1122
|
else {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1123
|
+
//
|
|
1124
|
+
// go back to first open node
|
|
1125
|
+
//
|
|
1126
|
+
// Required for cases where the text has repeating
|
|
1127
|
+
// characters that are the same as a delimiter prefix.
|
|
1128
|
+
// For instance:
|
|
1129
|
+
// Delimiter is '{!' and template text contains the string '{{!'
|
|
1130
|
+
//
|
|
1131
|
+
if (match.firstMatchIndex !== -1) {
|
|
1132
|
+
node = first(match.openNodes);
|
|
1133
|
+
textIndex = match.firstMatchIndex;
|
|
1134
|
+
} // update state
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
match.reset();
|
|
1138
|
+
|
|
1139
|
+
if (textIndex < node.textContent.length - 1) {
|
|
1140
|
+
match.openNodes.push(node);
|
|
1140
1141
|
}
|
|
1142
|
+
}
|
|
1141
1143
|
|
|
1142
1144
|
textIndex++;
|
|
1143
1145
|
}
|
|
@@ -1190,8 +1192,6 @@ class DelimiterSearcher {
|
|
|
1190
1192
|
|
|
1191
1193
|
}
|
|
1192
1194
|
|
|
1193
|
-
const getProp = require("lodash.get");
|
|
1194
|
-
|
|
1195
1195
|
class ScopeData {
|
|
1196
1196
|
static defaultResolver(args) {
|
|
1197
1197
|
let result;
|
|
@@ -1200,7 +1200,7 @@ class ScopeData {
|
|
|
1200
1200
|
|
|
1201
1201
|
while (result === undefined && curPath.length) {
|
|
1202
1202
|
curPath.pop();
|
|
1203
|
-
result =
|
|
1203
|
+
result = getProp__default["default"](args.data, curPath.concat(lastKey));
|
|
1204
1204
|
}
|
|
1205
1205
|
|
|
1206
1206
|
return result;
|
|
@@ -2619,9 +2619,9 @@ class LinkPlugin extends TemplatePlugin {
|
|
|
2619
2619
|
}
|
|
2620
2620
|
} // already isolated
|
|
2621
2621
|
else {
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2622
|
+
XmlNode.insertAfter(linkMarkup, tagRunNode);
|
|
2623
|
+
XmlNode.remove(tagRunNode);
|
|
2624
|
+
}
|
|
2625
2625
|
}
|
|
2626
2626
|
|
|
2627
2627
|
}
|
|
@@ -2807,7 +2807,9 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2807
2807
|
async containerTagReplacements(tags, data, context) {
|
|
2808
2808
|
let value = data.getScopeData(); // Non array value - treat as a boolean condition.
|
|
2809
2809
|
|
|
2810
|
-
|
|
2810
|
+
const isCondition = !Array.isArray(value);
|
|
2811
|
+
|
|
2812
|
+
if (isCondition) {
|
|
2811
2813
|
if (!!value) {
|
|
2812
2814
|
value = [{}];
|
|
2813
2815
|
} else {
|
|
@@ -2833,7 +2835,7 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2833
2835
|
// path to each token and use that to create new tokens instead of
|
|
2834
2836
|
// search through the text again)
|
|
2835
2837
|
|
|
2836
|
-
const compiledNodes = await this.compile(repeatedNodes, data, context); // merge back to the document
|
|
2838
|
+
const compiledNodes = await this.compile(isCondition, repeatedNodes, data, context); // merge back to the document
|
|
2837
2839
|
|
|
2838
2840
|
loopStrategy.mergeBack(compiledNodes, firstNode, lastNode);
|
|
2839
2841
|
}
|
|
@@ -2850,7 +2852,7 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2850
2852
|
return allResults;
|
|
2851
2853
|
}
|
|
2852
2854
|
|
|
2853
|
-
async compile(nodeGroups, data, context) {
|
|
2855
|
+
async compile(isCondition, nodeGroups, data, context) {
|
|
2854
2856
|
const compiledNodeGroups = []; // compile each node group with it's relevant data
|
|
2855
2857
|
|
|
2856
2858
|
for (let i = 0; i < nodeGroups.length; i++) {
|
|
@@ -2859,9 +2861,9 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2859
2861
|
const dummyRootNode = XmlNode.createGeneralNode('dummyRootNode');
|
|
2860
2862
|
curNodes.forEach(node => XmlNode.appendChild(dummyRootNode, node)); // compile the new root
|
|
2861
2863
|
|
|
2862
|
-
|
|
2864
|
+
const conditionTag = this.updatePathBefore(isCondition, data, i);
|
|
2863
2865
|
await this.utilities.compiler.compile(dummyRootNode, data, context);
|
|
2864
|
-
|
|
2866
|
+
this.updatePathAfter(isCondition, data, conditionTag); // disconnect from dummy root
|
|
2865
2867
|
|
|
2866
2868
|
const curResult = [];
|
|
2867
2869
|
|
|
@@ -2876,6 +2878,32 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2876
2878
|
return compiledNodeGroups;
|
|
2877
2879
|
}
|
|
2878
2880
|
|
|
2881
|
+
updatePathBefore(isCondition, data, groupIndex) {
|
|
2882
|
+
// if it's a condition - don't go deeper in the path
|
|
2883
|
+
// (so we need to extract the already pushed condition tag)
|
|
2884
|
+
if (isCondition) {
|
|
2885
|
+
if (groupIndex > 0) {
|
|
2886
|
+
// should never happen - conditions should have at most one (synthetic) child...
|
|
2887
|
+
throw new Error(`Internal error: Unexpected group index ${groupIndex} for boolean condition at path "${data.pathString()}".`);
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
return data.pathPop();
|
|
2891
|
+
} // else, it's an array - push the current index
|
|
2892
|
+
|
|
2893
|
+
|
|
2894
|
+
data.pathPush(groupIndex);
|
|
2895
|
+
return null;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
updatePathAfter(isCondition, data, conditionTag) {
|
|
2899
|
+
// reverse the "before" path operation
|
|
2900
|
+
if (isCondition) {
|
|
2901
|
+
data.pathPush(conditionTag);
|
|
2902
|
+
} else {
|
|
2903
|
+
data.pathPop();
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2879
2907
|
}
|
|
2880
2908
|
|
|
2881
2909
|
class RawXmlPlugin extends TemplatePlugin {
|
|
@@ -3280,7 +3308,7 @@ class TemplateHandler {
|
|
|
3280
3308
|
constructor(options) {
|
|
3281
3309
|
var _this$options$extensi, _this$options$extensi2, _this$options$extensi3, _this$options$extensi4;
|
|
3282
3310
|
|
|
3283
|
-
_defineProperty(this, "version", "
|
|
3311
|
+
_defineProperty(this, "version", "3.0.2" );
|
|
3284
3312
|
|
|
3285
3313
|
_defineProperty(this, "xmlParser", new XmlParser());
|
|
3286
3314
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as xmldom from 'xmldom';
|
|
2
|
+
import getProp from 'lodash.get';
|
|
2
3
|
import * as JSZip from 'jszip';
|
|
3
4
|
|
|
4
5
|
function _defineProperty(obj, key, value) {
|
|
@@ -1091,26 +1092,26 @@ class DelimiterSearcher {
|
|
|
1091
1092
|
}
|
|
1092
1093
|
} // no match
|
|
1093
1094
|
else {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
}
|
|
1095
|
+
//
|
|
1096
|
+
// go back to first open node
|
|
1097
|
+
//
|
|
1098
|
+
// Required for cases where the text has repeating
|
|
1099
|
+
// characters that are the same as a delimiter prefix.
|
|
1100
|
+
// For instance:
|
|
1101
|
+
// Delimiter is '{!' and template text contains the string '{{!'
|
|
1102
|
+
//
|
|
1103
|
+
if (match.firstMatchIndex !== -1) {
|
|
1104
|
+
node = first(match.openNodes);
|
|
1105
|
+
textIndex = match.firstMatchIndex;
|
|
1106
|
+
} // update state
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
match.reset();
|
|
1110
|
+
|
|
1111
|
+
if (textIndex < node.textContent.length - 1) {
|
|
1112
|
+
match.openNodes.push(node);
|
|
1113
1113
|
}
|
|
1114
|
+
}
|
|
1114
1115
|
|
|
1115
1116
|
textIndex++;
|
|
1116
1117
|
}
|
|
@@ -1163,8 +1164,6 @@ class DelimiterSearcher {
|
|
|
1163
1164
|
|
|
1164
1165
|
}
|
|
1165
1166
|
|
|
1166
|
-
const getProp = require("lodash.get");
|
|
1167
|
-
|
|
1168
1167
|
class ScopeData {
|
|
1169
1168
|
static defaultResolver(args) {
|
|
1170
1169
|
let result;
|
|
@@ -2592,9 +2591,9 @@ class LinkPlugin extends TemplatePlugin {
|
|
|
2592
2591
|
}
|
|
2593
2592
|
} // already isolated
|
|
2594
2593
|
else {
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2594
|
+
XmlNode.insertAfter(linkMarkup, tagRunNode);
|
|
2595
|
+
XmlNode.remove(tagRunNode);
|
|
2596
|
+
}
|
|
2598
2597
|
}
|
|
2599
2598
|
|
|
2600
2599
|
}
|
|
@@ -2780,7 +2779,9 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2780
2779
|
async containerTagReplacements(tags, data, context) {
|
|
2781
2780
|
let value = data.getScopeData(); // Non array value - treat as a boolean condition.
|
|
2782
2781
|
|
|
2783
|
-
|
|
2782
|
+
const isCondition = !Array.isArray(value);
|
|
2783
|
+
|
|
2784
|
+
if (isCondition) {
|
|
2784
2785
|
if (!!value) {
|
|
2785
2786
|
value = [{}];
|
|
2786
2787
|
} else {
|
|
@@ -2806,7 +2807,7 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2806
2807
|
// path to each token and use that to create new tokens instead of
|
|
2807
2808
|
// search through the text again)
|
|
2808
2809
|
|
|
2809
|
-
const compiledNodes = await this.compile(repeatedNodes, data, context); // merge back to the document
|
|
2810
|
+
const compiledNodes = await this.compile(isCondition, repeatedNodes, data, context); // merge back to the document
|
|
2810
2811
|
|
|
2811
2812
|
loopStrategy.mergeBack(compiledNodes, firstNode, lastNode);
|
|
2812
2813
|
}
|
|
@@ -2823,7 +2824,7 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2823
2824
|
return allResults;
|
|
2824
2825
|
}
|
|
2825
2826
|
|
|
2826
|
-
async compile(nodeGroups, data, context) {
|
|
2827
|
+
async compile(isCondition, nodeGroups, data, context) {
|
|
2827
2828
|
const compiledNodeGroups = []; // compile each node group with it's relevant data
|
|
2828
2829
|
|
|
2829
2830
|
for (let i = 0; i < nodeGroups.length; i++) {
|
|
@@ -2832,9 +2833,9 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2832
2833
|
const dummyRootNode = XmlNode.createGeneralNode('dummyRootNode');
|
|
2833
2834
|
curNodes.forEach(node => XmlNode.appendChild(dummyRootNode, node)); // compile the new root
|
|
2834
2835
|
|
|
2835
|
-
|
|
2836
|
+
const conditionTag = this.updatePathBefore(isCondition, data, i);
|
|
2836
2837
|
await this.utilities.compiler.compile(dummyRootNode, data, context);
|
|
2837
|
-
|
|
2838
|
+
this.updatePathAfter(isCondition, data, conditionTag); // disconnect from dummy root
|
|
2838
2839
|
|
|
2839
2840
|
const curResult = [];
|
|
2840
2841
|
|
|
@@ -2849,6 +2850,32 @@ class LoopPlugin extends TemplatePlugin {
|
|
|
2849
2850
|
return compiledNodeGroups;
|
|
2850
2851
|
}
|
|
2851
2852
|
|
|
2853
|
+
updatePathBefore(isCondition, data, groupIndex) {
|
|
2854
|
+
// if it's a condition - don't go deeper in the path
|
|
2855
|
+
// (so we need to extract the already pushed condition tag)
|
|
2856
|
+
if (isCondition) {
|
|
2857
|
+
if (groupIndex > 0) {
|
|
2858
|
+
// should never happen - conditions should have at most one (synthetic) child...
|
|
2859
|
+
throw new Error(`Internal error: Unexpected group index ${groupIndex} for boolean condition at path "${data.pathString()}".`);
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
return data.pathPop();
|
|
2863
|
+
} // else, it's an array - push the current index
|
|
2864
|
+
|
|
2865
|
+
|
|
2866
|
+
data.pathPush(groupIndex);
|
|
2867
|
+
return null;
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
updatePathAfter(isCondition, data, conditionTag) {
|
|
2871
|
+
// reverse the "before" path operation
|
|
2872
|
+
if (isCondition) {
|
|
2873
|
+
data.pathPush(conditionTag);
|
|
2874
|
+
} else {
|
|
2875
|
+
data.pathPop();
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2852
2879
|
}
|
|
2853
2880
|
|
|
2854
2881
|
class RawXmlPlugin extends TemplatePlugin {
|
|
@@ -3253,7 +3280,7 @@ class TemplateHandler {
|
|
|
3253
3280
|
constructor(options) {
|
|
3254
3281
|
var _this$options$extensi, _this$options$extensi2, _this$options$extensi3, _this$options$extensi4;
|
|
3255
3282
|
|
|
3256
|
-
_defineProperty(this, "version", "
|
|
3283
|
+
_defineProperty(this, "version", "3.0.2" );
|
|
3257
3284
|
|
|
3258
3285
|
_defineProperty(this, "xmlParser", new XmlParser());
|
|
3259
3286
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "easy-template-x",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Generate docx documents from templates, in Node or in the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docx",
|
|
@@ -39,39 +39,39 @@
|
|
|
39
39
|
"release": "yarn clean && yarn quality && yarn build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"jszip": "3.
|
|
42
|
+
"jszip": "3.10.0",
|
|
43
43
|
"lodash.get": "4.4.2",
|
|
44
|
-
"xmldom": "0.
|
|
44
|
+
"xmldom": "0.6.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@babel/core": "7.
|
|
48
|
-
"@babel/plugin-proposal-class-properties": "7.
|
|
49
|
-
"@babel/plugin-proposal-nullish-coalescing-operator": "7.
|
|
50
|
-
"@babel/plugin-proposal-object-rest-spread": "7.
|
|
51
|
-
"@babel/plugin-proposal-optional-catch-binding": "7.
|
|
52
|
-
"@babel/plugin-proposal-optional-chaining": "7.
|
|
53
|
-
"@babel/plugin-transform-modules-commonjs": "7.
|
|
54
|
-
"@babel/preset-typescript": "7.
|
|
55
|
-
"@rollup/plugin-replace": "
|
|
56
|
-
"@types/jest": "
|
|
57
|
-
"@types/jszip": "3.4.
|
|
58
|
-
"@types/node": "
|
|
47
|
+
"@babel/core": "7.18.6",
|
|
48
|
+
"@babel/plugin-proposal-class-properties": "7.18.6",
|
|
49
|
+
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
|
50
|
+
"@babel/plugin-proposal-object-rest-spread": "7.18.6",
|
|
51
|
+
"@babel/plugin-proposal-optional-catch-binding": "7.18.6",
|
|
52
|
+
"@babel/plugin-proposal-optional-chaining": "7.18.6",
|
|
53
|
+
"@babel/plugin-transform-modules-commonjs": "7.18.6",
|
|
54
|
+
"@babel/preset-typescript": "7.18.6",
|
|
55
|
+
"@rollup/plugin-replace": "4.0.0",
|
|
56
|
+
"@types/jest": "28.1.4",
|
|
57
|
+
"@types/jszip": "3.4.1",
|
|
58
|
+
"@types/node": "18.0.1",
|
|
59
59
|
"@types/ts-nameof": "4.2.1",
|
|
60
|
-
"@types/xmldom": "0.1.
|
|
61
|
-
"@typescript-eslint/eslint-plugin": "
|
|
62
|
-
"@typescript-eslint/parser": "
|
|
63
|
-
"babel-jest": "
|
|
64
|
-
"babel-loader": "8.2.
|
|
60
|
+
"@types/xmldom": "0.1.31",
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "5.30.4",
|
|
62
|
+
"@typescript-eslint/parser": "5.30.4",
|
|
63
|
+
"babel-jest": "28.1.2",
|
|
64
|
+
"babel-loader": "8.2.5",
|
|
65
65
|
"babel-plugin-ts-nameof": "4.2.1",
|
|
66
|
-
"eslint": "
|
|
67
|
-
"jest": "
|
|
68
|
-
"jest-junit": "
|
|
69
|
-
"lorem-ipsum": "2.0.
|
|
66
|
+
"eslint": "8.19.0",
|
|
67
|
+
"jest": "28.1.2",
|
|
68
|
+
"jest-junit": "14.0.0",
|
|
69
|
+
"lorem-ipsum": "2.0.8",
|
|
70
70
|
"rimraf": "3.0.2",
|
|
71
|
-
"rollup": "2.
|
|
71
|
+
"rollup": "2.75.7",
|
|
72
72
|
"rollup-plugin-auto-external": "2.0.0",
|
|
73
|
-
"rollup-plugin-babel": "4.
|
|
73
|
+
"rollup-plugin-babel": "4.4.0",
|
|
74
74
|
"rollup-plugin-node-resolve": "5.2.0",
|
|
75
|
-
"typescript": "4.
|
|
75
|
+
"typescript": "4.7.4"
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// https://lodash.com/docs/4.17.15#get
|
|
2
|
+
module "lodash.get" {
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Gets the value at path of object. If the resolved value is undefined, the
|
|
6
|
+
* defaultValue is returned in its place.
|
|
7
|
+
*/
|
|
8
|
+
function get(object: any, path: string | string[]): any;
|
|
9
|
+
function get(object: any, path: string | string[], defaultValue: any): any;
|
|
10
|
+
|
|
11
|
+
export default get;
|
|
12
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import getProp from 'lodash.get';
|
|
1
2
|
import { TemplateContent, TemplateData } from '../templateData';
|
|
2
3
|
import { isNumber, last } from '../utils';
|
|
3
4
|
import { Tag } from './tag';
|
|
4
5
|
|
|
5
|
-
const getProp = require("lodash.get");
|
|
6
|
-
|
|
7
6
|
export type PathPart = Tag | number;
|
|
8
7
|
|
|
9
8
|
export interface ScopeDataArgs {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ScopeData, Tag, TemplateContext } from '../../compilation';
|
|
1
|
+
import { PathPart, ScopeData, Tag, TemplateContext } from '../../compilation';
|
|
2
2
|
import { TemplateData } from '../../templateData';
|
|
3
3
|
import { last } from '../../utils';
|
|
4
4
|
import { XmlNode } from '../../xml';
|
|
@@ -27,7 +27,8 @@ export class LoopPlugin extends TemplatePlugin {
|
|
|
27
27
|
let value = data.getScopeData<TemplateData[]>();
|
|
28
28
|
|
|
29
29
|
// Non array value - treat as a boolean condition.
|
|
30
|
-
|
|
30
|
+
const isCondition = !Array.isArray(value);
|
|
31
|
+
if (isCondition) {
|
|
31
32
|
if (!!value) {
|
|
32
33
|
value = [{}];
|
|
33
34
|
} else {
|
|
@@ -54,7 +55,7 @@ export class LoopPlugin extends TemplatePlugin {
|
|
|
54
55
|
// (this step can be optimized in the future if we'll keep track of the
|
|
55
56
|
// path to each token and use that to create new tokens instead of
|
|
56
57
|
// search through the text again)
|
|
57
|
-
const compiledNodes = await this.compile(repeatedNodes, data, context);
|
|
58
|
+
const compiledNodes = await this.compile(isCondition, repeatedNodes, data, context);
|
|
58
59
|
|
|
59
60
|
// merge back to the document
|
|
60
61
|
loopStrategy.mergeBack(compiledNodes, firstNode, lastNode);
|
|
@@ -74,7 +75,7 @@ export class LoopPlugin extends TemplatePlugin {
|
|
|
74
75
|
return allResults;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
private async compile(nodeGroups: XmlNode[][], data: ScopeData, context: TemplateContext): Promise<XmlNode[][]> {
|
|
78
|
+
private async compile(isCondition: boolean, nodeGroups: XmlNode[][], data: ScopeData, context: TemplateContext): Promise<XmlNode[][]> {
|
|
78
79
|
const compiledNodeGroups: XmlNode[][] = [];
|
|
79
80
|
|
|
80
81
|
// compile each node group with it's relevant data
|
|
@@ -86,9 +87,9 @@ export class LoopPlugin extends TemplatePlugin {
|
|
|
86
87
|
curNodes.forEach(node => XmlNode.appendChild(dummyRootNode, node));
|
|
87
88
|
|
|
88
89
|
// compile the new root
|
|
89
|
-
|
|
90
|
+
const conditionTag = this.updatePathBefore(isCondition, data, i);
|
|
90
91
|
await this.utilities.compiler.compile(dummyRootNode, data, context);
|
|
91
|
-
|
|
92
|
+
this.updatePathAfter(isCondition, data, conditionTag);
|
|
92
93
|
|
|
93
94
|
// disconnect from dummy root
|
|
94
95
|
const curResult: XmlNode[] = [];
|
|
@@ -101,4 +102,32 @@ export class LoopPlugin extends TemplatePlugin {
|
|
|
101
102
|
|
|
102
103
|
return compiledNodeGroups;
|
|
103
104
|
}
|
|
105
|
+
|
|
106
|
+
private updatePathBefore(isCondition: boolean, data: ScopeData, groupIndex: number): PathPart {
|
|
107
|
+
|
|
108
|
+
// if it's a condition - don't go deeper in the path
|
|
109
|
+
// (so we need to extract the already pushed condition tag)
|
|
110
|
+
if (isCondition) {
|
|
111
|
+
if (groupIndex > 0) {
|
|
112
|
+
// should never happen - conditions should have at most one (synthetic) child...
|
|
113
|
+
throw new Error(`Internal error: Unexpected group index ${groupIndex} for boolean condition at path "${data.pathString()}".`);
|
|
114
|
+
}
|
|
115
|
+
return data.pathPop();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// else, it's an array - push the current index
|
|
119
|
+
data.pathPush(groupIndex);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private updatePathAfter(isCondition: boolean, data: ScopeData, conditionTag: PathPart): void {
|
|
124
|
+
|
|
125
|
+
// reverse the "before" path operation
|
|
126
|
+
if (isCondition) {
|
|
127
|
+
data.pathPush(conditionTag);
|
|
128
|
+
} else {
|
|
129
|
+
data.pathPop();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
104
132
|
}
|
|
133
|
+
|
package/CHANGELOG.md
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
# Change Log
|
|
2
|
-
|
|
3
|
-
## [2.1.0 - 2021-07-29](https://github.com/alonrbar/easy-template-x/tree/v2.1.0)
|
|
4
|
-
|
|
5
|
-
### Added
|
|
6
|
-
|
|
7
|
-
- Add skipEmptyTag option ([#45](https://github.com/alonrbar/easy-template-x/issues/45)).
|
|
8
|
-
|
|
9
|
-
## [2.0.0 - 2021-03-19](https://github.com/alonrbar/easy-template-x/tree/v2.0.0)
|
|
10
|
-
|
|
11
|
-
### Added
|
|
12
|
-
|
|
13
|
-
- Support for simple conditions ([docs](https://github.com/alonrbar/easy-template-x#conditions)).
|
|
14
|
-
- Support for custom data resolvers - enables advanced syntax support ([docs](https://github.com/alonrbar/easy-template-x#advanced-syntax-and-custom-resolvers)).
|
|
15
|
-
|
|
16
|
-
### Changed
|
|
17
|
-
|
|
18
|
-
- **BREAKING** - Container closing tag name is ignored and no longer throws when
|
|
19
|
-
the closing tag has different name than the opening one ([docs](https://github.com/alonrbar/easy-template-x#loop-plugin)).
|
|
20
|
-
|
|
21
|
-
## [1.0.1 - 2020-09-25](https://github.com/alonrbar/easy-template-x/tree/v1.0.1)
|
|
22
|
-
|
|
23
|
-
### Changed
|
|
24
|
-
|
|
25
|
-
- Update dependencies (jszip, xmldom).
|
|
26
|
-
|
|
27
|
-
## [1.0.0 - 2020-08-09](https://github.com/alonrbar/easy-template-x/tree/v1.0.0)
|
|
28
|
-
|
|
29
|
-
**Stable release** - from now on breaking changes to the public API (the public
|
|
30
|
-
interface of `TemplateHandler`) will introduce a new major release.
|
|
31
|
-
|
|
32
|
-
### Fixed
|
|
33
|
-
|
|
34
|
-
- Initial content types parsing.
|
|
35
|
-
- Bug in paragraph loops (#36).
|
|
36
|
-
|
|
37
|
-
## [0.12.0 - 2020-08-01](https://github.com/alonrbar/easy-template-x/tree/v0.12.0)
|
|
38
|
-
|
|
39
|
-
### Added
|
|
40
|
-
|
|
41
|
-
- Headers and footers support.
|
|
42
|
-
|
|
43
|
-
## [0.11.1 - 2020-03-29](https://github.com/alonrbar/easy-template-x/tree/v0.11.1)
|
|
44
|
-
|
|
45
|
-
### Fixed
|
|
46
|
-
|
|
47
|
-
- Consistent handling of `RawXmlContent` when the `xml` prop is null.
|
|
48
|
-
|
|
49
|
-
## [0.11.0 - 2020-03-29](https://github.com/alonrbar/easy-template-x/tree/v0.11.0)
|
|
50
|
-
|
|
51
|
-
### Added
|
|
52
|
-
|
|
53
|
-
- Support for `RawXmlContent.replaceParagraph`.
|
|
54
|
-
|
|
55
|
-
## [0.10.4 - 2020-03-02](https://github.com/alonrbar/easy-template-x/tree/v0.10.4)
|
|
56
|
-
|
|
57
|
-
### Added
|
|
58
|
-
|
|
59
|
-
- Expose "Community Extensions" on npm (readme changes).
|
|
60
|
-
|
|
61
|
-
## [0.10.3 - 2020-02-16](https://github.com/alonrbar/easy-template-x/tree/v0.10.3)
|
|
62
|
-
|
|
63
|
-
### Fixed
|
|
64
|
-
|
|
65
|
-
- Parsing of tags with custom delimiters.
|
|
66
|
-
|
|
67
|
-
## [0.10.2 - 2020-02-16](https://github.com/alonrbar/easy-template-x/tree/v0.10.2)
|
|
68
|
-
|
|
69
|
-
### Fixed
|
|
70
|
-
|
|
71
|
-
- Parsing of tags with custom delimiters.
|
|
72
|
-
|
|
73
|
-
## [0.10.1 - 2020-02-12](https://github.com/alonrbar/easy-template-x/tree/v0.10.1)
|
|
74
|
-
|
|
75
|
-
### Fixed
|
|
76
|
-
|
|
77
|
-
- Export extensions types.
|
|
78
|
-
|
|
79
|
-
## [0.10.0 - 2020-02-10](https://github.com/alonrbar/easy-template-x/tree/v0.10.0)
|
|
80
|
-
|
|
81
|
-
### Added
|
|
82
|
-
|
|
83
|
-
- Expose `Docx.rawZipFile` property.
|
|
84
|
-
|
|
85
|
-
## [0.9.0 - 2020-02-10](https://github.com/alonrbar/easy-template-x/tree/v0.9.0)
|
|
86
|
-
|
|
87
|
-
### Added
|
|
88
|
-
|
|
89
|
-
- Extensions API (#24).
|
|
90
|
-
|
|
91
|
-
## [0.8.3 - 2019-12-27](https://github.com/alonrbar/easy-template-x/tree/v0.8.3)
|
|
92
|
-
|
|
93
|
-
### Added
|
|
94
|
-
|
|
95
|
-
- Allow overriding container tag logic using explicit content type.
|
|
96
|
-
|
|
97
|
-
## [0.8.2 - 2019-11-30](https://github.com/alonrbar/easy-template-x/tree/v0.8.2)
|
|
98
|
-
|
|
99
|
-
### Changed
|
|
100
|
-
|
|
101
|
-
- `ScopeData.getScopeData` is now generic (#17).
|
|
102
|
-
- The `data` argument of `TemplateHandler.process` is now strongly typed.
|
|
103
|
-
- Bundle with Rollup instead of Webpack.
|
|
104
|
-
- Auto generate typings.
|
|
105
|
-
|
|
106
|
-
## [0.8.1 - 2019-11-02](https://github.com/alonrbar/easy-template-x/tree/v0.8.1)
|
|
107
|
-
|
|
108
|
-
### Fixed
|
|
109
|
-
|
|
110
|
-
- Fix typings.
|
|
111
|
-
|
|
112
|
-
## [0.8.0 - 2019-10-20](https://github.com/alonrbar/easy-template-x/tree/v0.8.0)
|
|
113
|
-
|
|
114
|
-
### Changed
|
|
115
|
-
|
|
116
|
-
- **BREAKING**: Delimiters can not contain leading or trailing whitespace.
|
|
117
|
-
- Loosen up `TemplateHandlerOptions` typings.
|
|
118
|
-
|
|
119
|
-
### Fixed
|
|
120
|
-
|
|
121
|
-
- Loop tag names trimming.
|
|
122
|
-
- Custom loop delimiters support.
|
|
123
|
-
- Zip export and typings.
|
|
124
|
-
|
|
125
|
-
## [0.7.3 - 2019-10-11](https://github.com/alonrbar/easy-template-x/tree/v0.7.3)
|
|
126
|
-
|
|
127
|
-
### Added
|
|
128
|
-
|
|
129
|
-
- Link to [live demo](https://codesandbox.io/s/easy-template-x-demo-x4ppu?fontsize=14&module=%2Findex.ts) on CodeSandbox.
|
|
130
|
-
|
|
131
|
-
## [0.7.2 - 2019-10-10](https://github.com/alonrbar/easy-template-x/tree/v0.7.2)
|
|
132
|
-
|
|
133
|
-
### Fixed
|
|
134
|
-
|
|
135
|
-
- Re-fix "Binary type 'Buffer' is not supported" on Node.
|
|
136
|
-
|
|
137
|
-
## [0.7.1 - 2019-10-03](https://github.com/alonrbar/easy-template-x/tree/v0.7.1)
|
|
138
|
-
|
|
139
|
-
### Fixed
|
|
140
|
-
|
|
141
|
-
- Link plugin in cases where the link tag is not the only node in it's run.
|
|
142
|
-
|
|
143
|
-
## [0.7.0 - 2019-10-02](https://github.com/alonrbar/easy-template-x/tree/v0.7.0)
|
|
144
|
-
|
|
145
|
-
### Added
|
|
146
|
-
|
|
147
|
-
- Link plugin.
|
|
148
|
-
- `TemplateHandler.version` property.
|
|
149
|
-
|
|
150
|
-
## [0.6.0 - 2019-09-29](https://github.com/alonrbar/easy-template-x/tree/v0.6.0)
|
|
151
|
-
|
|
152
|
-
This version removes the notion of a "tag type" and uses instead the notion of "content type". Instead of inferring the type from the tag _prefix_ the type is now explicitly declared in the supplied JSON _data_.
|
|
153
|
-
|
|
154
|
-
**Example:**
|
|
155
|
-
|
|
156
|
-
_Before:_
|
|
157
|
-
|
|
158
|
-
```text
|
|
159
|
-
tag: "{@newPage}"
|
|
160
|
-
data: {
|
|
161
|
-
newPage: "<w:br w:type="page"/>"
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
_After:_
|
|
166
|
-
|
|
167
|
-
```text
|
|
168
|
-
tag: "{newPage}"
|
|
169
|
-
data: {
|
|
170
|
-
newPage: {
|
|
171
|
-
_type: "rawXml",
|
|
172
|
-
xml: "<w:br w:type="page"/>"
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
The only exceptions are the "loop" content type which still uses the "#" opening prefix and "/" closing prefix, and the "text" content type which is the default and does not requires explicitly stating it.
|
|
178
|
-
|
|
179
|
-
### Added
|
|
180
|
-
|
|
181
|
-
- Image plugin.
|
|
182
|
-
- Support multi-character delimiters.
|
|
183
|
-
- Template plugins can be async.
|
|
184
|
-
- Improved the docs (readme).
|
|
185
|
-
|
|
186
|
-
### Changed
|
|
187
|
-
|
|
188
|
-
- **BREAKING**: `RawXmlPlugin` requires data of the form `{ _type: 'rawXml', xml: string }`.
|
|
189
|
-
|
|
190
|
-
### Removed
|
|
191
|
-
|
|
192
|
-
- **BREAKING**: Remove the `Tag.type` property.
|
|
193
|
-
|
|
194
|
-
### Fixed
|
|
195
|
-
|
|
196
|
-
- Parsing error in some cases where multiple tags are declared in the same run.
|
|
197
|
-
|
|
198
|
-
## [0.5.2 - 2019-09-11](https://github.com/alonrbar/easy-template-x/tree/v0.5.2)
|
|
199
|
-
|
|
200
|
-
### Fixed
|
|
201
|
-
|
|
202
|
-
- "Binary type 'Buffer' is not supported." on Node 12.
|
|
203
|
-
|
|
204
|
-
## [0.5.1 - 2019-06-05](https://github.com/alonrbar/easy-template-x/tree/v0.5.1)
|
|
205
|
-
|
|
206
|
-
### Fixed
|
|
207
|
-
|
|
208
|
-
- Handle non-textual values (numbers, booleans...) in TextPlugin.
|
|
209
|
-
|
|
210
|
-
## [0.5.0 - 2019-05-07](https://github.com/alonrbar/easy-template-x/tree/v0.5.0)
|
|
211
|
-
|
|
212
|
-
### Added
|
|
213
|
-
|
|
214
|
-
- Loop over lists and table rows.
|
|
215
|
-
**Notice**:
|
|
216
|
-
The loop logic for tables is a bit different than the logic of the existing
|
|
217
|
-
paragraph loop. Instead of repeating the content in between the opening and
|
|
218
|
-
closing tags it repeats entire rows (including content in the row that
|
|
219
|
-
appears before the opening or after the closing tag). The same goes for
|
|
220
|
-
lists - the entire bullet is repeated.
|
|
221
|
-
- Throw MalformedFileError when fails to open template file as zip.
|
|
222
|
-
- Continuous integration with CircleCI.
|
|
223
|
-
|
|
224
|
-
### Changed
|
|
225
|
-
|
|
226
|
-
- Change dev stack to Babel, Jest and ESLint.
|
|
227
|
-
|
|
228
|
-
## [0.4.0 - 2018-12-13](https://github.com/alonrbar/easy-template-x/tree/v0.4.0)
|
|
229
|
-
|
|
230
|
-
### Added
|
|
231
|
-
|
|
232
|
-
- Easily find out what tags are present in a given template (TemplateHandler.parseTags).
|
|
233
|
-
|
|
234
|
-
## [0.3.4 - 2018-12-09](https://github.com/alonrbar/easy-template-x/tree/v0.3.4)
|
|
235
|
-
|
|
236
|
-
### Added
|
|
237
|
-
|
|
238
|
-
- Full browser example in readme file.
|
|
239
|
-
|
|
240
|
-
## [0.3.3 - 2018-07-17](https://github.com/alonrbar/easy-template-x/tree/v0.3.3)
|
|
241
|
-
|
|
242
|
-
### Added
|
|
243
|
-
|
|
244
|
-
- Package keywords for npm visibility.
|
|
245
|
-
|
|
246
|
-
## [0.3.2 - 2018-06-22](https://github.com/alonrbar/easy-template-x/tree/v0.3.2)
|
|
247
|
-
|
|
248
|
-
### Fixed
|
|
249
|
-
|
|
250
|
-
- Fix serialization of text nodes with empty values.
|
|
251
|
-
|
|
252
|
-
## [0.3.1 - 2018-06-13](https://github.com/alonrbar/easy-template-x/tree/v0.3.1)
|
|
253
|
-
|
|
254
|
-
### Added
|
|
255
|
-
|
|
256
|
-
- Add readme badges
|
|
257
|
-
|
|
258
|
-
## [0.3.0 - 2018-06-13](https://github.com/alonrbar/easy-template-x/tree/v0.3.0)
|
|
259
|
-
|
|
260
|
-
### Added
|
|
261
|
-
|
|
262
|
-
- Preserve leading and trailing whitespace
|
|
263
|
-
- More info on missing delimiter errors
|
|
264
|
-
|
|
265
|
-
## [0.2.1 - 2018-06-12](https://github.com/alonrbar/easy-template-x/tree/v0.2.1)
|
|
266
|
-
|
|
267
|
-
### Fixed
|
|
268
|
-
|
|
269
|
-
- Various bug fixes
|
|
270
|
-
|
|
271
|
-
## [0.2.0 - 2018-06-12](https://github.com/alonrbar/easy-template-x/tree/v0.2.0)
|
|
272
|
-
|
|
273
|
-
### Added
|
|
274
|
-
|
|
275
|
-
- Typings file
|
|
276
|
-
|
|
277
|
-
## [0.1.0 - 2018-06-12](https://github.com/alonrbar/easy-template-x/tree/v0.1.0)
|
|
278
|
-
|
|
279
|
-
- First version
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
|
-
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
284
|
-
|
|
285
|
-
#### [Types of changes](http://keepachangelog.com)
|
|
286
|
-
|
|
287
|
-
- **Added** for new features.
|
|
288
|
-
- **Changed** for changes in existing functionality.
|
|
289
|
-
- **Deprecated** for soon-to-be removed features.
|
|
290
|
-
- **Removed** for now removed features.
|
|
291
|
-
- **Fixed** for any bug fixes.
|
|
292
|
-
- **Security** in case of vulnerabilities.
|