docxmlater 11.0.10 → 12.0.1
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 +51 -13
- package/dist/core/Document.d.ts +28 -2
- package/dist/core/Document.d.ts.map +1 -1
- package/dist/core/Document.js +725 -170
- package/dist/core/Document.js.map +1 -1
- package/dist/core/DocumentGenerator.d.ts +1 -0
- package/dist/core/DocumentGenerator.d.ts.map +1 -1
- package/dist/core/DocumentGenerator.js +77 -25
- package/dist/core/DocumentGenerator.js.map +1 -1
- package/dist/core/DocumentParser.d.ts +7 -3
- package/dist/core/DocumentParser.d.ts.map +1 -1
- package/dist/core/DocumentParser.js +427 -81
- package/dist/core/DocumentParser.js.map +1 -1
- package/dist/core/RelationshipManager.d.ts.map +1 -1
- package/dist/core/RelationshipManager.js +2 -2
- package/dist/core/RelationshipManager.js.map +1 -1
- package/dist/elements/Comment.d.ts +15 -1
- package/dist/elements/Comment.d.ts.map +1 -1
- package/dist/elements/Comment.js +67 -12
- package/dist/elements/Comment.js.map +1 -1
- package/dist/elements/CommentManager.d.ts +4 -0
- package/dist/elements/CommentManager.d.ts.map +1 -1
- package/dist/elements/CommentManager.js +74 -16
- package/dist/elements/CommentManager.js.map +1 -1
- package/dist/elements/EndnoteManager.d.ts.map +1 -1
- package/dist/elements/EndnoteManager.js.map +1 -1
- package/dist/elements/Field.d.ts +10 -1
- package/dist/elements/Field.d.ts.map +1 -1
- package/dist/elements/Field.js +76 -34
- package/dist/elements/Field.js.map +1 -1
- package/dist/elements/FieldHelpers.d.ts +1 -0
- package/dist/elements/FieldHelpers.d.ts.map +1 -1
- package/dist/elements/FieldHelpers.js +8 -4
- package/dist/elements/FieldHelpers.js.map +1 -1
- package/dist/elements/Footer.d.ts +3 -1
- package/dist/elements/Footer.d.ts.map +1 -1
- package/dist/elements/Footer.js +39 -53
- package/dist/elements/Footer.js.map +1 -1
- package/dist/elements/FootnoteManager.d.ts.map +1 -1
- package/dist/elements/FootnoteManager.js.map +1 -1
- package/dist/elements/Header.d.ts +3 -1
- package/dist/elements/Header.d.ts.map +1 -1
- package/dist/elements/Header.js +39 -53
- package/dist/elements/Header.js.map +1 -1
- package/dist/elements/HeaderFooterManager.d.ts +4 -2
- package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
- package/dist/elements/HeaderFooterManager.js +36 -6
- package/dist/elements/HeaderFooterManager.js.map +1 -1
- package/dist/elements/Hyperlink.d.ts +6 -1
- package/dist/elements/Hyperlink.d.ts.map +1 -1
- package/dist/elements/Hyperlink.js +69 -44
- package/dist/elements/Hyperlink.js.map +1 -1
- package/dist/elements/Image.d.ts.map +1 -1
- package/dist/elements/Image.js +33 -6
- package/dist/elements/Image.js.map +1 -1
- package/dist/elements/ImageManager.d.ts +1 -0
- package/dist/elements/ImageManager.d.ts.map +1 -1
- package/dist/elements/ImageManager.js +11 -0
- package/dist/elements/ImageManager.js.map +1 -1
- package/dist/elements/Paragraph.d.ts +3 -0
- package/dist/elements/Paragraph.d.ts.map +1 -1
- package/dist/elements/Paragraph.js +91 -26
- package/dist/elements/Paragraph.js.map +1 -1
- package/dist/elements/Revision.d.ts +4 -0
- package/dist/elements/Revision.d.ts.map +1 -1
- package/dist/elements/Revision.js +369 -29
- package/dist/elements/Revision.js.map +1 -1
- package/dist/elements/Run.d.ts +1 -0
- package/dist/elements/Run.d.ts.map +1 -1
- package/dist/elements/Run.js +24 -4
- package/dist/elements/Run.js.map +1 -1
- package/dist/elements/Section.d.ts.map +1 -1
- package/dist/elements/Section.js +17 -7
- package/dist/elements/Section.js.map +1 -1
- package/dist/elements/Shape.d.ts.map +1 -1
- package/dist/elements/Shape.js +50 -42
- package/dist/elements/Shape.js.map +1 -1
- package/dist/elements/StructuredDocumentTag.d.ts +9 -1
- package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
- package/dist/elements/StructuredDocumentTag.js +46 -6
- package/dist/elements/StructuredDocumentTag.js.map +1 -1
- package/dist/elements/Table.d.ts +2 -0
- package/dist/elements/Table.d.ts.map +1 -1
- package/dist/elements/Table.js +90 -42
- package/dist/elements/Table.js.map +1 -1
- package/dist/elements/TableCell.d.ts +1 -0
- package/dist/elements/TableCell.d.ts.map +1 -1
- package/dist/elements/TableCell.js +32 -13
- package/dist/elements/TableCell.js.map +1 -1
- package/dist/elements/TableOfContents.d.ts.map +1 -1
- package/dist/elements/TableOfContents.js +5 -7
- package/dist/elements/TableOfContents.js.map +1 -1
- package/dist/elements/TableRow.d.ts +1 -0
- package/dist/elements/TableRow.d.ts.map +1 -1
- package/dist/elements/TableRow.js +16 -1
- package/dist/elements/TableRow.js.map +1 -1
- package/dist/elements/TextBox.d.ts.map +1 -1
- package/dist/elements/TextBox.js +55 -42
- package/dist/elements/TextBox.js.map +1 -1
- package/dist/esm/core/Document.js +726 -171
- package/dist/esm/core/Document.js.map +1 -1
- package/dist/esm/core/DocumentGenerator.js +77 -25
- package/dist/esm/core/DocumentGenerator.js.map +1 -1
- package/dist/esm/core/DocumentParser.js +427 -81
- package/dist/esm/core/DocumentParser.js.map +1 -1
- package/dist/esm/core/RelationshipManager.js +2 -2
- package/dist/esm/core/RelationshipManager.js.map +1 -1
- package/dist/esm/elements/Comment.js +67 -12
- package/dist/esm/elements/Comment.js.map +1 -1
- package/dist/esm/elements/CommentManager.js +74 -16
- package/dist/esm/elements/CommentManager.js.map +1 -1
- package/dist/esm/elements/EndnoteManager.js.map +1 -1
- package/dist/esm/elements/Field.js +77 -35
- package/dist/esm/elements/Field.js.map +1 -1
- package/dist/esm/elements/FieldHelpers.js +7 -4
- package/dist/esm/elements/FieldHelpers.js.map +1 -1
- package/dist/esm/elements/Footer.js +39 -53
- package/dist/esm/elements/Footer.js.map +1 -1
- package/dist/esm/elements/FootnoteManager.js.map +1 -1
- package/dist/esm/elements/Header.js +39 -53
- package/dist/esm/elements/Header.js.map +1 -1
- package/dist/esm/elements/HeaderFooterManager.js +36 -6
- package/dist/esm/elements/HeaderFooterManager.js.map +1 -1
- package/dist/esm/elements/Hyperlink.js +69 -44
- package/dist/esm/elements/Hyperlink.js.map +1 -1
- package/dist/esm/elements/Image.js +33 -6
- package/dist/esm/elements/Image.js.map +1 -1
- package/dist/esm/elements/ImageManager.js +11 -0
- package/dist/esm/elements/ImageManager.js.map +1 -1
- package/dist/esm/elements/Paragraph.js +91 -26
- package/dist/esm/elements/Paragraph.js.map +1 -1
- package/dist/esm/elements/Revision.js +369 -29
- package/dist/esm/elements/Revision.js.map +1 -1
- package/dist/esm/elements/Run.js +24 -4
- package/dist/esm/elements/Run.js.map +1 -1
- package/dist/esm/elements/Section.js +17 -7
- package/dist/esm/elements/Section.js.map +1 -1
- package/dist/esm/elements/Shape.js +50 -42
- package/dist/esm/elements/Shape.js.map +1 -1
- package/dist/esm/elements/StructuredDocumentTag.js +46 -6
- package/dist/esm/elements/StructuredDocumentTag.js.map +1 -1
- package/dist/esm/elements/Table.js +90 -42
- package/dist/esm/elements/Table.js.map +1 -1
- package/dist/esm/elements/TableCell.js +32 -13
- package/dist/esm/elements/TableCell.js.map +1 -1
- package/dist/esm/elements/TableOfContents.js +5 -7
- package/dist/esm/elements/TableOfContents.js.map +1 -1
- package/dist/esm/elements/TableRow.js +16 -1
- package/dist/esm/elements/TableRow.js.map +1 -1
- package/dist/esm/elements/TextBox.js +55 -42
- package/dist/esm/elements/TextBox.js.map +1 -1
- package/dist/esm/formatting/AbstractNumbering.js +1 -1
- package/dist/esm/formatting/AbstractNumbering.js.map +1 -1
- package/dist/esm/formatting/NumberingLevel.js +8 -6
- package/dist/esm/formatting/NumberingLevel.js.map +1 -1
- package/dist/esm/formatting/NumberingManager.js +16 -1
- package/dist/esm/formatting/NumberingManager.js.map +1 -1
- package/dist/esm/formatting/Style.js +14 -0
- package/dist/esm/formatting/Style.js.map +1 -1
- package/dist/esm/formatting/StylesManager.js +14 -2
- package/dist/esm/formatting/StylesManager.js.map +1 -1
- package/dist/esm/helpers/CleanupHelper.js +103 -26
- package/dist/esm/helpers/CleanupHelper.js.map +1 -1
- package/dist/esm/images/ImageOptimizer.js +52 -4
- package/dist/esm/images/ImageOptimizer.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal.js +1 -1
- package/dist/esm/internal.js.map +1 -1
- package/dist/esm/processors/CompatibilityUpgrader.js +7 -1
- package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -1
- package/dist/esm/processors/InMemoryRevisionAcceptor.js +134 -13
- package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -1
- package/dist/esm/processors/RevisionWalker.js +58 -8
- package/dist/esm/processors/RevisionWalker.js.map +1 -1
- package/dist/esm/processors/SelectiveRevisionAcceptor.js +116 -17
- package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -1
- package/dist/esm/processors/cnfStyleDecoder.js +7 -7
- package/dist/esm/processors/cnfStyleDecoder.js.map +1 -1
- package/dist/esm/processors/stripTrackedChanges.js +16 -5
- package/dist/esm/processors/stripTrackedChanges.js.map +1 -1
- package/dist/esm/utils/deepClone.js +24 -11
- package/dist/esm/utils/deepClone.js.map +1 -1
- package/dist/esm/utils/units.js +1 -1
- package/dist/esm/utils/units.js.map +1 -1
- package/dist/esm/utils/validation.js +11 -23
- package/dist/esm/utils/validation.js.map +1 -1
- package/dist/esm/utils/xmlSanitization.js +131 -7
- package/dist/esm/utils/xmlSanitization.js.map +1 -1
- package/dist/esm/validation/RevisionAutoFixer.js +69 -55
- package/dist/esm/validation/RevisionAutoFixer.js.map +1 -1
- package/dist/esm/validation/RevisionValidator.js +24 -20
- package/dist/esm/validation/RevisionValidator.js.map +1 -1
- package/dist/esm/xml/XMLBuilder.js +52 -20
- package/dist/esm/xml/XMLBuilder.js.map +1 -1
- package/dist/esm/xml/XMLParser.js +55 -13
- package/dist/esm/xml/XMLParser.js.map +1 -1
- package/dist/esm/zip/ZipHandler.js +37 -13
- package/dist/esm/zip/ZipHandler.js.map +1 -1
- package/dist/esm/zip/ZipReader.js +53 -6
- package/dist/esm/zip/ZipReader.js.map +1 -1
- package/dist/esm/zip/errors.js +7 -0
- package/dist/esm/zip/errors.js.map +1 -1
- package/dist/esm/zip/types.js +4 -0
- package/dist/esm/zip/types.js.map +1 -1
- package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
- package/dist/formatting/AbstractNumbering.js +1 -1
- package/dist/formatting/AbstractNumbering.js.map +1 -1
- package/dist/formatting/NumberingLevel.d.ts +1 -1
- package/dist/formatting/NumberingLevel.d.ts.map +1 -1
- package/dist/formatting/NumberingLevel.js +8 -6
- package/dist/formatting/NumberingLevel.js.map +1 -1
- package/dist/formatting/NumberingManager.d.ts.map +1 -1
- package/dist/formatting/NumberingManager.js +16 -1
- package/dist/formatting/NumberingManager.js.map +1 -1
- package/dist/formatting/Style.d.ts.map +1 -1
- package/dist/formatting/Style.js +14 -0
- package/dist/formatting/Style.js.map +1 -1
- package/dist/formatting/StylesManager.d.ts +2 -0
- package/dist/formatting/StylesManager.d.ts.map +1 -1
- package/dist/formatting/StylesManager.js +14 -2
- package/dist/formatting/StylesManager.js.map +1 -1
- package/dist/helpers/CleanupHelper.d.ts.map +1 -1
- package/dist/helpers/CleanupHelper.js +103 -26
- package/dist/helpers/CleanupHelper.js.map +1 -1
- package/dist/images/ImageOptimizer.d.ts.map +1 -1
- package/dist/images/ImageOptimizer.js +52 -4
- package/dist/images/ImageOptimizer.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -1
- package/dist/internal.js.map +1 -1
- package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -1
- package/dist/processors/CompatibilityUpgrader.js +7 -1
- package/dist/processors/CompatibilityUpgrader.js.map +1 -1
- package/dist/processors/InMemoryRevisionAcceptor.d.ts +1 -0
- package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -1
- package/dist/processors/InMemoryRevisionAcceptor.js +135 -13
- package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -1
- package/dist/processors/RevisionWalker.d.ts +2 -0
- package/dist/processors/RevisionWalker.d.ts.map +1 -1
- package/dist/processors/RevisionWalker.js +58 -8
- package/dist/processors/RevisionWalker.js.map +1 -1
- package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -1
- package/dist/processors/SelectiveRevisionAcceptor.js +116 -17
- package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -1
- package/dist/processors/cnfStyleDecoder.d.ts.map +1 -1
- package/dist/processors/cnfStyleDecoder.js +7 -7
- package/dist/processors/cnfStyleDecoder.js.map +1 -1
- package/dist/processors/stripTrackedChanges.d.ts.map +1 -1
- package/dist/processors/stripTrackedChanges.js +16 -5
- package/dist/processors/stripTrackedChanges.js.map +1 -1
- package/dist/utils/deepClone.d.ts.map +1 -1
- package/dist/utils/deepClone.js +24 -11
- package/dist/utils/deepClone.js.map +1 -1
- package/dist/utils/units.d.ts.map +1 -1
- package/dist/utils/units.js +1 -1
- package/dist/utils/units.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +11 -23
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/xmlSanitization.d.ts +2 -0
- package/dist/utils/xmlSanitization.d.ts.map +1 -1
- package/dist/utils/xmlSanitization.js +133 -7
- package/dist/utils/xmlSanitization.js.map +1 -1
- package/dist/validation/RevisionAutoFixer.d.ts +6 -2
- package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
- package/dist/validation/RevisionAutoFixer.js +68 -54
- package/dist/validation/RevisionAutoFixer.js.map +1 -1
- package/dist/validation/RevisionValidator.d.ts +2 -0
- package/dist/validation/RevisionValidator.d.ts.map +1 -1
- package/dist/validation/RevisionValidator.js +26 -21
- package/dist/validation/RevisionValidator.js.map +1 -1
- package/dist/xml/XMLBuilder.d.ts +1 -0
- package/dist/xml/XMLBuilder.d.ts.map +1 -1
- package/dist/xml/XMLBuilder.js +52 -20
- package/dist/xml/XMLBuilder.js.map +1 -1
- package/dist/xml/XMLParser.d.ts +1 -0
- package/dist/xml/XMLParser.d.ts.map +1 -1
- package/dist/xml/XMLParser.js +55 -13
- package/dist/xml/XMLParser.js.map +1 -1
- package/dist/zip/ZipHandler.d.ts.map +1 -1
- package/dist/zip/ZipHandler.js +37 -13
- package/dist/zip/ZipHandler.js.map +1 -1
- package/dist/zip/ZipReader.d.ts.map +1 -1
- package/dist/zip/ZipReader.js +52 -5
- package/dist/zip/ZipReader.js.map +1 -1
- package/dist/zip/errors.d.ts +3 -0
- package/dist/zip/errors.d.ts.map +1 -1
- package/dist/zip/errors.js +9 -1
- package/dist/zip/errors.js.map +1 -1
- package/dist/zip/types.d.ts +4 -0
- package/dist/zip/types.d.ts.map +1 -1
- package/dist/zip/types.js +4 -0
- package/dist/zip/types.js.map +1 -1
- package/package.json +2 -2
- package/src/core/Document.ts +1159 -240
- package/src/core/DocumentGenerator.ts +106 -26
- package/src/core/DocumentParser.ts +681 -114
- package/src/core/RelationshipManager.ts +446 -442
- package/src/elements/Comment.ts +123 -18
- package/src/elements/CommentManager.ts +121 -22
- package/src/elements/EndnoteManager.ts +4 -1
- package/src/elements/Field.ts +134 -43
- package/src/elements/FieldHelpers.ts +20 -4
- package/src/elements/Footer.ts +62 -67
- package/src/elements/FootnoteManager.ts +4 -1
- package/src/elements/Header.ts +62 -67
- package/src/elements/HeaderFooterManager.ts +56 -8
- package/src/elements/Hyperlink.ts +150 -55
- package/src/elements/Image.ts +51 -7
- package/src/elements/ImageManager.ts +24 -0
- package/src/elements/Paragraph.ts +161 -40
- package/src/elements/Revision.ts +446 -47
- package/src/elements/Run.ts +44 -6
- package/src/elements/Section.ts +25 -9
- package/src/elements/Shape.ts +52 -44
- package/src/elements/StructuredDocumentTag.ts +83 -8
- package/src/elements/Table.ts +151 -50
- package/src/elements/TableCell.ts +57 -17
- package/src/elements/TableOfContents.ts +10 -9
- package/src/elements/TableRow.ts +35 -3
- package/src/elements/TextBox.ts +58 -43
- package/src/formatting/AbstractNumbering.ts +4 -2
- package/src/formatting/NumberingLevel.ts +50 -12
- package/src/formatting/NumberingManager.ts +32 -3
- package/src/formatting/Style.ts +14 -0
- package/src/formatting/StylesManager.ts +29 -2
- package/src/helpers/CleanupHelper.ts +122 -30
- package/src/images/ImageOptimizer.ts +64 -5
- package/src/index.ts +7 -0
- package/src/internal.ts +1 -0
- package/src/processors/CompatibilityUpgrader.ts +15 -2
- package/src/processors/InMemoryRevisionAcceptor.ts +276 -17
- package/src/processors/RevisionWalker.ts +83 -7
- package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
- package/src/processors/cnfStyleDecoder.ts +18 -12
- package/src/processors/stripTrackedChanges.ts +47 -10
- package/src/utils/deepClone.ts +48 -14
- package/src/utils/units.ts +3 -1
- package/src/utils/validation.ts +30 -32
- package/src/utils/xmlSanitization.ts +218 -22
- package/src/validation/RevisionAutoFixer.ts +109 -65
- package/src/validation/RevisionValidator.ts +48 -23
- package/src/xml/XMLBuilder.ts +82 -22
- package/src/xml/XMLParser.ts +87 -20
- package/src/zip/ZipHandler.ts +61 -21
- package/src/zip/ZipReader.ts +135 -13
- package/src/zip/errors.ts +15 -0
- package/src/zip/types.ts +44 -2
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
isImageRunContent,
|
|
25
25
|
} from '../elements/RevisionContent.js';
|
|
26
26
|
import { ComplexField } from '../elements/Field.js';
|
|
27
|
+
import { RangeMarker, RangeMarkerType } from '../elements/RangeMarker.js';
|
|
28
|
+
import { PreservedElement } from '../elements/PreservedElement.js';
|
|
27
29
|
import { Table } from '../elements/Table.js';
|
|
28
30
|
import { getGlobalLogger, createScopedLogger, ILogger } from '../utils/logger.js';
|
|
29
31
|
|
|
@@ -82,6 +84,34 @@ const PROPERTY_REVISION_TYPES: RevisionType[] = [
|
|
|
82
84
|
'numberingChange',
|
|
83
85
|
];
|
|
84
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Range-marker boundary types dropped when the matching accept option is set.
|
|
89
|
+
* These are paired boundaries for the move / ins / del revisions handled
|
|
90
|
+
* elsewhere in the loop; once the revision they delimit is accepted, the
|
|
91
|
+
* markers reference a change that no longer exists (ECMA-376 §17.13.5.21-28)
|
|
92
|
+
* and Word still reports the document as containing tracked changes. The
|
|
93
|
+
* raw-XML acceptor (acceptRevisions.ts) and stripRevisionsFromXml remove the
|
|
94
|
+
* same set; this keeps the in-memory paragraph path in parity.
|
|
95
|
+
*/
|
|
96
|
+
const MOVE_RANGE_MARKER_TYPES = new Set<RangeMarkerType>([
|
|
97
|
+
'moveFromRangeStart',
|
|
98
|
+
'moveFromRangeEnd',
|
|
99
|
+
'moveToRangeStart',
|
|
100
|
+
'moveToRangeEnd',
|
|
101
|
+
'customXmlMoveFromRangeStart',
|
|
102
|
+
'customXmlMoveFromRangeEnd',
|
|
103
|
+
'customXmlMoveToRangeStart',
|
|
104
|
+
'customXmlMoveToRangeEnd',
|
|
105
|
+
]);
|
|
106
|
+
const INS_RANGE_MARKER_TYPES = new Set<RangeMarkerType>([
|
|
107
|
+
'customXmlInsRangeStart',
|
|
108
|
+
'customXmlInsRangeEnd',
|
|
109
|
+
]);
|
|
110
|
+
const DEL_RANGE_MARKER_TYPES = new Set<RangeMarkerType>([
|
|
111
|
+
'customXmlDelRangeStart',
|
|
112
|
+
'customXmlDelRangeEnd',
|
|
113
|
+
]);
|
|
114
|
+
|
|
85
115
|
/**
|
|
86
116
|
* Strip revision markup from raw XML string.
|
|
87
117
|
* Used for nested tables stored as raw XML that cannot be processed via the in-memory model.
|
|
@@ -132,21 +162,27 @@ export function stripRevisionsFromXml(xml: string): string {
|
|
|
132
162
|
}
|
|
133
163
|
|
|
134
164
|
// Step 3: Remove deletions entirely (including content)
|
|
165
|
+
// Self-closing markers (paragraph-mark deletions in w:pPr/w:rPr per
|
|
166
|
+
// ECMA-376 §17.13.5.15) must be stripped BEFORE the block pattern runs:
|
|
167
|
+
// its [^>]* also matches the trailing '/' of `<w:del .../>`, which would
|
|
168
|
+
// turn the marker into an opening tag and swallow everything up to the
|
|
169
|
+
// next </w:del>, leaving unbalanced XML.
|
|
170
|
+
result = result.replace(/<w:del\b[^>]*\/>/g, '');
|
|
135
171
|
// Iterate until no more deletions (handles nested cases)
|
|
136
172
|
let prevLen = 0;
|
|
137
173
|
while (result.length !== prevLen) {
|
|
138
174
|
prevLen = result.length;
|
|
139
175
|
result = result.replace(/<w:del\b[^>]*>[\s\S]*?<\/w:del>/g, '');
|
|
140
176
|
}
|
|
141
|
-
result = result.replace(/<w:del\b[^>]*\/>/g, '');
|
|
142
177
|
|
|
143
178
|
// Step 4: Remove moveFrom entirely (source of moved content)
|
|
179
|
+
// Same self-closing-first ordering as Step 3.
|
|
180
|
+
result = result.replace(/<w:moveFrom\b[^>]*\/>/g, '');
|
|
144
181
|
prevLen = 0;
|
|
145
182
|
while (result.length !== prevLen) {
|
|
146
183
|
prevLen = result.length;
|
|
147
184
|
result = result.replace(/<w:moveFrom\b[^>]*>[\s\S]*?<\/w:moveFrom>/g, '');
|
|
148
185
|
}
|
|
149
|
-
result = result.replace(/<w:moveFrom\b[^>]*\/>/g, '');
|
|
150
186
|
|
|
151
187
|
// Step 5: Unwrap moveTo (keep content, remove wrapper)
|
|
152
188
|
result = result.replace(/<\/w:moveTo>/g, '');
|
|
@@ -200,6 +236,11 @@ export function acceptRevisionsInMemory(
|
|
|
200
236
|
|
|
201
237
|
logger.info('Accepting revisions in-memory', { options: opts });
|
|
202
238
|
|
|
239
|
+
// Paragraphs whose tracked paragraph-mark deletion (w:del in w:pPr/w:rPr)
|
|
240
|
+
// is accepted — they must be merged into their following sibling after the
|
|
241
|
+
// walk completes (ECMA-376 §17.13.5.15).
|
|
242
|
+
const acceptedMarkDeletions: Paragraph[] = [];
|
|
243
|
+
|
|
203
244
|
// Validate move pairs before accepting if moves are being accepted
|
|
204
245
|
// Orphaned moves could result in content loss (moveFrom without moveTo = content deleted)
|
|
205
246
|
if (opts.acceptMoves) {
|
|
@@ -228,7 +269,7 @@ export function acceptRevisionsInMemory(
|
|
|
228
269
|
// Process all paragraphs in the document body
|
|
229
270
|
const paragraphs = doc.getAllParagraphs();
|
|
230
271
|
for (const paragraph of paragraphs) {
|
|
231
|
-
const paragraphResult = acceptRevisionsInParagraph(paragraph, opts);
|
|
272
|
+
const paragraphResult = acceptRevisionsInParagraph(paragraph, opts, acceptedMarkDeletions);
|
|
232
273
|
result.insertionsAccepted += paragraphResult.insertionsAccepted;
|
|
233
274
|
result.deletionsAccepted += paragraphResult.deletionsAccepted;
|
|
234
275
|
result.movesAccepted += paragraphResult.movesAccepted;
|
|
@@ -309,7 +350,11 @@ export function acceptRevisionsInMemory(
|
|
|
309
350
|
|
|
310
351
|
// Process paragraphs in the cell
|
|
311
352
|
for (const paragraph of cell.getParagraphs()) {
|
|
312
|
-
const paragraphResult = acceptRevisionsInParagraph(
|
|
353
|
+
const paragraphResult = acceptRevisionsInParagraph(
|
|
354
|
+
paragraph,
|
|
355
|
+
opts,
|
|
356
|
+
acceptedMarkDeletions
|
|
357
|
+
);
|
|
313
358
|
result.insertionsAccepted += paragraphResult.insertionsAccepted;
|
|
314
359
|
result.deletionsAccepted += paragraphResult.deletionsAccepted;
|
|
315
360
|
result.movesAccepted += paragraphResult.movesAccepted;
|
|
@@ -352,7 +397,7 @@ export function acceptRevisionsInMemory(
|
|
|
352
397
|
for (const element of elements) {
|
|
353
398
|
// Element can be Paragraph or Table - use instanceof for type safety
|
|
354
399
|
if (element instanceof Paragraph) {
|
|
355
|
-
const paragraphResult = acceptRevisionsInParagraph(element, opts);
|
|
400
|
+
const paragraphResult = acceptRevisionsInParagraph(element, opts, acceptedMarkDeletions);
|
|
356
401
|
result.insertionsAccepted += paragraphResult.insertionsAccepted;
|
|
357
402
|
result.deletionsAccepted += paragraphResult.deletionsAccepted;
|
|
358
403
|
result.movesAccepted += paragraphResult.movesAccepted;
|
|
@@ -362,7 +407,11 @@ export function acceptRevisionsInMemory(
|
|
|
362
407
|
for (const row of element.getRows()) {
|
|
363
408
|
for (const cell of row.getCells()) {
|
|
364
409
|
for (const paragraph of cell.getParagraphs()) {
|
|
365
|
-
const paragraphResult = acceptRevisionsInParagraph(
|
|
410
|
+
const paragraphResult = acceptRevisionsInParagraph(
|
|
411
|
+
paragraph,
|
|
412
|
+
opts,
|
|
413
|
+
acceptedMarkDeletions
|
|
414
|
+
);
|
|
366
415
|
result.insertionsAccepted += paragraphResult.insertionsAccepted;
|
|
367
416
|
result.deletionsAccepted += paragraphResult.deletionsAccepted;
|
|
368
417
|
result.movesAccepted += paragraphResult.movesAccepted;
|
|
@@ -395,7 +444,7 @@ export function acceptRevisionsInMemory(
|
|
|
395
444
|
for (const element of elements) {
|
|
396
445
|
// Element can be Paragraph or Table - use instanceof for type safety
|
|
397
446
|
if (element instanceof Paragraph) {
|
|
398
|
-
const paragraphResult = acceptRevisionsInParagraph(element, opts);
|
|
447
|
+
const paragraphResult = acceptRevisionsInParagraph(element, opts, acceptedMarkDeletions);
|
|
399
448
|
result.insertionsAccepted += paragraphResult.insertionsAccepted;
|
|
400
449
|
result.deletionsAccepted += paragraphResult.deletionsAccepted;
|
|
401
450
|
result.movesAccepted += paragraphResult.movesAccepted;
|
|
@@ -405,7 +454,11 @@ export function acceptRevisionsInMemory(
|
|
|
405
454
|
for (const row of element.getRows()) {
|
|
406
455
|
for (const cell of row.getCells()) {
|
|
407
456
|
for (const paragraph of cell.getParagraphs()) {
|
|
408
|
-
const paragraphResult = acceptRevisionsInParagraph(
|
|
457
|
+
const paragraphResult = acceptRevisionsInParagraph(
|
|
458
|
+
paragraph,
|
|
459
|
+
opts,
|
|
460
|
+
acceptedMarkDeletions
|
|
461
|
+
);
|
|
409
462
|
result.insertionsAccepted += paragraphResult.insertionsAccepted;
|
|
410
463
|
result.deletionsAccepted += paragraphResult.deletionsAccepted;
|
|
411
464
|
result.movesAccepted += paragraphResult.movesAccepted;
|
|
@@ -445,7 +498,7 @@ export function acceptRevisionsInMemory(
|
|
|
445
498
|
if (footnoteManager) {
|
|
446
499
|
for (const fn of footnoteManager.getAllFootnotes()) {
|
|
447
500
|
for (const paragraph of fn.getParagraphs()) {
|
|
448
|
-
const paragraphResult = acceptRevisionsInParagraph(paragraph, opts);
|
|
501
|
+
const paragraphResult = acceptRevisionsInParagraph(paragraph, opts, acceptedMarkDeletions);
|
|
449
502
|
result.insertionsAccepted += paragraphResult.insertionsAccepted;
|
|
450
503
|
result.deletionsAccepted += paragraphResult.deletionsAccepted;
|
|
451
504
|
result.movesAccepted += paragraphResult.movesAccepted;
|
|
@@ -457,7 +510,7 @@ export function acceptRevisionsInMemory(
|
|
|
457
510
|
if (endnoteManager) {
|
|
458
511
|
for (const en of endnoteManager.getAllEndnotes()) {
|
|
459
512
|
for (const paragraph of en.getParagraphs()) {
|
|
460
|
-
const paragraphResult = acceptRevisionsInParagraph(paragraph, opts);
|
|
513
|
+
const paragraphResult = acceptRevisionsInParagraph(paragraph, opts, acceptedMarkDeletions);
|
|
461
514
|
result.insertionsAccepted += paragraphResult.insertionsAccepted;
|
|
462
515
|
result.deletionsAccepted += paragraphResult.deletionsAccepted;
|
|
463
516
|
result.movesAccepted += paragraphResult.movesAccepted;
|
|
@@ -484,6 +537,13 @@ export function acceptRevisionsInMemory(
|
|
|
484
537
|
// Disable track changes setting
|
|
485
538
|
doc.disableTrackChanges();
|
|
486
539
|
|
|
540
|
+
// Combine paragraphs whose paragraph-mark deletion was accepted with their
|
|
541
|
+
// following sibling (ECMA-376 §17.13.5.15). Runs after tracking is disabled
|
|
542
|
+
// so the removals are plain splices rather than new tracked deletions.
|
|
543
|
+
if (acceptedMarkDeletions.length > 0) {
|
|
544
|
+
mergeParagraphsWithDeletedMarks(doc, new Set(acceptedMarkDeletions));
|
|
545
|
+
}
|
|
546
|
+
|
|
487
547
|
// Cleanup empty tables if enabled
|
|
488
548
|
// This removes tables that have no visible content after revision acceptance
|
|
489
549
|
// (e.g., tables where all content was deleted via tracked changes)
|
|
@@ -525,7 +585,8 @@ export function acceptRevisionsInMemory(
|
|
|
525
585
|
*/
|
|
526
586
|
function acceptRevisionsInParagraph(
|
|
527
587
|
paragraph: Paragraph,
|
|
528
|
-
options: Required<AcceptRevisionsOptions
|
|
588
|
+
options: Required<AcceptRevisionsOptions>,
|
|
589
|
+
acceptedMarkDeletions?: Paragraph[]
|
|
529
590
|
): AcceptRevisionsResult {
|
|
530
591
|
const result: AcceptRevisionsResult = {
|
|
531
592
|
insertionsAccepted: 0,
|
|
@@ -645,6 +706,20 @@ function acceptRevisionsInParagraph(
|
|
|
645
706
|
}
|
|
646
707
|
}
|
|
647
708
|
newContent.push(item);
|
|
709
|
+
} else if (item instanceof RangeMarker) {
|
|
710
|
+
// Boundary markers for a move / ins / del span. Drop them when the
|
|
711
|
+
// revision they delimit is being accepted so no orphaned range markers
|
|
712
|
+
// remain (the move/ins/del revisions themselves are counted above; the
|
|
713
|
+
// markers carry no content, so they are not counted again).
|
|
714
|
+
const markerType = item.getType();
|
|
715
|
+
if (
|
|
716
|
+
(options.acceptMoves && MOVE_RANGE_MARKER_TYPES.has(markerType)) ||
|
|
717
|
+
(options.acceptInsertions && INS_RANGE_MARKER_TYPES.has(markerType)) ||
|
|
718
|
+
(options.acceptDeletions && DEL_RANGE_MARKER_TYPES.has(markerType))
|
|
719
|
+
) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
newContent.push(item);
|
|
648
723
|
} else {
|
|
649
724
|
// Non-revision content - keep as-is
|
|
650
725
|
newContent.push(item);
|
|
@@ -672,12 +747,17 @@ function acceptRevisionsInParagraph(
|
|
|
672
747
|
}
|
|
673
748
|
|
|
674
749
|
// Clear paragraph mark deletion tracking if accepting deletions
|
|
675
|
-
// This removes the w:del element from w:pPr/w:rPr
|
|
750
|
+
// This removes the w:del element from w:pPr/w:rPr. The paragraph is also
|
|
751
|
+
// recorded for the post-walk merge pass: per ECMA-376 §17.13.5.15 a deleted
|
|
752
|
+
// paragraph mark means this paragraph's contents combine with the FOLLOWING
|
|
753
|
+
// paragraph, so clearing the marker alone would leave a leftover paragraph
|
|
754
|
+
// Word's own Accept All removes.
|
|
676
755
|
if (options.acceptDeletions) {
|
|
677
756
|
const formatting = paragraph.getFormatting();
|
|
678
757
|
if (formatting.paragraphMarkDeletion) {
|
|
679
758
|
paragraph.clearParagraphMarkDeletion();
|
|
680
759
|
result.deletionsAccepted++;
|
|
760
|
+
acceptedMarkDeletions?.push(paragraph);
|
|
681
761
|
}
|
|
682
762
|
}
|
|
683
763
|
|
|
@@ -694,6 +774,111 @@ function acceptRevisionsInParagraph(
|
|
|
694
774
|
return result;
|
|
695
775
|
}
|
|
696
776
|
|
|
777
|
+
/**
|
|
778
|
+
* Merge paragraphs whose tracked paragraph-mark deletion was accepted into
|
|
779
|
+
* their immediately following sibling paragraph.
|
|
780
|
+
*
|
|
781
|
+
* Per ECMA-376 §17.13.5.15, a w:del inside w:pPr/w:rPr marks the paragraph
|
|
782
|
+
* mark (¶) itself as deleted: accepting it means the paragraph's contents are
|
|
783
|
+
* no longer delimited by that mark and combine with the FOLLOWING paragraph.
|
|
784
|
+
* Clearing the marker alone leaves a leftover paragraph (blank line) that
|
|
785
|
+
* Word's own Accept All removes.
|
|
786
|
+
*
|
|
787
|
+
* Merging only happens between siblings of the same container (document body
|
|
788
|
+
* or table cell). A paragraph with no following sibling paragraph — last in
|
|
789
|
+
* its container, or followed by a table/SDT/raw nested content — keeps its
|
|
790
|
+
* content in place, matching Word, which cannot delete a container's final
|
|
791
|
+
* paragraph mark.
|
|
792
|
+
*/
|
|
793
|
+
export function mergeParagraphsWithDeletedMarks(doc: Document, marked: Set<Paragraph>): void {
|
|
794
|
+
if (marked.size === 0) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Body-level siblings
|
|
799
|
+
mergeMarkedSequence(doc.getBodyElements(), marked, (para) => {
|
|
800
|
+
doc.removeElement(para);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// Table-cell siblings (same tables the acceptance walk visits)
|
|
804
|
+
for (const table of doc.getTables()) {
|
|
805
|
+
for (const row of table.getRows()) {
|
|
806
|
+
for (const cell of row.getCells()) {
|
|
807
|
+
// Raw nested content (nested tables/SDTs) at position p renders
|
|
808
|
+
// between paragraph p-1 and paragraph p, so it interrupts sibling
|
|
809
|
+
// adjacency the same way a body-level table does. Capture the
|
|
810
|
+
// boundaries before any removal: removeParagraph keeps positions in
|
|
811
|
+
// sync with live indices, which match this snapshot only up front.
|
|
812
|
+
const blockedBoundaries = new Set(cell.getRawNestedContent().map((item) => item.position));
|
|
813
|
+
mergeMarkedSequence(
|
|
814
|
+
cell.getParagraphs(),
|
|
815
|
+
marked,
|
|
816
|
+
(para) => {
|
|
817
|
+
const index = cell.getParagraphs().indexOf(para);
|
|
818
|
+
if (index !== -1) {
|
|
819
|
+
cell.removeParagraph(index);
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
(index) => blockedBoundaries.has(index)
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Walk one sibling sequence (body elements or cell paragraphs) and fold each
|
|
831
|
+
* run of marked paragraphs into the first unmarked paragraph that follows it.
|
|
832
|
+
* Consecutive marked paragraphs chain: their contents concatenate, in
|
|
833
|
+
* document order, ahead of the surviving paragraph's own content. The
|
|
834
|
+
* surviving paragraph keeps its own pPr — the deleted marks took their
|
|
835
|
+
* properties with them.
|
|
836
|
+
*/
|
|
837
|
+
function mergeMarkedSequence(
|
|
838
|
+
sequence: readonly unknown[],
|
|
839
|
+
marked: Set<Paragraph>,
|
|
840
|
+
removeParagraph: (paragraph: Paragraph) => void,
|
|
841
|
+
blockedBefore?: (index: number) => boolean
|
|
842
|
+
): void {
|
|
843
|
+
let chain: Paragraph[] = [];
|
|
844
|
+
|
|
845
|
+
for (let i = 0; i < sequence.length; i++) {
|
|
846
|
+
const element = sequence[i];
|
|
847
|
+
|
|
848
|
+
if (!(element instanceof Paragraph)) {
|
|
849
|
+
// A non-paragraph block (table, SDT, TOC) is not a valid merge target
|
|
850
|
+
chain = [];
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (blockedBefore?.(i)) {
|
|
855
|
+
chain = [];
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (marked.has(element)) {
|
|
859
|
+
chain.push(element);
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (chain.length > 0) {
|
|
864
|
+
const inherited: ParagraphContent[] = [];
|
|
865
|
+
for (const para of chain) {
|
|
866
|
+
inherited.push(...para.getContent());
|
|
867
|
+
}
|
|
868
|
+
element.setContent([...inherited, ...element.getContent()]);
|
|
869
|
+
for (const para of chain) {
|
|
870
|
+
// Empty before removal so a tracking-bound removal path can never
|
|
871
|
+
// duplicate content that now lives in the surviving paragraph
|
|
872
|
+
para.setContent([]);
|
|
873
|
+
removeParagraph(para);
|
|
874
|
+
}
|
|
875
|
+
chain = [];
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
// A trailing chain has no following sibling: the final paragraph mark of a
|
|
879
|
+
// container cannot be deleted, so those paragraphs stay as-is.
|
|
880
|
+
}
|
|
881
|
+
|
|
697
882
|
/**
|
|
698
883
|
* Check if a paragraph has any revisions
|
|
699
884
|
*/
|
|
@@ -736,12 +921,80 @@ export function countRevisionsByType(doc: Document): Map<RevisionType, number> {
|
|
|
736
921
|
return counts;
|
|
737
922
|
}
|
|
738
923
|
|
|
924
|
+
/**
|
|
925
|
+
* Inline preserved-element types that are pure range/proofing markers.
|
|
926
|
+
* They carry no displayable content, so a table whose cells hold only these
|
|
927
|
+
* (e.g. leftover proofErr markers after all text was deleted via tracked
|
|
928
|
+
* changes) must still be eligible for cleanup.
|
|
929
|
+
*/
|
|
930
|
+
const MARKER_PRESERVED_ELEMENT_TYPES = new Set([
|
|
931
|
+
'w:proofErr',
|
|
932
|
+
'w:permStart',
|
|
933
|
+
'w:permEnd',
|
|
934
|
+
'w:commentRangeStart',
|
|
935
|
+
'w:commentRangeEnd',
|
|
936
|
+
]);
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Check whether a paragraph carries visible content for empty-table cleanup.
|
|
940
|
+
*
|
|
941
|
+
* Paragraph.getText() only surfaces Run/Hyperlink text, so images, shapes,
|
|
942
|
+
* text boxes, fields, pending revisions, and preserved raw XML (loaded-doc
|
|
943
|
+
* hyperlinks, inline SDTs, math) would read as "empty" and the table holding
|
|
944
|
+
* them would be silently deleted. Mirrors TableCell.isParaBlank's duck-typed
|
|
945
|
+
* element detection.
|
|
946
|
+
*/
|
|
947
|
+
function paragraphHasVisibleContent(para: Paragraph): boolean {
|
|
948
|
+
if (para.getText().trim().length > 0) {
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
for (const item of para.getContent()) {
|
|
953
|
+
if (!item) continue;
|
|
954
|
+
|
|
955
|
+
// Revisions left behind by selective acceptance still render their
|
|
956
|
+
// content (e.g. strikethrough deletions) — removing the table would
|
|
957
|
+
// discard tracked changes the caller chose to keep.
|
|
958
|
+
if (item instanceof Revision) {
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Marker-only preserved elements are not content; everything else
|
|
963
|
+
// preserved as raw XML (w:r, w:hyperlink, m:oMath, w:ruby, nested
|
|
964
|
+
// revision wrappers) is.
|
|
965
|
+
if (item instanceof PreservedElement) {
|
|
966
|
+
if (!MARKER_PRESERVED_ELEMENT_TYPES.has(item.getElementType())) {
|
|
967
|
+
return true;
|
|
968
|
+
}
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Duck-typed checks matching TableCell.isParaBlank: ImageRun
|
|
973
|
+
// (getImageElement), Shape (getShapeType), TextBox (getTextContent),
|
|
974
|
+
// Hyperlink (getUrl), Field/ComplexField (getInstruction).
|
|
975
|
+
const candidate = item as unknown as Record<string, unknown>;
|
|
976
|
+
if (
|
|
977
|
+
typeof candidate.getImageElement === 'function' ||
|
|
978
|
+
typeof candidate.getShapeType === 'function' ||
|
|
979
|
+
typeof candidate.getTextContent === 'function' ||
|
|
980
|
+
typeof candidate.getUrl === 'function' ||
|
|
981
|
+
typeof candidate.getInstruction === 'function'
|
|
982
|
+
) {
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
|
|
739
990
|
/**
|
|
740
991
|
* Remove tables that have no visible content after revision acceptance.
|
|
741
992
|
*
|
|
742
|
-
* A table is considered empty if ALL cells in ALL rows have no
|
|
743
|
-
*
|
|
744
|
-
*
|
|
993
|
+
* A table is considered empty only if ALL cells in ALL rows have no visible
|
|
994
|
+
* content: no text, images, shapes, text boxes, fields, hyperlinks, preserved
|
|
995
|
+
* raw XML, or raw nested content (nested tables/SDTs). This handles cases
|
|
996
|
+
* where all table content was deleted via tracked changes - the deletion
|
|
997
|
+
* markers are stripped but the empty table structure remains.
|
|
745
998
|
*
|
|
746
999
|
* @param doc - Document to clean up
|
|
747
1000
|
* @param logger - Logger instance for debug output
|
|
@@ -762,10 +1015,16 @@ function cleanupEmptyTables(doc: Document, logger: ILogger): number {
|
|
|
762
1015
|
for (const row of rows) {
|
|
763
1016
|
const cells = row.getCells();
|
|
764
1017
|
for (const cell of cells) {
|
|
1018
|
+
// Nested tables/SDTs live in raw XML passthrough, never in
|
|
1019
|
+
// paragraphs — a cell whose real content is a nested table would
|
|
1020
|
+
// otherwise read as empty and be deleted.
|
|
1021
|
+
if (cell.hasRawNestedContent()) {
|
|
1022
|
+
hasContent = true;
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
765
1025
|
const paragraphs = cell.getParagraphs();
|
|
766
1026
|
for (const para of paragraphs) {
|
|
767
|
-
|
|
768
|
-
if (text.length > 0) {
|
|
1027
|
+
if (paragraphHasVisibleContent(para)) {
|
|
769
1028
|
hasContent = true;
|
|
770
1029
|
break;
|
|
771
1030
|
}
|
|
@@ -149,6 +149,13 @@ export class RevisionWalker {
|
|
|
149
149
|
// detect it at the table level. The default `w:del` removal in
|
|
150
150
|
// processRevisions only strips the marker itself, leaving a zombie
|
|
151
151
|
// empty row; per spec, accepting the deletion removes the entire row.
|
|
152
|
+
// Tables are checked from the parent first: a table whose rows are ALL
|
|
153
|
+
// tracked-deleted must be removed entirely (a row-less w:tbl violates
|
|
154
|
+
// §17.4.38's at-least-one-row requirement and Word's Accept All drops
|
|
155
|
+
// the whole table), and only the parent can remove the w:tbl itself.
|
|
156
|
+
if (options.acceptDeletions && obj['w:tbl']) {
|
|
157
|
+
RevisionWalker.removeFullyDeletedTables(obj);
|
|
158
|
+
}
|
|
152
159
|
if (options.acceptDeletions && obj['w:tr']) {
|
|
153
160
|
RevisionWalker.filterDeletedRows(obj);
|
|
154
161
|
}
|
|
@@ -193,19 +200,88 @@ export class RevisionWalker {
|
|
|
193
200
|
};
|
|
194
201
|
|
|
195
202
|
if (Array.isArray(rows)) {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
delete tbl['w:tr'];
|
|
200
|
-
} else {
|
|
201
|
-
tbl['w:tr'] = kept;
|
|
202
|
-
}
|
|
203
|
+
const removedIndices = new Set<number>();
|
|
204
|
+
for (let i = 0; i < rows.length; i++) {
|
|
205
|
+
if (isRowDeleted(rows[i])) removedIndices.add(i);
|
|
203
206
|
}
|
|
207
|
+
if (removedIndices.size === 0) return;
|
|
208
|
+
const kept = rows.filter((_, i) => !removedIndices.has(i));
|
|
209
|
+
if (kept.length === 0) {
|
|
210
|
+
delete tbl['w:tr'];
|
|
211
|
+
} else {
|
|
212
|
+
tbl['w:tr'] = kept;
|
|
213
|
+
}
|
|
214
|
+
RevisionWalker.dropOrderedChildEntries(tbl, 'w:tr', removedIndices);
|
|
204
215
|
} else if (isRowDeleted(rows)) {
|
|
205
216
|
delete tbl['w:tr'];
|
|
217
|
+
RevisionWalker.dropOrderedChildEntries(tbl, 'w:tr', new Set([0]));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Remove `<w:tbl>` children whose rows are ALL tracked-deleted.
|
|
223
|
+
*
|
|
224
|
+
* Runs at the table's parent because filterDeletedRows (invoked on the
|
|
225
|
+
* table itself) has no reference back to the container holding the
|
|
226
|
+
* `w:tbl` key. Skips tables that still carry potential row containers
|
|
227
|
+
* (w:ins / w:moveTo wrappers, w:sdt, w:customXml) — their surviving rows
|
|
228
|
+
* are only materialized as direct `w:tr` children later, when the
|
|
229
|
+
* wrappers are unwrapped during recursion.
|
|
230
|
+
*/
|
|
231
|
+
private static removeFullyDeletedTables(parent: any): void {
|
|
232
|
+
const tables = parent['w:tbl'];
|
|
233
|
+
const becomesRowless = (tbl: any): boolean => {
|
|
234
|
+
// Tables with no rows at all are pre-existing structures, not the
|
|
235
|
+
// result of accepting a deletion — leave them untouched.
|
|
236
|
+
if (!tbl || typeof tbl !== 'object' || tbl['w:tr'] === undefined) return false;
|
|
237
|
+
RevisionWalker.filterDeletedRows(tbl);
|
|
238
|
+
return (
|
|
239
|
+
tbl['w:tr'] === undefined &&
|
|
240
|
+
!tbl['w:ins'] &&
|
|
241
|
+
!tbl['w:moveTo'] &&
|
|
242
|
+
!tbl['w:sdt'] &&
|
|
243
|
+
!tbl['w:customXml']
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (Array.isArray(tables)) {
|
|
248
|
+
const removedIndices = new Set<number>();
|
|
249
|
+
for (let i = 0; i < tables.length; i++) {
|
|
250
|
+
if (becomesRowless(tables[i])) removedIndices.add(i);
|
|
251
|
+
}
|
|
252
|
+
if (removedIndices.size === 0) return;
|
|
253
|
+
const kept = tables.filter((_: any, i: number) => !removedIndices.has(i));
|
|
254
|
+
if (kept.length === 0) {
|
|
255
|
+
delete parent['w:tbl'];
|
|
256
|
+
} else {
|
|
257
|
+
parent['w:tbl'] = kept;
|
|
258
|
+
}
|
|
259
|
+
RevisionWalker.dropOrderedChildEntries(parent, 'w:tbl', removedIndices);
|
|
260
|
+
} else if (becomesRowless(tables)) {
|
|
261
|
+
delete parent['w:tbl'];
|
|
262
|
+
RevisionWalker.dropOrderedChildEntries(parent, 'w:tbl', new Set([0]));
|
|
206
263
|
}
|
|
207
264
|
}
|
|
208
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Drop specific {type, index} entries from `_orderedChildren` and
|
|
268
|
+
* re-index the survivors. The serializer maps each entry onto the
|
|
269
|
+
* element array by index, so stale entries left after filtering would
|
|
270
|
+
* shift surviving elements relative to inter-element siblings (e.g. a
|
|
271
|
+
* w:bookmarkEnd between table rows) or silently drop the tail.
|
|
272
|
+
*/
|
|
273
|
+
private static dropOrderedChildEntries(
|
|
274
|
+
parent: any,
|
|
275
|
+
type: string,
|
|
276
|
+
removedIndices: Set<number>
|
|
277
|
+
): void {
|
|
278
|
+
if (!parent._orderedChildren) return;
|
|
279
|
+
parent._orderedChildren = parent._orderedChildren.filter(
|
|
280
|
+
(c: OrderedChildInfo) => c.type !== type || !removedIndices.has(c.index)
|
|
281
|
+
);
|
|
282
|
+
RevisionWalker.reindexOrderedChildren(parent._orderedChildren);
|
|
283
|
+
}
|
|
284
|
+
|
|
209
285
|
/**
|
|
210
286
|
* Process revision elements at the current level
|
|
211
287
|
*/
|