docxmlater 10.2.2 → 10.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docxmlater",
3
- "version": "10.2.2",
3
+ "version": "10.2.3",
4
4
  "description": "A comprehensive DOCX editing framework for creating, reading, and manipulating Microsoft Word documents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -2689,6 +2689,7 @@ export class DocumentParser {
2689
2689
  let fieldRuns: Run[] = [];
2690
2690
  let fieldRevisions: Revision[] = []; // Track revisions inside field result section
2691
2691
  let instructionRevisions: Revision[] = []; // Track revisions in instruction area
2692
+ let orderedResultItems: Array<Run | Revision> = []; // Track interleaved order of result runs + revisions
2692
2693
  let fieldState: 'begin' | 'instruction' | 'separate' | 'result' | 'end' | null = null;
2693
2694
  let nestingDepth = 0;
2694
2695
  let hasNestedFields = false;
@@ -2749,6 +2750,7 @@ export class DocumentParser {
2749
2750
  fieldRuns = [];
2750
2751
  instructionRevisions = [];
2751
2752
  fieldRevisions = [];
2753
+ orderedResultItems = [];
2752
2754
  fieldState = null;
2753
2755
  hasNestedFields = false;
2754
2756
  fieldStartIndex = -1;
@@ -2801,6 +2803,7 @@ export class DocumentParser {
2801
2803
  fieldRuns = [];
2802
2804
  instructionRevisions = [];
2803
2805
  fieldRevisions = [];
2806
+ orderedResultItems = [];
2804
2807
  fieldState = null;
2805
2808
  break;
2806
2809
  }
@@ -2840,6 +2843,39 @@ export class DocumentParser {
2840
2843
  if (fieldRevisions.length > 0) {
2841
2844
  const complexField = this.createComplexFieldFromRuns(fieldRuns);
2842
2845
  if (complexField) {
2846
+ // Build ordered resultContent for correct round-trip serialization
2847
+ if (orderedResultItems.length > 0) {
2848
+ const orderedContent: XMLElement[] = [];
2849
+ for (const resultItem of orderedResultItems) {
2850
+ if (resultItem instanceof Run) {
2851
+ orderedContent.push(resultItem.toXML());
2852
+ } else if (resultItem instanceof Revision) {
2853
+ const xml = resultItem.toXML();
2854
+ if (xml) orderedContent.push(xml);
2855
+ }
2856
+ }
2857
+ // Compute accepted text (non-revision runs + insertion text)
2858
+ let acceptedText = '';
2859
+ for (const resultItem of orderedResultItems) {
2860
+ if (resultItem instanceof Run) {
2861
+ acceptedText += resultItem.getText() || '';
2862
+ } else if (
2863
+ resultItem instanceof Revision &&
2864
+ resultItem.getType() === 'insert'
2865
+ ) {
2866
+ for (const child of resultItem.getRuns()) {
2867
+ acceptedText += child.getText() || '';
2868
+ }
2869
+ }
2870
+ }
2871
+ // Set accepted text as the result (for getResult() API)
2872
+ complexField.setResult(acceptedText || complexField.getResult() || '');
2873
+ // Add ordered content for serialization (overrides the text-based path)
2874
+ for (const xmlEl of orderedContent) {
2875
+ complexField.addResultContent(xmlEl);
2876
+ }
2877
+ }
2878
+ // Attach revisions for tracking/acceptance
2843
2879
  for (const rev of fieldRevisions) {
2844
2880
  rev.setFieldContext({
2845
2881
  field: complexField,
@@ -2858,6 +2894,7 @@ export class DocumentParser {
2858
2894
  fieldRuns = [];
2859
2895
  instructionRevisions = [];
2860
2896
  fieldRevisions = [];
2897
+ orderedResultItems = [];
2861
2898
  fieldState = null;
2862
2899
  break;
2863
2900
  }
@@ -2905,6 +2942,7 @@ export class DocumentParser {
2905
2942
  fieldRuns = [];
2906
2943
  instructionRevisions = [];
2907
2944
  fieldRevisions = [];
2945
+ orderedResultItems = [];
2908
2946
  fieldState = null;
2909
2947
  break;
2910
2948
  }
@@ -2924,6 +2962,7 @@ export class DocumentParser {
2924
2962
  fieldRuns = [];
2925
2963
  instructionRevisions = [];
2926
2964
  fieldRevisions = [];
2965
+ orderedResultItems = [];
2927
2966
  fieldState = null;
2928
2967
  break;
2929
2968
  }
@@ -2958,6 +2997,7 @@ export class DocumentParser {
2958
2997
  fieldRuns = [];
2959
2998
  instructionRevisions = [];
2960
2999
  fieldRevisions = [];
3000
+ orderedResultItems = [];
2961
3001
  fieldState = null;
2962
3002
  }
2963
3003
  break;
@@ -2971,11 +3011,16 @@ export class DocumentParser {
2971
3011
  } else {
2972
3012
  // Regular run - check if we're inside a field result section
2973
3013
  if (nestingDepth > 0) {
2974
- // Inside a nested field - collect all runs to preserve raw structure
3014
+ // Inside a field - collect all runs to preserve raw structure
2975
3015
  fieldRuns.push(item);
3016
+ // Track result runs for interleaved ordering (only outer field level)
3017
+ if (nestingDepth === 1 && (fieldState === 'separate' || fieldState === 'result')) {
3018
+ orderedResultItems.push(item);
3019
+ }
2976
3020
  } else if (fieldState === 'separate' || fieldState === 'result') {
2977
3021
  // This run is part of the field result - collect it
2978
3022
  fieldRuns.push(item);
3023
+ orderedResultItems.push(item);
2979
3024
  fieldState = 'result';
2980
3025
  } else if (fieldState === 'begin' || fieldState === 'instruction') {
2981
3026
  // We're in the middle of parsing field instruction
@@ -2994,6 +3039,7 @@ export class DocumentParser {
2994
3039
  fieldRuns = [];
2995
3040
  instructionRevisions = [];
2996
3041
  fieldRevisions = [];
3042
+ orderedResultItems = [];
2997
3043
  fieldState = null;
2998
3044
  groupedContent.push(item);
2999
3045
  } else {
@@ -3010,6 +3056,7 @@ export class DocumentParser {
3010
3056
  // Set preliminary field context (field reference will be set when ComplexField is created)
3011
3057
  item.setFieldContext({ position: 'result' });
3012
3058
  fieldRevisions.push(item);
3059
+ orderedResultItems.push(item);
3013
3060
  fieldState = 'result';
3014
3061
  defaultLogger.debug(
3015
3062
  `Found revision inside complex field result: type=${item.getType()}, id=${item.getId()}`
@@ -3038,6 +3085,7 @@ export class DocumentParser {
3038
3085
  fieldRuns = [];
3039
3086
  instructionRevisions = [];
3040
3087
  fieldRevisions = [];
3088
+ orderedResultItems = [];
3041
3089
  fieldState = null;
3042
3090
  groupedContent.push(item);
3043
3091
  } else {
@@ -3061,6 +3109,7 @@ export class DocumentParser {
3061
3109
  fieldRuns = [];
3062
3110
  instructionRevisions = [];
3063
3111
  fieldRevisions = [];
3112
+ orderedResultItems = [];
3064
3113
  fieldState = null;
3065
3114
  }
3066
3115
  groupedContent.push(item);
@@ -970,13 +970,16 @@ export class ComplexField {
970
970
  });
971
971
  }
972
972
 
973
- // 4a. Result revisions (tracked changes within the result section)
974
- // These MUST appear between the separator and end marker per ECMA-376
975
- // The revisions contain the actual field result content wrapped in w:ins or w:del
976
- for (const revision of this.resultRevisions) {
977
- const revisionXml = revision.toXML();
978
- if (revisionXml) {
979
- runs.push(revisionXml);
973
+ // 4a. Result revisions only when NOT using resultContent
974
+ // When resultContent is populated (e.g., by DocumentParser with interleaved revision XML),
975
+ // it already includes revision elements in the correct interleaved position.
976
+ // Emitting them again here would cause duplication.
977
+ if (this.resultContent.length === 0) {
978
+ for (const revision of this.resultRevisions) {
979
+ const revisionXml = revision.toXML();
980
+ if (revisionXml) {
981
+ runs.push(revisionXml);
982
+ }
980
983
  }
981
984
  }
982
985
 
@@ -547,15 +547,42 @@ function acceptRevisionsInParagraph(
547
547
  newContent.push(item);
548
548
  } else if (item instanceof ComplexField && item.hasResultRevisions()) {
549
549
  // Accept revisions nested inside ComplexField result sections
550
+ // Merge insertion/moveTo text into the result before clearing
550
551
  const fieldRevisions = item.getResultRevisions();
552
+ let mergedResult = item.getResult() || '';
553
+ const revisionsToKeep: Revision[] = [];
554
+
551
555
  for (const rev of fieldRevisions) {
552
556
  const revType = rev.getType();
553
- if (revType === 'insert' && options.acceptInsertions) result.insertionsAccepted++;
554
- else if (revType === 'delete' && options.acceptDeletions) result.deletionsAccepted++;
555
- else if ((revType === 'moveFrom' || revType === 'moveTo') && options.acceptMoves)
557
+ if (revType === 'insert' && options.acceptInsertions) {
558
+ // Merge insertion text into result
559
+ for (const child of rev.getRuns()) {
560
+ mergedResult += child.getText() || '';
561
+ }
562
+ result.insertionsAccepted++;
563
+ } else if (revType === 'delete' && options.acceptDeletions) {
564
+ // Deleted text was never in result — just count it
565
+ result.deletionsAccepted++;
566
+ } else if (revType === 'moveTo' && options.acceptMoves) {
567
+ // Merge moveTo text into result
568
+ for (const child of rev.getRuns()) {
569
+ mergedResult += child.getText() || '';
570
+ }
571
+ result.movesAccepted++;
572
+ } else if (revType === 'moveFrom' && options.acceptMoves) {
556
573
  result.movesAccepted++;
574
+ } else {
575
+ // Revision type not being accepted — keep it
576
+ revisionsToKeep.push(rev);
577
+ }
578
+ }
579
+
580
+ // setResult() clears both resultRevisions and resultContent
581
+ item.setResult(mergedResult);
582
+ // Restore any unaccepted revisions
583
+ if (revisionsToKeep.length > 0) {
584
+ item.setResultRevisions(revisionsToKeep);
557
585
  }
558
- item.setResultRevisions([]); // Clear after acceptance
559
586
  newContent.push(item);
560
587
  } else {
561
588
  // Non-revision content - keep as-is