docxmlater 11.0.10 → 12.1.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 +53 -13
- package/dist/core/Document.d.ts +29 -3
- package/dist/core/Document.d.ts.map +1 -1
- package/dist/core/Document.js +733 -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 +735 -172
- 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 +223 -39
- 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/acceptRevisions.js +20 -11
- package/dist/esm/processors/acceptRevisions.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 +9 -3
- package/dist/processors/RevisionWalker.d.ts.map +1 -1
- package/dist/processors/RevisionWalker.js +223 -39
- 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/acceptRevisions.d.ts +1 -0
- package/dist/processors/acceptRevisions.d.ts.map +1 -1
- package/dist/processors/acceptRevisions.js +21 -11
- package/dist/processors/acceptRevisions.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 +1184 -242
- 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 +390 -46
- package/src/processors/SelectiveRevisionAcceptor.ts +164 -28
- package/src/processors/acceptRevisions.ts +67 -17
- 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
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
import type { Document } from '../core/Document.js';
|
|
16
16
|
import type { Paragraph, ParagraphContent } from '../elements/Paragraph.js';
|
|
17
17
|
import { Revision, RevisionType } from '../elements/Revision.js';
|
|
18
|
-
import type { Run } from '../elements/Run.js';
|
|
18
|
+
import type { Run, RunFormatting } from '../elements/Run.js';
|
|
19
19
|
import { isRunContent, isHyperlinkContent } from '../elements/RevisionContent.js';
|
|
20
20
|
import { ChangeCategory } from './ChangelogGenerator.js';
|
|
21
21
|
import { SelectionCriteria } from './RevisionAwareProcessor.js';
|
|
22
|
+
import { mergeParagraphsWithDeletedMarks } from './InMemoryRevisionAcceptor.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Result of selective revision acceptance.
|
|
@@ -62,9 +63,23 @@ export class SelectiveRevisionAcceptor {
|
|
|
62
63
|
const hasFullApi = typeof (doc as any).getAllParagraphs === 'function';
|
|
63
64
|
|
|
64
65
|
if (hasFullApi) {
|
|
66
|
+
const acceptedMarkDeletions: Paragraph[] = [];
|
|
65
67
|
this.walkAllParagraphs(doc, (paragraph) => {
|
|
66
|
-
this.processSelectiveParagraph(
|
|
68
|
+
this.processSelectiveParagraph(
|
|
69
|
+
paragraph,
|
|
70
|
+
criteria,
|
|
71
|
+
'accept',
|
|
72
|
+
accepted,
|
|
73
|
+
remaining,
|
|
74
|
+
acceptedMarkDeletions
|
|
75
|
+
);
|
|
67
76
|
});
|
|
77
|
+
// Accepted paragraph-mark deletions combine the paragraph with its
|
|
78
|
+
// following sibling (ECMA-376 §17.13.5.15) — same merge the all-or-
|
|
79
|
+
// nothing acceptor performs.
|
|
80
|
+
if (acceptedMarkDeletions.length > 0) {
|
|
81
|
+
mergeParagraphsWithDeletedMarks(doc, new Set(acceptedMarkDeletions));
|
|
82
|
+
}
|
|
68
83
|
} else {
|
|
69
84
|
// Fallback: Filter revisions from RevisionManager only (for backward compatibility)
|
|
70
85
|
const revisionManager = doc.getRevisionManager();
|
|
@@ -257,7 +272,8 @@ export class SelectiveRevisionAcceptor {
|
|
|
257
272
|
criteria: SelectionCriteria,
|
|
258
273
|
action: 'accept' | 'reject',
|
|
259
274
|
processedIds: string[],
|
|
260
|
-
remainingIds: string[]
|
|
275
|
+
remainingIds: string[],
|
|
276
|
+
acceptedMarkDeletions?: Paragraph[]
|
|
261
277
|
): void {
|
|
262
278
|
const content = paragraph.getContent();
|
|
263
279
|
const newContent: ParagraphContent[] = [];
|
|
@@ -267,15 +283,18 @@ export class SelectiveRevisionAcceptor {
|
|
|
267
283
|
const revisionId = item.getId().toString();
|
|
268
284
|
|
|
269
285
|
if (this.matchesCriteria(item, criteria)) {
|
|
270
|
-
// This revision matches the criteria - process it
|
|
271
|
-
processedIds.push(revisionId);
|
|
272
|
-
|
|
273
286
|
if (action === 'accept') {
|
|
274
287
|
// Accept: Transform based on revision type
|
|
288
|
+
processedIds.push(revisionId);
|
|
275
289
|
this.acceptRevisionItem(item, newContent);
|
|
276
|
-
} else {
|
|
290
|
+
} else if (this.rejectRevisionItem(item, newContent, paragraph)) {
|
|
277
291
|
// Reject: Transform opposite of accept
|
|
278
|
-
|
|
292
|
+
processedIds.push(revisionId);
|
|
293
|
+
} else {
|
|
294
|
+
// The revision was kept in place (its previous-property
|
|
295
|
+
// snapshot could not be restored), so report it as remaining
|
|
296
|
+
// rather than rejected — it must stay in the RevisionManager.
|
|
297
|
+
remainingIds.push(revisionId);
|
|
279
298
|
}
|
|
280
299
|
} else {
|
|
281
300
|
// This revision doesn't match - keep it
|
|
@@ -291,13 +310,18 @@ export class SelectiveRevisionAcceptor {
|
|
|
291
310
|
// Replace paragraph content with the transformed content
|
|
292
311
|
paragraph.setContent(newContent);
|
|
293
312
|
|
|
294
|
-
// Handle paragraph mark revision markers (w:del/w:ins in w:pPr/w:rPr)
|
|
295
|
-
//
|
|
296
|
-
//
|
|
313
|
+
// Handle paragraph mark revision markers (w:del/w:ins in w:pPr/w:rPr).
|
|
314
|
+
// Rejecting a mark deletion just restores the mark (clear the marker);
|
|
315
|
+
// ACCEPTING one means the paragraph's contents combine with the following
|
|
316
|
+
// paragraph (ECMA-376 §17.13.5.15), so the paragraph is recorded for the
|
|
317
|
+
// post-walk merge pass in addition to clearing the marker.
|
|
297
318
|
const formatting = paragraph.getFormatting();
|
|
298
319
|
if (formatting.paragraphMarkDeletion) {
|
|
299
320
|
const del = formatting.paragraphMarkDeletion;
|
|
300
321
|
if (this.matchesMarkerCriteria(del, criteria)) {
|
|
322
|
+
if (action === 'accept') {
|
|
323
|
+
acceptedMarkDeletions?.push(paragraph);
|
|
324
|
+
}
|
|
301
325
|
paragraph.clearParagraphMarkDeletion();
|
|
302
326
|
processedIds.push(del.id.toString());
|
|
303
327
|
} else {
|
|
@@ -415,8 +439,17 @@ export class SelectiveRevisionAcceptor {
|
|
|
415
439
|
* Reject a single revision item (opposite of accept).
|
|
416
440
|
* - Rejecting an insertion removes the content
|
|
417
441
|
* - Rejecting a deletion keeps the content (unwraps it)
|
|
442
|
+
* - Rejecting a run/paragraph property change restores the previous
|
|
443
|
+
* properties snapshot (ECMA-376 §17.13.5.30/§17.13.5.31)
|
|
444
|
+
*
|
|
445
|
+
* @returns true if the revision was rejected, false if it was kept in
|
|
446
|
+
* place because its previous-property snapshot could not be restored
|
|
418
447
|
*/
|
|
419
|
-
private static rejectRevisionItem(
|
|
448
|
+
private static rejectRevisionItem(
|
|
449
|
+
revision: Revision,
|
|
450
|
+
newContent: ParagraphContent[],
|
|
451
|
+
paragraph: Paragraph
|
|
452
|
+
): boolean {
|
|
420
453
|
const revisionType = revision.getType();
|
|
421
454
|
const childContent = revision.getContent();
|
|
422
455
|
|
|
@@ -424,7 +457,7 @@ export class SelectiveRevisionAcceptor {
|
|
|
424
457
|
case 'insert':
|
|
425
458
|
case 'moveTo':
|
|
426
459
|
// Reject insertion: Remove the inserted content entirely
|
|
427
|
-
|
|
460
|
+
return true;
|
|
428
461
|
|
|
429
462
|
case 'delete':
|
|
430
463
|
case 'moveFrom':
|
|
@@ -436,19 +469,46 @@ export class SelectiveRevisionAcceptor {
|
|
|
436
469
|
newContent.push(child);
|
|
437
470
|
}
|
|
438
471
|
}
|
|
439
|
-
|
|
472
|
+
return true;
|
|
473
|
+
|
|
474
|
+
case 'runPropertiesChange': {
|
|
475
|
+
const previous = revision.getPreviousProperties();
|
|
476
|
+
if (!previous) {
|
|
477
|
+
// No snapshot to restore from — keep the tracked change so the
|
|
478
|
+
// prior formatting is not silently lost.
|
|
479
|
+
newContent.push(revision);
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
for (const child of childContent) {
|
|
483
|
+
if (isRunContent(child)) {
|
|
484
|
+
const run = child as Run;
|
|
485
|
+
// w:rPrChange carries the complete pre-change run properties,
|
|
486
|
+
// so rejecting replaces (not merges) the run's formatting.
|
|
487
|
+
// Run.formatting is private; written directly the same way
|
|
488
|
+
// DocumentParser populates parsed properties.
|
|
489
|
+
(run as unknown as { formatting: RunFormatting }).formatting = {
|
|
490
|
+
...(previous as RunFormatting),
|
|
491
|
+
};
|
|
492
|
+
newContent.push(run);
|
|
493
|
+
} else if (isHyperlinkContent(child)) {
|
|
494
|
+
newContent.push(child);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
440
499
|
|
|
441
|
-
case '
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
500
|
+
case 'paragraphPropertiesChange': {
|
|
501
|
+
const previous = revision.getPreviousProperties();
|
|
502
|
+
if (!previous) {
|
|
503
|
+
newContent.push(revision);
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
// The snapshot uses the same key names as ParagraphFormatting
|
|
507
|
+
// (see Revision.PROPERTY_KEY_TO_ELEMENT), so the recorded values
|
|
508
|
+
// are written back onto the containing paragraph. Only recorded
|
|
509
|
+
// keys are touched: internal bookkeeping fields (revision markers,
|
|
510
|
+
// paraId) are not part of a w:pPrChange snapshot.
|
|
511
|
+
Object.assign(paragraph.formatting, previous);
|
|
452
512
|
for (const child of childContent) {
|
|
453
513
|
if (isRunContent(child)) {
|
|
454
514
|
newContent.push(child as Run);
|
|
@@ -456,11 +516,26 @@ export class SelectiveRevisionAcceptor {
|
|
|
456
516
|
newContent.push(child);
|
|
457
517
|
}
|
|
458
518
|
}
|
|
459
|
-
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
case 'tablePropertiesChange':
|
|
523
|
+
case 'tableExceptionPropertiesChange':
|
|
524
|
+
case 'tableRowPropertiesChange':
|
|
525
|
+
case 'tableCellPropertiesChange':
|
|
526
|
+
case 'sectionPropertiesChange':
|
|
527
|
+
case 'numberingChange':
|
|
528
|
+
// The element these snapshots belong to (table/row/cell/section/
|
|
529
|
+
// numbering definition) is not reachable from paragraph content,
|
|
530
|
+
// so the previous properties cannot be restored here. Keep the
|
|
531
|
+
// revision in place rather than discarding the snapshot.
|
|
532
|
+
newContent.push(revision);
|
|
533
|
+
return false;
|
|
460
534
|
|
|
461
535
|
default:
|
|
462
536
|
// Unknown type - keep the revision as-is for safety
|
|
463
537
|
newContent.push(revision);
|
|
538
|
+
return true;
|
|
464
539
|
}
|
|
465
540
|
}
|
|
466
541
|
|
|
@@ -477,8 +552,69 @@ export class SelectiveRevisionAcceptor {
|
|
|
477
552
|
criteria: SelectionCriteria,
|
|
478
553
|
action: 'accept' | 'reject'
|
|
479
554
|
): SelectiveAcceptResult {
|
|
480
|
-
|
|
481
|
-
|
|
555
|
+
const processed: string[] = [];
|
|
556
|
+
const remaining: string[] = [];
|
|
557
|
+
|
|
558
|
+
// Delegating to accept()/reject() would mutate paragraph content and
|
|
559
|
+
// splice revisions out of the RevisionManager, so preview classifies
|
|
560
|
+
// with the same traversal and criteria but never transforms anything.
|
|
561
|
+
const hasFullApi = typeof (doc as any).getAllParagraphs === 'function';
|
|
562
|
+
|
|
563
|
+
if (hasFullApi) {
|
|
564
|
+
this.walkAllParagraphs(doc, (paragraph) => {
|
|
565
|
+
for (const item of paragraph.getContent()) {
|
|
566
|
+
if (item instanceof Revision) {
|
|
567
|
+
if (this.matchesCriteria(item, criteria)) {
|
|
568
|
+
processed.push(item.getId().toString());
|
|
569
|
+
} else {
|
|
570
|
+
remaining.push(item.getId().toString());
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const formatting = paragraph.getFormatting();
|
|
576
|
+
if (formatting.paragraphMarkDeletion) {
|
|
577
|
+
const del = formatting.paragraphMarkDeletion;
|
|
578
|
+
if (this.matchesMarkerCriteria(del, criteria)) {
|
|
579
|
+
processed.push(del.id.toString());
|
|
580
|
+
} else {
|
|
581
|
+
remaining.push(del.id.toString());
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (formatting.paragraphMarkInsertion) {
|
|
585
|
+
const ins = formatting.paragraphMarkInsertion;
|
|
586
|
+
if (this.matchesMarkerCriteria(ins, criteria)) {
|
|
587
|
+
processed.push(ins.id.toString());
|
|
588
|
+
} else {
|
|
589
|
+
remaining.push(ins.id.toString());
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
} else {
|
|
594
|
+
// Fallback: Filter revisions from RevisionManager only (for backward compatibility)
|
|
595
|
+
const revisionManager = doc.getRevisionManager();
|
|
596
|
+
if (revisionManager) {
|
|
597
|
+
for (const rev of revisionManager.getAllRevisions()) {
|
|
598
|
+
if (this.matchesCriteria(rev, criteria)) {
|
|
599
|
+
processed.push(rev.getId().toString());
|
|
600
|
+
} else {
|
|
601
|
+
remaining.push(rev.getId().toString());
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
accepted: action === 'accept' ? processed : [],
|
|
609
|
+
rejected: action === 'reject' ? processed : [],
|
|
610
|
+
remaining,
|
|
611
|
+
summary: {
|
|
612
|
+
totalProcessed: processed.length + remaining.length,
|
|
613
|
+
acceptedCount: action === 'accept' ? processed.length : 0,
|
|
614
|
+
rejectedCount: action === 'reject' ? processed.length : 0,
|
|
615
|
+
remainingCount: remaining.length,
|
|
616
|
+
},
|
|
617
|
+
};
|
|
482
618
|
}
|
|
483
619
|
|
|
484
620
|
/**
|
|
@@ -5,16 +5,19 @@ import { RevisionWalker } from './RevisionWalker.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* Markers covered: w:ins, w:del, w:moveFrom, w:moveTo,
|
|
7
7
|
* w:rPrChange, w:pPrChange, w:tblPrChange, w:tblPrExChange,
|
|
8
|
-
* w:trPrChange, w:tcPrChange, w:sectPrChange, w:
|
|
9
|
-
* w:cellIns, w:cellDel, w:cellMerge, and any
|
|
8
|
+
* w:trPrChange, w:tcPrChange, w:sectPrChange, w:tblGridChange,
|
|
9
|
+
* w:numberingChange, w:cellIns, w:cellDel, w:cellMerge, and any
|
|
10
|
+
* *RangeStart/End markers.
|
|
10
11
|
*/
|
|
11
12
|
const REVISION_MARKER_PATTERN =
|
|
12
|
-
/<w:(?:ins|del|moveFrom|moveTo|rPrChange|pPrChange|tblPrChange|tblPrExChange|trPrChange|tcPrChange|sectPrChange|numberingChange|cellIns|cellDel|cellMerge|moveFromRangeStart|moveFromRangeEnd|moveToRangeStart|moveToRangeEnd|customXmlInsRangeStart|customXmlInsRangeEnd|customXmlDelRangeStart|customXmlDelRangeEnd|customXmlMoveFromRangeStart|customXmlMoveFromRangeEnd|customXmlMoveToRangeStart|customXmlMoveToRangeEnd)\b/;
|
|
13
|
+
/<w:(?:ins|del|moveFrom|moveTo|rPrChange|pPrChange|tblPrChange|tblPrExChange|trPrChange|tcPrChange|sectPrChange|tblGridChange|numberingChange|cellIns|cellDel|cellMerge|moveFromRangeStart|moveFromRangeEnd|moveToRangeStart|moveToRangeEnd|customXmlInsRangeStart|customXmlInsRangeEnd|customXmlDelRangeStart|customXmlDelRangeEnd|customXmlMoveFromRangeStart|customXmlMoveFromRangeEnd|customXmlMoveToRangeStart|customXmlMoveToRangeEnd)\b/;
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* Applies tracked changes in a Word document per Microsoft's OpenXML SDK
|
|
17
|
+
* pattern, in either direction (see `mode`).
|
|
16
18
|
*
|
|
17
|
-
* This implementation uses DOM-based tree walking for reliability
|
|
19
|
+
* This implementation uses DOM-based tree walking for reliability. In 'accept'
|
|
20
|
+
* mode (the post-edit document):
|
|
18
21
|
* 1. Insertions (<w:ins>): Keep content, remove wrapper tags
|
|
19
22
|
* 2. Deletions (<w:del>): Remove entirely (content and tags)
|
|
20
23
|
* 3. Move From (<w:moveFrom>): Remove entirely (source of move)
|
|
@@ -22,17 +25,27 @@ const REVISION_MARKER_PATTERN =
|
|
|
22
25
|
* 5. Property changes: Remove all *Change elements
|
|
23
26
|
* 6. Range markers: Remove all boundary markers
|
|
24
27
|
*
|
|
28
|
+
* In 'reject' mode every content decision is inverted and property changes
|
|
29
|
+
* restore the previous formatting (see RevisionWalker).
|
|
30
|
+
*
|
|
25
31
|
* Also cleans up metadata in people.xml, settings.xml, and core.xml
|
|
26
32
|
*
|
|
27
33
|
* @see https://learn.microsoft.com/en-us/office/open-xml/how-to-accept-all-revisions
|
|
28
34
|
*/
|
|
29
|
-
class
|
|
35
|
+
class RevisionProcessor {
|
|
30
36
|
private zipHandler: ZipHandler;
|
|
31
37
|
/** Feature flag for DOM-based processing (default: true) */
|
|
32
38
|
private useDomBasedProcessing = true;
|
|
39
|
+
/**
|
|
40
|
+
* Processing direction. 'accept' yields the post-edit document; 'reject'
|
|
41
|
+
* reverts to the original pre-edit document (the exact inverse). Reject is
|
|
42
|
+
* DOM-only — the regex fallback cannot restore previous formatting.
|
|
43
|
+
*/
|
|
44
|
+
private mode: 'accept' | 'reject';
|
|
33
45
|
|
|
34
|
-
constructor(zipHandler: ZipHandler) {
|
|
46
|
+
constructor(zipHandler: ZipHandler, mode: 'accept' | 'reject' = 'accept') {
|
|
35
47
|
this.zipHandler = zipHandler;
|
|
48
|
+
this.mode = mode;
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
/**
|
|
@@ -43,9 +56,10 @@ class RevisionAcceptor {
|
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
/**
|
|
46
|
-
* Main
|
|
59
|
+
* Main entry point: apply every tracked change in the document in the
|
|
60
|
+
* configured direction (accept or reject).
|
|
47
61
|
*/
|
|
48
|
-
public async
|
|
62
|
+
public async process(): Promise<void> {
|
|
49
63
|
// Process document.xml
|
|
50
64
|
await this.processDocumentPart('word/document.xml');
|
|
51
65
|
|
|
@@ -91,11 +105,13 @@ class RevisionAcceptor {
|
|
|
91
105
|
// byte-for-byte passthrough preservation in downstream consumers
|
|
92
106
|
// (e.g., comments.xml round-trip with no tracked changes inside).
|
|
93
107
|
const xml = this.zipHandler.getFileAsString(partPath);
|
|
94
|
-
if (!xml || !
|
|
108
|
+
if (!xml || !RevisionProcessor.containsRevisionMarkup(xml)) {
|
|
95
109
|
return;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
// Reject must restore previous formatting from *Change snapshots, which the
|
|
113
|
+
// regex fallback cannot do — always take the DOM path when rejecting.
|
|
114
|
+
if (this.useDomBasedProcessing || this.mode === 'reject') {
|
|
99
115
|
return this.processDocumentPartDOM(partPath);
|
|
100
116
|
}
|
|
101
117
|
return this.processDocumentPartRegex(partPath);
|
|
@@ -126,14 +142,22 @@ class RevisionAcceptor {
|
|
|
126
142
|
|
|
127
143
|
// Step 2: Process revisions using DOM walker
|
|
128
144
|
const processed = RevisionWalker.processTree(parsed, {
|
|
145
|
+
mode: this.mode,
|
|
129
146
|
acceptInsertions: true,
|
|
130
147
|
acceptDeletions: true,
|
|
131
148
|
acceptMoves: true,
|
|
132
149
|
acceptPropertyChanges: true,
|
|
133
150
|
});
|
|
134
151
|
|
|
135
|
-
// Step 3: Handle image relationship ID remapping
|
|
136
|
-
|
|
152
|
+
// Step 3: Handle image relationship ID remapping.
|
|
153
|
+
// Only relevant when accepting: unwrapping an inserted image can surface a
|
|
154
|
+
// relationship ID that Word reused across tracked-change contexts, so a
|
|
155
|
+
// fresh unique ID is assigned. Rejecting discards insertions and restores
|
|
156
|
+
// the original content, whose relationship IDs are already unique, so
|
|
157
|
+
// remapping would needlessly rewrite valid references.
|
|
158
|
+
if (this.mode === 'accept') {
|
|
159
|
+
this.remapImageRelationshipsInTree(processed);
|
|
160
|
+
}
|
|
137
161
|
|
|
138
162
|
// Step 4: Convert back to XML
|
|
139
163
|
const outputXml =
|
|
@@ -768,8 +792,34 @@ class RevisionAcceptor {
|
|
|
768
792
|
* Convenience function to accept all revisions in a document
|
|
769
793
|
*/
|
|
770
794
|
export async function acceptAllRevisions(zipHandler: ZipHandler): Promise<void> {
|
|
771
|
-
const acceptor = new
|
|
772
|
-
await acceptor.
|
|
795
|
+
const acceptor = new RevisionProcessor(zipHandler);
|
|
796
|
+
await acceptor.process();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Convenience function to reject all revisions in a document, reverting it to
|
|
801
|
+
* its original pre-edit state (the exact inverse of {@link acceptAllRevisions}):
|
|
802
|
+
*
|
|
803
|
+
* 1. Insertions (`w:ins`): removed — inserted content is discarded
|
|
804
|
+
* 2. Deletions (`w:del`): unwrapped and `w:delText` restored to `w:t` — deleted
|
|
805
|
+
* content reappears
|
|
806
|
+
* 3. MoveFrom (`w:moveFrom`): unwrapped — the original source content is restored
|
|
807
|
+
* 4. MoveTo (`w:moveTo`): removed — the moved-to destination is discarded
|
|
808
|
+
* 5. Property changes (`w:rPrChange`, `w:pPrChange`, ...): previous formatting
|
|
809
|
+
* stored inside the change is restored, then the marker is removed
|
|
810
|
+
* 6. Inserted rows/tables are removed; deleted rows/tables are kept
|
|
811
|
+
* 7. Range markers are removed and revision metadata is cleaned up
|
|
812
|
+
*
|
|
813
|
+
* Limitations (shared with {@link acceptAllRevisions}): cell-level markers
|
|
814
|
+
* (`w:cellIns`/`w:cellDel`/`w:cellMerge`) are left in place, and the legacy
|
|
815
|
+
* `w:numberingChange` is dropped rather than reverted (it embeds no recoverable
|
|
816
|
+
* previous value).
|
|
817
|
+
*
|
|
818
|
+
* @param zipHandler - The ZipHandler containing the DOCX package
|
|
819
|
+
*/
|
|
820
|
+
export async function rejectAllRevisions(zipHandler: ZipHandler): Promise<void> {
|
|
821
|
+
const rejector = new RevisionProcessor(zipHandler, 'reject');
|
|
822
|
+
await rejector.process();
|
|
773
823
|
}
|
|
774
824
|
|
|
775
825
|
/**
|
|
@@ -786,6 +836,6 @@ export async function acceptAllRevisions(zipHandler: ZipHandler): Promise<void>
|
|
|
786
836
|
* @param zipHandler - The ZipHandler containing the DOCX package
|
|
787
837
|
*/
|
|
788
838
|
export function cleanupRevisionMetadata(zipHandler: ZipHandler): void {
|
|
789
|
-
const
|
|
790
|
-
|
|
839
|
+
const processor = new RevisionProcessor(zipHandler);
|
|
840
|
+
processor.cleanupMetadata();
|
|
791
841
|
}
|
|
@@ -78,25 +78,31 @@ const BIT_POSITIONS: Record<keyof CnfStyleFlags, number> = {
|
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Priority order for conditional formatting resolution
|
|
81
|
-
*
|
|
82
|
-
*
|
|
81
|
+
*
|
|
82
|
+
* ECMA-376 §17.7.6.6 applies conditional formats in sequence (banding, then
|
|
83
|
+
* first/last row, first/last column, then corners top-left through
|
|
84
|
+
* bottom-right) with subsequent formats overriding earlier ones. First-match
|
|
85
|
+
* consumers need the opposite: the last-applied (winning) member of each
|
|
86
|
+
* group listed first, so when two conditionals in the same group co-apply
|
|
87
|
+
* (e.g. firstRow + lastRow in a single-row table) resolution picks the one
|
|
88
|
+
* Word actually renders.
|
|
83
89
|
*/
|
|
84
90
|
export const CONDITIONAL_PRIORITY_ORDER: (keyof CnfStyleFlags)[] = [
|
|
85
|
-
// Corner cells (most specific)
|
|
86
|
-
'nwCell',
|
|
87
|
-
'neCell',
|
|
88
|
-
'swCell',
|
|
91
|
+
// Corner cells (most specific; spec applies nw, ne, sw, se — se wins)
|
|
89
92
|
'seCell',
|
|
90
|
-
|
|
91
|
-
'
|
|
93
|
+
'swCell',
|
|
94
|
+
'neCell',
|
|
95
|
+
'nwCell',
|
|
96
|
+
// Edge rows/columns (within each pair, last is applied after first)
|
|
92
97
|
'lastRow',
|
|
93
|
-
'
|
|
98
|
+
'firstRow',
|
|
94
99
|
'lastCol',
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
'firstCol',
|
|
101
|
+
// Banding (least specific; even banding is applied after odd)
|
|
97
102
|
'band2Horz',
|
|
98
|
-
'
|
|
103
|
+
'band1Horz',
|
|
99
104
|
'band2Vert',
|
|
105
|
+
'band1Vert',
|
|
100
106
|
];
|
|
101
107
|
|
|
102
108
|
/**
|
|
@@ -67,19 +67,30 @@ class TrackedChangesStripper {
|
|
|
67
67
|
// STEP 2: Process insertions - keep content, remove wrapper
|
|
68
68
|
content = this.processInsertions(content);
|
|
69
69
|
|
|
70
|
-
// STEP 3:
|
|
70
|
+
// STEP 3: Remove rows whose w:trPr carries a row-deletion marker
|
|
71
|
+
// (self-closing <w:del/> per ECMA-376 §17.13.5.14 marks the ENTIRE
|
|
72
|
+
// row as deleted). Must run before the marker itself is stripped in
|
|
73
|
+
// STEP 4, otherwise the row would silently survive as un-deleted.
|
|
74
|
+
content = this.removeDeletedRows(content);
|
|
75
|
+
|
|
76
|
+
// STEP 4: Remove self-closing revision markers (paragraph-mark
|
|
77
|
+
// deletions in w:pPr/w:rPr per ECMA-376 §17.13.5.15, etc.). Must run
|
|
78
|
+
// BEFORE the block-removal steps: their [^>]* also matches the
|
|
79
|
+
// trailing '/' of `<w:del .../>`, which would turn the marker into an
|
|
80
|
+
// opening tag and swallow everything up to the next closing tag,
|
|
81
|
+
// corrupting the part.
|
|
82
|
+
content = this.removeSelfClosingRevisionTags(content);
|
|
83
|
+
|
|
84
|
+
// STEP 5: Process deletions - remove entirely
|
|
71
85
|
content = this.processDeletions(content);
|
|
72
86
|
|
|
73
|
-
// STEP
|
|
87
|
+
// STEP 6: Process move operations
|
|
74
88
|
content = this.processMoveFrom(content);
|
|
75
89
|
content = this.processMoveTo(content);
|
|
76
90
|
|
|
77
|
-
// STEP
|
|
91
|
+
// STEP 7: Remove all property change tracking elements
|
|
78
92
|
content = this.removePropertyChanges(content);
|
|
79
93
|
|
|
80
|
-
// STEP 6: Remove any remaining self-closing revision tags
|
|
81
|
-
content = this.removeSelfClosingRevisionTags(content);
|
|
82
|
-
|
|
83
94
|
// Update the file in the zip
|
|
84
95
|
this.zipHandler.updateFile(partPath, content);
|
|
85
96
|
}
|
|
@@ -160,6 +171,30 @@ class TrackedChangesStripper {
|
|
|
160
171
|
return result;
|
|
161
172
|
}
|
|
162
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Remove entire <w:tr> rows marked as tracked deletions.
|
|
176
|
+
*
|
|
177
|
+
* Per ECMA-376 Part 1 §17.13.5.14, a self-closing `<w:del/>` inside
|
|
178
|
+
* `<w:trPr>` marks the whole row as deleted — stripping just the marker
|
|
179
|
+
* would resurrect the row instead of removing it. Mirrors the row-level
|
|
180
|
+
* pass in acceptRevisions.ts: the negative lookaheads keep the match
|
|
181
|
+
* anchored to a single row (it cannot slip past a cell boundary or a
|
|
182
|
+
* nested-table row and latch onto a later row's trPr-with-del).
|
|
183
|
+
*/
|
|
184
|
+
private removeDeletedRows(xml: string): string {
|
|
185
|
+
const rowDelPattern =
|
|
186
|
+
/<w:tr\b[^>]*>(?:(?!<w:tc\b|<w:tr\b|<\/w:tr>)[\s\S])*?<w:trPr\b(?:\s[^>]*)?>(?:(?!<\/w:trPr>)[\s\S])*?<w:del\b[^>]*\/>(?:(?!<\/w:trPr>)[\s\S])*?<\/w:trPr>(?:(?!<w:tr\b|<\/w:tr>)[\s\S])*?<\/w:tr>/g;
|
|
187
|
+
|
|
188
|
+
let result = xml;
|
|
189
|
+
let previousLength = 0;
|
|
190
|
+
while (result.length !== previousLength) {
|
|
191
|
+
previousLength = result.length;
|
|
192
|
+
result = result.replace(rowDelPattern, '');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
163
198
|
/**
|
|
164
199
|
* Process <w:del> elements - remove entire element
|
|
165
200
|
* Uses iterative replacement to handle nested deletions
|
|
@@ -241,13 +276,15 @@ class TrackedChangesStripper {
|
|
|
241
276
|
|
|
242
277
|
/**
|
|
243
278
|
* Remove self-closing revision tags
|
|
279
|
+
* `\b` (not `\s+`) so attribute-less markers like `<w:del/>` are also
|
|
280
|
+
* caught while `<w:delText>` is not.
|
|
244
281
|
*/
|
|
245
282
|
private removeSelfClosingRevisionTags(xml: string): string {
|
|
246
283
|
const patterns = [
|
|
247
|
-
/<w:ins\
|
|
248
|
-
/<w:del\
|
|
249
|
-
/<w:moveFrom\
|
|
250
|
-
/<w:moveTo\
|
|
284
|
+
/<w:ins\b[^>]*\/>/g,
|
|
285
|
+
/<w:del\b[^>]*\/>/g,
|
|
286
|
+
/<w:moveFrom\b[^>]*\/>/g,
|
|
287
|
+
/<w:moveTo\b[^>]*\/>/g,
|
|
251
288
|
];
|
|
252
289
|
|
|
253
290
|
let result = xml;
|
package/src/utils/deepClone.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* For simple objects (like formatting options), this is more efficient
|
|
11
11
|
* than JSON.parse(JSON.stringify()) and doesn't lose non-serializable values.
|
|
12
12
|
*
|
|
13
|
+
* Cyclic and shared references are handled via an internal seen-cache: an
|
|
14
|
+
* object encountered more than once during a single clone is cloned exactly
|
|
15
|
+
* once, so circular graphs cannot overflow the stack and shared sub-objects
|
|
16
|
+
* (including shared Date and RegExp instances) retain a single shared identity
|
|
17
|
+
* in the result.
|
|
18
|
+
*
|
|
13
19
|
* @param obj - Object to clone
|
|
14
20
|
* @returns Deep cloned copy of the object
|
|
15
21
|
*
|
|
@@ -23,21 +29,50 @@
|
|
|
23
29
|
* ```
|
|
24
30
|
*/
|
|
25
31
|
export function deepClone<T>(obj: T): T {
|
|
32
|
+
// Map of already-cloned source objects to their clones, scoped to this call.
|
|
33
|
+
// Guards against infinite recursion on cyclic input and preserves the
|
|
34
|
+
// identity of references shared by multiple parents within the same graph.
|
|
35
|
+
return cloneInternal(obj, new WeakMap<object, unknown>());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cloneInternal<T>(obj: T, seen: WeakMap<object, unknown>): T {
|
|
26
39
|
// Handle primitive types and null
|
|
27
40
|
if (obj === null || typeof obj !== 'object') {
|
|
28
41
|
return obj;
|
|
29
42
|
}
|
|
30
43
|
|
|
31
|
-
//
|
|
44
|
+
// Return the existing clone for any object already seen in this call. Placed
|
|
45
|
+
// before the Date/RegExp branches so a single Date/RegExp instance shared by
|
|
46
|
+
// multiple nodes resolves to one shared clone (the JSDoc shared-identity
|
|
47
|
+
// guarantee), not a fresh copy per reference.
|
|
48
|
+
const existing = seen.get(obj as object);
|
|
49
|
+
if (existing !== undefined) {
|
|
50
|
+
return existing as T;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle Date (immutable value copy; cannot form a cycle). Registered in
|
|
54
|
+
// `seen` so repeat references to the same instance share this clone.
|
|
32
55
|
if (obj instanceof Date) {
|
|
33
|
-
|
|
56
|
+
const dateCopy = new Date(obj.getTime());
|
|
57
|
+
seen.set(obj as object, dateCopy);
|
|
58
|
+
return dateCopy as T;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle RegExp (immutable value copy; cannot form a cycle). Registered in
|
|
62
|
+
// `seen` so repeat references to the same instance share this clone.
|
|
63
|
+
if (obj instanceof RegExp) {
|
|
64
|
+
const regexpCopy = new RegExp(obj.source, obj.flags);
|
|
65
|
+
seen.set(obj as object, regexpCopy);
|
|
66
|
+
return regexpCopy as T;
|
|
34
67
|
}
|
|
35
68
|
|
|
36
69
|
// Handle Array
|
|
37
70
|
if (Array.isArray(obj)) {
|
|
38
71
|
const arrCopy: unknown[] = [];
|
|
72
|
+
// Register before recursing so self/back references resolve to this clone.
|
|
73
|
+
seen.set(obj as object, arrCopy);
|
|
39
74
|
for (let i = 0; i < obj.length; i++) {
|
|
40
|
-
arrCopy[i] =
|
|
75
|
+
arrCopy[i] = cloneInternal(obj[i], seen);
|
|
41
76
|
}
|
|
42
77
|
return arrCopy as T;
|
|
43
78
|
}
|
|
@@ -45,8 +80,9 @@ export function deepClone<T>(obj: T): T {
|
|
|
45
80
|
// Handle Map
|
|
46
81
|
if (obj instanceof Map) {
|
|
47
82
|
const mapCopy = new Map();
|
|
83
|
+
seen.set(obj, mapCopy);
|
|
48
84
|
obj.forEach((value, key) => {
|
|
49
|
-
mapCopy.set(
|
|
85
|
+
mapCopy.set(cloneInternal(key, seen), cloneInternal(value, seen));
|
|
50
86
|
});
|
|
51
87
|
return mapCopy as T;
|
|
52
88
|
}
|
|
@@ -54,23 +90,21 @@ export function deepClone<T>(obj: T): T {
|
|
|
54
90
|
// Handle Set
|
|
55
91
|
if (obj instanceof Set) {
|
|
56
92
|
const setCopy = new Set();
|
|
93
|
+
seen.set(obj, setCopy);
|
|
57
94
|
obj.forEach((value) => {
|
|
58
|
-
setCopy.add(
|
|
95
|
+
setCopy.add(cloneInternal(value, seen));
|
|
59
96
|
});
|
|
60
97
|
return setCopy as T;
|
|
61
98
|
}
|
|
62
99
|
|
|
63
|
-
// Handle RegExp
|
|
64
|
-
if (obj instanceof RegExp) {
|
|
65
|
-
return new RegExp(obj.source, obj.flags) as T;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
100
|
// Handle plain objects
|
|
69
101
|
const objCopy = Object.create(Object.getPrototypeOf(obj)) as Record<string, unknown>;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
seen.set(obj as object, objCopy);
|
|
103
|
+
// Object.keys avoids the prototype-builtins shadowing risk of
|
|
104
|
+
// obj.hasOwnProperty(key) (matches sibling deepEqual.ts) and yields the same
|
|
105
|
+
// own-enumerable string keys the previous for..in + hasOwnProperty loop did.
|
|
106
|
+
for (const key of Object.keys(obj as object)) {
|
|
107
|
+
objCopy[key] = cloneInternal((obj as Record<string, unknown>)[key], seen);
|
|
74
108
|
}
|
|
75
109
|
|
|
76
110
|
return objCopy as T;
|