easy-template-x 7.1.1 → 7.2.0
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 +17 -1
- package/dist/cjs/easy-template-x.cjs +71 -22
- package/dist/es/easy-template-x.mjs +71 -22
- package/dist/types/plugins/loop/loopTagOptions.d.ts +1 -0
- package/dist/types/plugins/loop/strategy/index.d.ts +1 -0
- package/dist/types/plugins/loop/strategy/loopContentStrategy.d.ts +8 -0
- package/dist/types/plugins/loop/strategy/loopParagraphStrategy.d.ts +1 -1
- package/package.json +1 -1
- package/src/plugins/loop/loopPlugin.ts +3 -2
- package/src/plugins/loop/loopTagOptions.ts +6 -2
- package/src/plugins/loop/strategy/index.ts +1 -0
- package/src/plugins/loop/strategy/{loopParagraphStrategy.tests.ts → loopContentStrategy.tests.ts} +4 -4
- package/src/plugins/loop/strategy/loopContentStrategy.ts +76 -0
- package/src/plugins/loop/strategy/loopParagraphStrategy.ts +24 -50
package/README.md
CHANGED
|
@@ -292,7 +292,7 @@ The default heuristics are as follows:
|
|
|
292
292
|
##### Changing the default
|
|
293
293
|
|
|
294
294
|
To use a different behavior than the default one you can use the `loopOver`
|
|
295
|
-
option. Supported values are: `row`, `column` and `content`.
|
|
295
|
+
option. Supported values are: `row`, `column`, `paragraph` and `content`.
|
|
296
296
|
|
|
297
297
|
**Note:** This option controls conditions too.
|
|
298
298
|
|
|
@@ -331,6 +331,22 @@ And the third will produce this document:
|
|
|
331
331
|
|
|
332
332
|

|
|
333
333
|
|
|
334
|
+
For paragraphs, you can choose between this syntax (same as the default behavior):
|
|
335
|
+
|
|
336
|
+

|
|
337
|
+
|
|
338
|
+
And this syntax:
|
|
339
|
+
|
|
340
|
+

|
|
341
|
+
|
|
342
|
+
The first will produce this document:
|
|
343
|
+
|
|
344
|
+

|
|
345
|
+
|
|
346
|
+
And the second will produce this document:
|
|
347
|
+
|
|
348
|
+

|
|
349
|
+
|
|
334
350
|
### Image plugin
|
|
335
351
|
|
|
336
352
|
Embed images into the document.
|
|
@@ -3361,7 +3361,66 @@ class LoopListStrategy {
|
|
|
3361
3361
|
}
|
|
3362
3362
|
}
|
|
3363
3363
|
|
|
3364
|
+
const LoopOver = Object.freeze({
|
|
3365
|
+
/**
|
|
3366
|
+
* Loop over the entire table row.
|
|
3367
|
+
*/
|
|
3368
|
+
Row: 'row',
|
|
3369
|
+
/**
|
|
3370
|
+
* Loop over the entire table column.
|
|
3371
|
+
*/
|
|
3372
|
+
Column: 'column',
|
|
3373
|
+
/**
|
|
3374
|
+
* Loop over the entire paragraph.
|
|
3375
|
+
*/
|
|
3376
|
+
Paragraph: 'paragraph',
|
|
3377
|
+
/**
|
|
3378
|
+
* Loop over the content enclosed between the opening and closing tag.
|
|
3379
|
+
*/
|
|
3380
|
+
Content: 'content'
|
|
3381
|
+
});
|
|
3382
|
+
|
|
3364
3383
|
class LoopParagraphStrategy {
|
|
3384
|
+
isApplicable(openTag, closeTag, isCondition) {
|
|
3385
|
+
const options = openTag.options;
|
|
3386
|
+
return options?.loopOver === LoopOver.Paragraph;
|
|
3387
|
+
}
|
|
3388
|
+
splitBefore(openTag, closeTag) {
|
|
3389
|
+
const firstParagraph = officeMarkup.query.containingParagraphNode(openTag.xmlTextNode);
|
|
3390
|
+
const lastParagraph = officeMarkup.query.containingParagraphNode(closeTag.xmlTextNode);
|
|
3391
|
+
const paragraphsToRepeat = xml.query.siblingsInRange(firstParagraph, lastParagraph);
|
|
3392
|
+
|
|
3393
|
+
// Remove the loop tags.
|
|
3394
|
+
xml.modify.remove(openTag.xmlTextNode);
|
|
3395
|
+
xml.modify.remove(closeTag.xmlTextNode);
|
|
3396
|
+
return {
|
|
3397
|
+
firstNode: firstParagraph,
|
|
3398
|
+
nodesToRepeat: paragraphsToRepeat,
|
|
3399
|
+
lastNode: lastParagraph
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
mergeBack(newParagraphs, firstParagraph, lastParagraph) {
|
|
3403
|
+
// Add new paragraphs to the document.
|
|
3404
|
+
let insertAfter = lastParagraph;
|
|
3405
|
+
for (const curParagraphsGroup of newParagraphs) {
|
|
3406
|
+
for (const paragraph of curParagraphsGroup) {
|
|
3407
|
+
xml.modify.insertAfter(paragraph, insertAfter);
|
|
3408
|
+
insertAfter = paragraph;
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
// Remove old paragraphs - between first and last paragraph.
|
|
3413
|
+
xml.modify.removeSiblings(firstParagraph, lastParagraph);
|
|
3414
|
+
|
|
3415
|
+
// Remove old paragraphs - first and last.
|
|
3416
|
+
xml.modify.remove(firstParagraph);
|
|
3417
|
+
if (firstParagraph !== lastParagraph) {
|
|
3418
|
+
xml.modify.remove(lastParagraph);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
class LoopContentStrategy {
|
|
3365
3424
|
isApplicable(openTag, closeTag, isCondition) {
|
|
3366
3425
|
return true;
|
|
3367
3426
|
}
|
|
@@ -3423,21 +3482,6 @@ class LoopParagraphStrategy {
|
|
|
3423
3482
|
}
|
|
3424
3483
|
}
|
|
3425
3484
|
|
|
3426
|
-
const LoopOver = Object.freeze({
|
|
3427
|
-
/**
|
|
3428
|
-
* Loop over the entire row.
|
|
3429
|
-
*/
|
|
3430
|
-
Row: 'row',
|
|
3431
|
-
/**
|
|
3432
|
-
* Loop over the entire column.
|
|
3433
|
-
*/
|
|
3434
|
-
Column: 'column',
|
|
3435
|
-
/**
|
|
3436
|
-
* Loop over the content enclosed between the opening and closing tag.
|
|
3437
|
-
*/
|
|
3438
|
-
Content: 'content'
|
|
3439
|
-
});
|
|
3440
|
-
|
|
3441
3485
|
class LoopTableColumnsStrategy {
|
|
3442
3486
|
isApplicable(openTag, closeTag, isCondition) {
|
|
3443
3487
|
const openCell = officeMarkup.query.containingTableCellNode(openTag.xmlTextNode);
|
|
@@ -3633,7 +3677,7 @@ class LoopTableRowsStrategy {
|
|
|
3633
3677
|
const LOOP_CONTENT_TYPE = 'loop';
|
|
3634
3678
|
class LoopPlugin extends TemplatePlugin {
|
|
3635
3679
|
contentType = LOOP_CONTENT_TYPE;
|
|
3636
|
-
loopStrategies = [new LoopTableColumnsStrategy(), new LoopTableRowsStrategy(), new LoopListStrategy(), new
|
|
3680
|
+
loopStrategies = [new LoopParagraphStrategy(), new LoopTableColumnsStrategy(), new LoopTableRowsStrategy(), new LoopListStrategy(), new LoopContentStrategy() // the default strategy
|
|
3637
3681
|
];
|
|
3638
3682
|
setUtilities(utilities) {
|
|
3639
3683
|
this.utilities = utilities;
|
|
@@ -3803,7 +3847,7 @@ class TextPlugin extends TemplatePlugin {
|
|
|
3803
3847
|
if (lines.length < 2) {
|
|
3804
3848
|
this.replaceSingleLine(tag, lines.length ? lines[0] : '');
|
|
3805
3849
|
} else {
|
|
3806
|
-
this.replaceMultiLine(tag
|
|
3850
|
+
this.replaceMultiLine(tag, lines);
|
|
3807
3851
|
}
|
|
3808
3852
|
}
|
|
3809
3853
|
replaceInAttribute(tag, text) {
|
|
@@ -3831,14 +3875,14 @@ class TextPlugin extends TemplatePlugin {
|
|
|
3831
3875
|
const wordTextNode = officeMarkup.query.containingTextNode(textNode);
|
|
3832
3876
|
officeMarkup.modify.setSpacePreserveAttribute(wordTextNode);
|
|
3833
3877
|
}
|
|
3834
|
-
replaceMultiLine(
|
|
3878
|
+
replaceMultiLine(tag, lines) {
|
|
3879
|
+
const textNode = tag.xmlTextNode;
|
|
3835
3880
|
const runNode = officeMarkup.query.containingRunNode(textNode);
|
|
3836
3881
|
const namespace = runNode.nodeName.split(':')[0];
|
|
3837
3882
|
|
|
3838
3883
|
// First line
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
}
|
|
3884
|
+
const firstLine = lines[0];
|
|
3885
|
+
textNode.textContent = firstLine;
|
|
3842
3886
|
|
|
3843
3887
|
// Other lines
|
|
3844
3888
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -3852,6 +3896,11 @@ class TextPlugin extends TemplatePlugin {
|
|
|
3852
3896
|
xml.modify.appendChild(runNode, lineNode);
|
|
3853
3897
|
}
|
|
3854
3898
|
}
|
|
3899
|
+
|
|
3900
|
+
// Clean up if the original text node is now empty
|
|
3901
|
+
if (!firstLine) {
|
|
3902
|
+
officeMarkup.modify.removeTag(tag);
|
|
3903
|
+
}
|
|
3855
3904
|
}
|
|
3856
3905
|
getLineBreak(namespace) {
|
|
3857
3906
|
return xml.create.generalNode(namespace + ':br');
|
|
@@ -5244,7 +5293,7 @@ class TemplateHandler {
|
|
|
5244
5293
|
/**
|
|
5245
5294
|
* Version number of the `easy-template-x` library.
|
|
5246
5295
|
*/
|
|
5247
|
-
version = "7.
|
|
5296
|
+
version = "7.2.0" ;
|
|
5248
5297
|
constructor(options) {
|
|
5249
5298
|
this.options = new TemplateHandlerOptions(options);
|
|
5250
5299
|
const delimiters = this.options.delimiters;
|
|
@@ -3359,7 +3359,66 @@ class LoopListStrategy {
|
|
|
3359
3359
|
}
|
|
3360
3360
|
}
|
|
3361
3361
|
|
|
3362
|
+
const LoopOver = Object.freeze({
|
|
3363
|
+
/**
|
|
3364
|
+
* Loop over the entire table row.
|
|
3365
|
+
*/
|
|
3366
|
+
Row: 'row',
|
|
3367
|
+
/**
|
|
3368
|
+
* Loop over the entire table column.
|
|
3369
|
+
*/
|
|
3370
|
+
Column: 'column',
|
|
3371
|
+
/**
|
|
3372
|
+
* Loop over the entire paragraph.
|
|
3373
|
+
*/
|
|
3374
|
+
Paragraph: 'paragraph',
|
|
3375
|
+
/**
|
|
3376
|
+
* Loop over the content enclosed between the opening and closing tag.
|
|
3377
|
+
*/
|
|
3378
|
+
Content: 'content'
|
|
3379
|
+
});
|
|
3380
|
+
|
|
3362
3381
|
class LoopParagraphStrategy {
|
|
3382
|
+
isApplicable(openTag, closeTag, isCondition) {
|
|
3383
|
+
const options = openTag.options;
|
|
3384
|
+
return options?.loopOver === LoopOver.Paragraph;
|
|
3385
|
+
}
|
|
3386
|
+
splitBefore(openTag, closeTag) {
|
|
3387
|
+
const firstParagraph = officeMarkup.query.containingParagraphNode(openTag.xmlTextNode);
|
|
3388
|
+
const lastParagraph = officeMarkup.query.containingParagraphNode(closeTag.xmlTextNode);
|
|
3389
|
+
const paragraphsToRepeat = xml.query.siblingsInRange(firstParagraph, lastParagraph);
|
|
3390
|
+
|
|
3391
|
+
// Remove the loop tags.
|
|
3392
|
+
xml.modify.remove(openTag.xmlTextNode);
|
|
3393
|
+
xml.modify.remove(closeTag.xmlTextNode);
|
|
3394
|
+
return {
|
|
3395
|
+
firstNode: firstParagraph,
|
|
3396
|
+
nodesToRepeat: paragraphsToRepeat,
|
|
3397
|
+
lastNode: lastParagraph
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
mergeBack(newParagraphs, firstParagraph, lastParagraph) {
|
|
3401
|
+
// Add new paragraphs to the document.
|
|
3402
|
+
let insertAfter = lastParagraph;
|
|
3403
|
+
for (const curParagraphsGroup of newParagraphs) {
|
|
3404
|
+
for (const paragraph of curParagraphsGroup) {
|
|
3405
|
+
xml.modify.insertAfter(paragraph, insertAfter);
|
|
3406
|
+
insertAfter = paragraph;
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
// Remove old paragraphs - between first and last paragraph.
|
|
3411
|
+
xml.modify.removeSiblings(firstParagraph, lastParagraph);
|
|
3412
|
+
|
|
3413
|
+
// Remove old paragraphs - first and last.
|
|
3414
|
+
xml.modify.remove(firstParagraph);
|
|
3415
|
+
if (firstParagraph !== lastParagraph) {
|
|
3416
|
+
xml.modify.remove(lastParagraph);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
class LoopContentStrategy {
|
|
3363
3422
|
isApplicable(openTag, closeTag, isCondition) {
|
|
3364
3423
|
return true;
|
|
3365
3424
|
}
|
|
@@ -3421,21 +3480,6 @@ class LoopParagraphStrategy {
|
|
|
3421
3480
|
}
|
|
3422
3481
|
}
|
|
3423
3482
|
|
|
3424
|
-
const LoopOver = Object.freeze({
|
|
3425
|
-
/**
|
|
3426
|
-
* Loop over the entire row.
|
|
3427
|
-
*/
|
|
3428
|
-
Row: 'row',
|
|
3429
|
-
/**
|
|
3430
|
-
* Loop over the entire column.
|
|
3431
|
-
*/
|
|
3432
|
-
Column: 'column',
|
|
3433
|
-
/**
|
|
3434
|
-
* Loop over the content enclosed between the opening and closing tag.
|
|
3435
|
-
*/
|
|
3436
|
-
Content: 'content'
|
|
3437
|
-
});
|
|
3438
|
-
|
|
3439
3483
|
class LoopTableColumnsStrategy {
|
|
3440
3484
|
isApplicable(openTag, closeTag, isCondition) {
|
|
3441
3485
|
const openCell = officeMarkup.query.containingTableCellNode(openTag.xmlTextNode);
|
|
@@ -3631,7 +3675,7 @@ class LoopTableRowsStrategy {
|
|
|
3631
3675
|
const LOOP_CONTENT_TYPE = 'loop';
|
|
3632
3676
|
class LoopPlugin extends TemplatePlugin {
|
|
3633
3677
|
contentType = LOOP_CONTENT_TYPE;
|
|
3634
|
-
loopStrategies = [new LoopTableColumnsStrategy(), new LoopTableRowsStrategy(), new LoopListStrategy(), new
|
|
3678
|
+
loopStrategies = [new LoopParagraphStrategy(), new LoopTableColumnsStrategy(), new LoopTableRowsStrategy(), new LoopListStrategy(), new LoopContentStrategy() // the default strategy
|
|
3635
3679
|
];
|
|
3636
3680
|
setUtilities(utilities) {
|
|
3637
3681
|
this.utilities = utilities;
|
|
@@ -3801,7 +3845,7 @@ class TextPlugin extends TemplatePlugin {
|
|
|
3801
3845
|
if (lines.length < 2) {
|
|
3802
3846
|
this.replaceSingleLine(tag, lines.length ? lines[0] : '');
|
|
3803
3847
|
} else {
|
|
3804
|
-
this.replaceMultiLine(tag
|
|
3848
|
+
this.replaceMultiLine(tag, lines);
|
|
3805
3849
|
}
|
|
3806
3850
|
}
|
|
3807
3851
|
replaceInAttribute(tag, text) {
|
|
@@ -3829,14 +3873,14 @@ class TextPlugin extends TemplatePlugin {
|
|
|
3829
3873
|
const wordTextNode = officeMarkup.query.containingTextNode(textNode);
|
|
3830
3874
|
officeMarkup.modify.setSpacePreserveAttribute(wordTextNode);
|
|
3831
3875
|
}
|
|
3832
|
-
replaceMultiLine(
|
|
3876
|
+
replaceMultiLine(tag, lines) {
|
|
3877
|
+
const textNode = tag.xmlTextNode;
|
|
3833
3878
|
const runNode = officeMarkup.query.containingRunNode(textNode);
|
|
3834
3879
|
const namespace = runNode.nodeName.split(':')[0];
|
|
3835
3880
|
|
|
3836
3881
|
// First line
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
}
|
|
3882
|
+
const firstLine = lines[0];
|
|
3883
|
+
textNode.textContent = firstLine;
|
|
3840
3884
|
|
|
3841
3885
|
// Other lines
|
|
3842
3886
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -3850,6 +3894,11 @@ class TextPlugin extends TemplatePlugin {
|
|
|
3850
3894
|
xml.modify.appendChild(runNode, lineNode);
|
|
3851
3895
|
}
|
|
3852
3896
|
}
|
|
3897
|
+
|
|
3898
|
+
// Clean up if the original text node is now empty
|
|
3899
|
+
if (!firstLine) {
|
|
3900
|
+
officeMarkup.modify.removeTag(tag);
|
|
3901
|
+
}
|
|
3853
3902
|
}
|
|
3854
3903
|
getLineBreak(namespace) {
|
|
3855
3904
|
return xml.create.generalNode(namespace + ':br');
|
|
@@ -5242,7 +5291,7 @@ class TemplateHandler {
|
|
|
5242
5291
|
/**
|
|
5243
5292
|
* Version number of the `easy-template-x` library.
|
|
5244
5293
|
*/
|
|
5245
|
-
version = "7.
|
|
5294
|
+
version = "7.2.0" ;
|
|
5246
5295
|
constructor(options) {
|
|
5247
5296
|
this.options = new TemplateHandlerOptions(options);
|
|
5248
5297
|
const delimiters = this.options.delimiters;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TextNodeTag } from "src/compilation";
|
|
2
|
+
import { XmlNode } from "src/xml";
|
|
3
|
+
import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
|
4
|
+
export declare class LoopContentStrategy implements ILoopStrategy {
|
|
5
|
+
isApplicable(openTag: TextNodeTag, closeTag: TextNodeTag, isCondition: boolean): boolean;
|
|
6
|
+
splitBefore(openTag: TextNodeTag, closeTag: TextNodeTag): SplitBeforeResult;
|
|
7
|
+
mergeBack(middleParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void;
|
|
8
|
+
}
|
|
@@ -4,5 +4,5 @@ import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
|
|
4
4
|
export declare class LoopParagraphStrategy implements ILoopStrategy {
|
|
5
5
|
isApplicable(openTag: TextNodeTag, closeTag: TextNodeTag, isCondition: boolean): boolean;
|
|
6
6
|
splitBefore(openTag: TextNodeTag, closeTag: TextNodeTag): SplitBeforeResult;
|
|
7
|
-
mergeBack(
|
|
7
|
+
mergeBack(newParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void;
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import { PluginUtilities, TemplatePlugin } from "src/plugins/templatePlugin";
|
|
|
7
7
|
import { TemplateData } from "src/templateData";
|
|
8
8
|
import { last } from "src/utils";
|
|
9
9
|
import { xml, XmlNode } from "src/xml";
|
|
10
|
-
import { ILoopStrategy, LoopListStrategy, LoopParagraphStrategy, LoopTableColumnsStrategy, LoopTableRowsStrategy } from "./strategy";
|
|
10
|
+
import { ILoopStrategy, LoopListStrategy, LoopParagraphStrategy, LoopContentStrategy, LoopTableColumnsStrategy, LoopTableRowsStrategy } from "./strategy";
|
|
11
11
|
|
|
12
12
|
export const LOOP_CONTENT_TYPE = 'loop';
|
|
13
13
|
|
|
@@ -16,10 +16,11 @@ export class LoopPlugin extends TemplatePlugin {
|
|
|
16
16
|
public readonly contentType = LOOP_CONTENT_TYPE;
|
|
17
17
|
|
|
18
18
|
private readonly loopStrategies: ILoopStrategy[] = [
|
|
19
|
+
new LoopParagraphStrategy(),
|
|
19
20
|
new LoopTableColumnsStrategy(),
|
|
20
21
|
new LoopTableRowsStrategy(),
|
|
21
22
|
new LoopListStrategy(),
|
|
22
|
-
new
|
|
23
|
+
new LoopContentStrategy() // the default strategy
|
|
23
24
|
];
|
|
24
25
|
|
|
25
26
|
public setUtilities(utilities: PluginUtilities): void {
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
export const LoopOver = Object.freeze({
|
|
2
2
|
/**
|
|
3
|
-
* Loop over the entire row.
|
|
3
|
+
* Loop over the entire table row.
|
|
4
4
|
*/
|
|
5
5
|
Row: 'row',
|
|
6
6
|
/**
|
|
7
|
-
* Loop over the entire column.
|
|
7
|
+
* Loop over the entire table column.
|
|
8
8
|
*/
|
|
9
9
|
Column: 'column',
|
|
10
|
+
/**
|
|
11
|
+
* Loop over the entire paragraph.
|
|
12
|
+
*/
|
|
13
|
+
Paragraph: 'paragraph',
|
|
10
14
|
/**
|
|
11
15
|
* Loop over the content enclosed between the opening and closing tag.
|
|
12
16
|
*/
|
package/src/plugins/loop/strategy/{loopParagraphStrategy.tests.ts → loopContentStrategy.tests.ts}
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { TagDisposition, TagPlacement, TextNodeTag, XmlTextNode } from "src";
|
|
2
|
-
import {
|
|
2
|
+
import { LoopContentStrategy } from "./loopContentStrategy";
|
|
3
3
|
import { parseXml } from "test/testUtils";
|
|
4
4
|
import { describe, expect, it } from "vitest";
|
|
5
5
|
|
|
6
|
-
describe(
|
|
6
|
+
describe(LoopContentStrategy, () => {
|
|
7
7
|
|
|
8
|
-
describe(
|
|
8
|
+
describe(LoopContentStrategy.prototype.splitBefore, () => {
|
|
9
9
|
|
|
10
10
|
it("when the closing loop tag's run has additional content before the tag, the extra content is preserved (bug #36)", () => {
|
|
11
11
|
|
|
@@ -45,7 +45,7 @@ describe(LoopParagraphStrategy, () => {
|
|
|
45
45
|
expect(openTag.xmlTextNode.textContent).toEqual('{#loop}');
|
|
46
46
|
expect(closeTag.xmlTextNode.textContent).toEqual('{/loop}');
|
|
47
47
|
|
|
48
|
-
const strategy = new
|
|
48
|
+
const strategy = new LoopContentStrategy();
|
|
49
49
|
|
|
50
50
|
//
|
|
51
51
|
// test
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { TextNodeTag } from "src/compilation";
|
|
2
|
+
import { officeMarkup } from "src/office";
|
|
3
|
+
import { xml, XmlNode } from "src/xml";
|
|
4
|
+
import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
|
5
|
+
|
|
6
|
+
export class LoopContentStrategy implements ILoopStrategy {
|
|
7
|
+
|
|
8
|
+
public isApplicable(openTag: TextNodeTag, closeTag: TextNodeTag, isCondition: boolean): boolean {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public splitBefore(openTag: TextNodeTag, closeTag: TextNodeTag): SplitBeforeResult {
|
|
13
|
+
|
|
14
|
+
// Gather some info
|
|
15
|
+
let firstParagraph: XmlNode = officeMarkup.query.containingParagraphNode(openTag.xmlTextNode);
|
|
16
|
+
let lastParagraph: XmlNode = officeMarkup.query.containingParagraphNode(closeTag.xmlTextNode);
|
|
17
|
+
const areSame = (firstParagraph === lastParagraph);
|
|
18
|
+
|
|
19
|
+
// Split first paragraph
|
|
20
|
+
const removeTextNode = true;
|
|
21
|
+
let splitResult = officeMarkup.modify.splitParagraphByTextNode(firstParagraph, openTag.xmlTextNode, removeTextNode);
|
|
22
|
+
firstParagraph = splitResult[0];
|
|
23
|
+
let afterFirstParagraph = splitResult[1];
|
|
24
|
+
if (areSame)
|
|
25
|
+
lastParagraph = afterFirstParagraph;
|
|
26
|
+
|
|
27
|
+
// Split last paragraph
|
|
28
|
+
splitResult = officeMarkup.modify.splitParagraphByTextNode(lastParagraph, closeTag.xmlTextNode, removeTextNode);
|
|
29
|
+
const beforeLastParagraph = splitResult[0];
|
|
30
|
+
lastParagraph = splitResult[1];
|
|
31
|
+
if (areSame)
|
|
32
|
+
afterFirstParagraph = beforeLastParagraph;
|
|
33
|
+
|
|
34
|
+
// Disconnect splitted paragraph from their parents
|
|
35
|
+
xml.modify.remove(afterFirstParagraph);
|
|
36
|
+
if (!areSame)
|
|
37
|
+
xml.modify.remove(beforeLastParagraph);
|
|
38
|
+
|
|
39
|
+
// Extract all paragraphs in between
|
|
40
|
+
let middleParagraphs: XmlNode[];
|
|
41
|
+
if (areSame) {
|
|
42
|
+
middleParagraphs = [afterFirstParagraph];
|
|
43
|
+
} else {
|
|
44
|
+
const inBetween = xml.modify.removeSiblings(firstParagraph, lastParagraph);
|
|
45
|
+
middleParagraphs = [afterFirstParagraph].concat(inBetween).concat(beforeLastParagraph);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
firstNode: firstParagraph,
|
|
50
|
+
nodesToRepeat: middleParagraphs,
|
|
51
|
+
lastNode: lastParagraph
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public mergeBack(middleParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void {
|
|
56
|
+
|
|
57
|
+
let mergeTo = firstParagraph;
|
|
58
|
+
for (const curParagraphsGroup of middleParagraphs) {
|
|
59
|
+
|
|
60
|
+
// Merge first paragraphs
|
|
61
|
+
officeMarkup.modify.joinParagraphs(mergeTo, curParagraphsGroup[0]);
|
|
62
|
+
|
|
63
|
+
// Add middle and last paragraphs to the original document
|
|
64
|
+
for (let i = 1; i < curParagraphsGroup.length; i++) {
|
|
65
|
+
xml.modify.insertBefore(curParagraphsGroup[i], lastParagraph);
|
|
66
|
+
mergeTo = curParagraphsGroup[i];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Merge last paragraph
|
|
71
|
+
officeMarkup.modify.joinParagraphs(mergeTo, lastParagraph);
|
|
72
|
+
|
|
73
|
+
// Remove the old last paragraph (was merged into the new one)
|
|
74
|
+
xml.modify.remove(lastParagraph);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TextNodeTag } from "src/compilation";
|
|
2
|
+
import { LoopOver, LoopTagOptions } from "src/plugins/loop/loopTagOptions";
|
|
2
3
|
import { officeMarkup } from "src/office";
|
|
3
4
|
import { xml, XmlNode } from "src/xml";
|
|
4
5
|
import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
|
@@ -6,71 +7,44 @@ import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
|
|
6
7
|
export class LoopParagraphStrategy implements ILoopStrategy {
|
|
7
8
|
|
|
8
9
|
public isApplicable(openTag: TextNodeTag, closeTag: TextNodeTag, isCondition: boolean): boolean {
|
|
9
|
-
|
|
10
|
+
const options = openTag.options as LoopTagOptions;
|
|
11
|
+
return options?.loopOver === LoopOver.Paragraph;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
public splitBefore(openTag: TextNodeTag, closeTag: TextNodeTag): SplitBeforeResult {
|
|
15
|
+
const firstParagraph = officeMarkup.query.containingParagraphNode(openTag.xmlTextNode);
|
|
16
|
+
const lastParagraph = officeMarkup.query.containingParagraphNode(closeTag.xmlTextNode);
|
|
17
|
+
const paragraphsToRepeat = xml.query.siblingsInRange(firstParagraph, lastParagraph);
|
|
13
18
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const areSame = (firstParagraph === lastParagraph);
|
|
18
|
-
|
|
19
|
-
// Split first paragraph
|
|
20
|
-
const removeTextNode = true;
|
|
21
|
-
let splitResult = officeMarkup.modify.splitParagraphByTextNode(firstParagraph, openTag.xmlTextNode, removeTextNode);
|
|
22
|
-
firstParagraph = splitResult[0];
|
|
23
|
-
let afterFirstParagraph = splitResult[1];
|
|
24
|
-
if (areSame)
|
|
25
|
-
lastParagraph = afterFirstParagraph;
|
|
26
|
-
|
|
27
|
-
// Split last paragraph
|
|
28
|
-
splitResult = officeMarkup.modify.splitParagraphByTextNode(lastParagraph, closeTag.xmlTextNode, removeTextNode);
|
|
29
|
-
const beforeLastParagraph = splitResult[0];
|
|
30
|
-
lastParagraph = splitResult[1];
|
|
31
|
-
if (areSame)
|
|
32
|
-
afterFirstParagraph = beforeLastParagraph;
|
|
33
|
-
|
|
34
|
-
// Disconnect splitted paragraph from their parents
|
|
35
|
-
xml.modify.remove(afterFirstParagraph);
|
|
36
|
-
if (!areSame)
|
|
37
|
-
xml.modify.remove(beforeLastParagraph);
|
|
38
|
-
|
|
39
|
-
// Extract all paragraphs in between
|
|
40
|
-
let middleParagraphs: XmlNode[];
|
|
41
|
-
if (areSame) {
|
|
42
|
-
middleParagraphs = [afterFirstParagraph];
|
|
43
|
-
} else {
|
|
44
|
-
const inBetween = xml.modify.removeSiblings(firstParagraph, lastParagraph);
|
|
45
|
-
middleParagraphs = [afterFirstParagraph].concat(inBetween).concat(beforeLastParagraph);
|
|
46
|
-
}
|
|
19
|
+
// Remove the loop tags.
|
|
20
|
+
xml.modify.remove(openTag.xmlTextNode);
|
|
21
|
+
xml.modify.remove(closeTag.xmlTextNode);
|
|
47
22
|
|
|
48
23
|
return {
|
|
49
24
|
firstNode: firstParagraph,
|
|
50
|
-
nodesToRepeat:
|
|
25
|
+
nodesToRepeat: paragraphsToRepeat,
|
|
51
26
|
lastNode: lastParagraph
|
|
52
27
|
};
|
|
53
28
|
}
|
|
54
29
|
|
|
55
|
-
public mergeBack(
|
|
56
|
-
|
|
57
|
-
let mergeTo = firstParagraph;
|
|
58
|
-
for (const curParagraphsGroup of middleParagraphs) {
|
|
30
|
+
public mergeBack(newParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void {
|
|
59
31
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
mergeTo = curParagraphsGroup[i];
|
|
32
|
+
// Add new paragraphs to the document.
|
|
33
|
+
let insertAfter = lastParagraph;
|
|
34
|
+
for (const curParagraphsGroup of newParagraphs) {
|
|
35
|
+
for (const paragraph of curParagraphsGroup) {
|
|
36
|
+
xml.modify.insertAfter(paragraph, insertAfter);
|
|
37
|
+
insertAfter = paragraph;
|
|
67
38
|
}
|
|
68
39
|
}
|
|
69
40
|
|
|
70
|
-
//
|
|
71
|
-
|
|
41
|
+
// Remove old paragraphs - between first and last paragraph.
|
|
42
|
+
xml.modify.removeSiblings(firstParagraph, lastParagraph);
|
|
72
43
|
|
|
73
|
-
// Remove
|
|
74
|
-
xml.modify.remove(
|
|
44
|
+
// Remove old paragraphs - first and last.
|
|
45
|
+
xml.modify.remove(firstParagraph);
|
|
46
|
+
if (firstParagraph !== lastParagraph) {
|
|
47
|
+
xml.modify.remove(lastParagraph);
|
|
48
|
+
}
|
|
75
49
|
}
|
|
76
50
|
}
|