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 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
  ![output document](./docs/assets/loop-over-content-out.png?raw=true)
333
333
 
334
+ For paragraphs, you can choose between this syntax (same as the default behavior):
335
+
336
+ ![input template](./docs/assets/loop-over-paragraph-in-1.png?raw=true)
337
+
338
+ And this syntax:
339
+
340
+ ![input template](./docs/assets/loop-over-paragraph-in-2.png?raw=true)
341
+
342
+ The first will produce this document:
343
+
344
+ ![output document](./docs/assets/loop-over-paragraph-out-1.png?raw=true)
345
+
346
+ And the second will produce this document:
347
+
348
+ ![output document](./docs/assets/loop-over-paragraph-out-2.png?raw=true)
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 LoopParagraphStrategy() // the default strategy
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.xmlTextNode, lines);
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(textNode, lines) {
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
- if (typeof lines[0] === 'string') {
3840
- textNode.textContent = lines[0];
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.1.0" ;
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 LoopParagraphStrategy() // the default strategy
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.xmlTextNode, lines);
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(textNode, lines) {
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
- if (typeof lines[0] === 'string') {
3838
- textNode.textContent = lines[0];
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.1.0" ;
5294
+ version = "7.2.0" ;
5246
5295
  constructor(options) {
5247
5296
  this.options = new TemplateHandlerOptions(options);
5248
5297
  const delimiters = this.options.delimiters;
@@ -1,6 +1,7 @@
1
1
  export declare const LoopOver: Readonly<{
2
2
  readonly Row: "row";
3
3
  readonly Column: "column";
4
+ readonly Paragraph: "paragraph";
4
5
  readonly Content: "content";
5
6
  }>;
6
7
  export type LoopOver = typeof LoopOver[keyof typeof LoopOver];
@@ -1,5 +1,6 @@
1
1
  export * from './iLoopStrategy';
2
2
  export * from './loopListStrategy';
3
3
  export * from './loopParagraphStrategy';
4
+ export * from './loopContentStrategy';
4
5
  export * from './loopTableColumnsStrategy';
5
6
  export * from './loopTableRowsStrategy';
@@ -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(middleParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void;
7
+ mergeBack(newParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void;
8
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-template-x",
3
- "version": "7.1.1",
3
+ "version": "7.2.0",
4
4
  "description": "Generate docx documents from templates, in Node or in the browser.",
5
5
  "keywords": [
6
6
  "docx",
@@ -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 LoopParagraphStrategy() // the default strategy
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
  */
@@ -1,5 +1,6 @@
1
1
  export * from './iLoopStrategy';
2
2
  export * from './loopListStrategy';
3
3
  export * from './loopParagraphStrategy';
4
+ export * from './loopContentStrategy';
4
5
  export * from './loopTableColumnsStrategy';
5
6
  export * from './loopTableRowsStrategy';
@@ -1,11 +1,11 @@
1
1
  import { TagDisposition, TagPlacement, TextNodeTag, XmlTextNode } from "src";
2
- import { LoopParagraphStrategy } from "src/plugins/loop/strategy";
2
+ import { LoopContentStrategy } from "./loopContentStrategy";
3
3
  import { parseXml } from "test/testUtils";
4
4
  import { describe, expect, it } from "vitest";
5
5
 
6
- describe(LoopParagraphStrategy, () => {
6
+ describe(LoopContentStrategy, () => {
7
7
 
8
- describe(LoopParagraphStrategy.prototype.splitBefore, () => {
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 LoopParagraphStrategy();
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
- return true;
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
- // 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
- }
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: middleParagraphs,
25
+ nodesToRepeat: paragraphsToRepeat,
51
26
  lastNode: lastParagraph
52
27
  };
53
28
  }
54
29
 
55
- public mergeBack(middleParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void {
56
-
57
- let mergeTo = firstParagraph;
58
- for (const curParagraphsGroup of middleParagraphs) {
30
+ public mergeBack(newParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void {
59
31
 
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];
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
- // Merge last paragraph
71
- officeMarkup.modify.joinParagraphs(mergeTo, lastParagraph);
41
+ // Remove old paragraphs - between first and last paragraph.
42
+ xml.modify.removeSiblings(firstParagraph, lastParagraph);
72
43
 
73
- // Remove the old last paragraph (was merged into the new one)
74
- xml.modify.remove(lastParagraph);
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
  }