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
|
@@ -17,7 +17,11 @@ import {
|
|
|
17
17
|
FixAction,
|
|
18
18
|
ValidationIssue,
|
|
19
19
|
} from './ValidationRules.js';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
CONTENTLESS_REVISION_TYPES,
|
|
22
|
+
RevisionValidator,
|
|
23
|
+
revisionHasContent,
|
|
24
|
+
} from './RevisionValidator.js';
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* Automatically fixes revision validation issues.
|
|
@@ -81,9 +85,16 @@ export class RevisionAutoFixer {
|
|
|
81
85
|
actions.push(...this.fixMissingAuthors(revisions, defaultAuthor, options?.dryRun));
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
// Fix orphaned move markers (REV003, REV004)
|
|
88
|
+
// Fix orphaned move markers (REV003, REV004) — one pairing pass
|
|
89
|
+
// produces both rules, so gate each side individually to honor
|
|
90
|
+
// onlyRules/skipRules without mutating revisions the caller excluded.
|
|
85
91
|
if (shouldProcess('REV003') || shouldProcess('REV004')) {
|
|
86
|
-
actions.push(
|
|
92
|
+
actions.push(
|
|
93
|
+
...this.fixOrphanedMoveMarkers(revisionManager, revisions, options?.dryRun, doc, {
|
|
94
|
+
moveFrom: shouldProcess('REV003'),
|
|
95
|
+
moveTo: shouldProcess('REV004'),
|
|
96
|
+
})
|
|
97
|
+
);
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
// Fix missing dates (REV101)
|
|
@@ -98,7 +109,7 @@ export class RevisionAutoFixer {
|
|
|
98
109
|
|
|
99
110
|
// Fix empty revisions (REV103)
|
|
100
111
|
if (shouldProcess('REV103')) {
|
|
101
|
-
actions.push(...this.fixEmptyRevisions(revisionManager, revisions, options?.dryRun));
|
|
112
|
+
actions.push(...this.fixEmptyRevisions(revisionManager, revisions, options?.dryRun, doc));
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
// Fix non-sequential IDs (REV104)
|
|
@@ -297,20 +308,51 @@ export class RevisionAutoFixer {
|
|
|
297
308
|
return actions;
|
|
298
309
|
}
|
|
299
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Removes a revision from the paragraph content that owns it.
|
|
313
|
+
*
|
|
314
|
+
* RevisionManager.removeById() only deregisters the revision from the
|
|
315
|
+
* manager registry, but serialization reads Paragraph.content directly —
|
|
316
|
+
* without this step the removed markup would still be written on save.
|
|
317
|
+
*
|
|
318
|
+
* @param doc - Document whose paragraphs are searched
|
|
319
|
+
* @param revision - Revision instance to remove
|
|
320
|
+
* @returns True if the revision was found and removed from a paragraph
|
|
321
|
+
*/
|
|
322
|
+
private static removeRevisionFromContent(doc: Document, revision: Revision): boolean {
|
|
323
|
+
for (const paragraph of doc.getAllParagraphs()) {
|
|
324
|
+
// replaceContent matches by identity and splices without tracking side effects
|
|
325
|
+
if (paragraph.replaceContent(revision, [])) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
300
332
|
/**
|
|
301
333
|
* Fix orphaned move markers by removing them.
|
|
302
334
|
*
|
|
303
335
|
* @param revisionManager - RevisionManager instance
|
|
304
336
|
* @param revisions - Array of revisions
|
|
305
337
|
* @param dryRun - If true, only report changes without applying
|
|
338
|
+
* @param doc - Document to remove the revision content from; without it
|
|
339
|
+
* only the manager registry is pruned and saved XML keeps the markup
|
|
340
|
+
* @param ruleFilter - Which sides of the pairing to fix; REV003 (moveFrom)
|
|
341
|
+
* and REV004 (moveTo) are separate rules, so callers honoring
|
|
342
|
+
* onlyRules/skipRules must be able to fix one without the other.
|
|
343
|
+
* Defaults to both.
|
|
306
344
|
* @returns Array of fix actions
|
|
307
345
|
*/
|
|
308
346
|
static fixOrphanedMoveMarkers(
|
|
309
347
|
revisionManager: { removeById(id: number): boolean },
|
|
310
348
|
revisions: Revision[],
|
|
311
|
-
dryRun?: boolean
|
|
349
|
+
dryRun?: boolean,
|
|
350
|
+
doc?: Document,
|
|
351
|
+
ruleFilter?: { moveFrom?: boolean; moveTo?: boolean }
|
|
312
352
|
): FixAction[] {
|
|
313
353
|
const actions: FixAction[] = [];
|
|
354
|
+
const fixMoveFrom = ruleFilter?.moveFrom ?? true;
|
|
355
|
+
const fixMoveTo = ruleFilter?.moveTo ?? true;
|
|
314
356
|
|
|
315
357
|
const moveFromIds = new Map<string, Revision>();
|
|
316
358
|
const moveToIds = new Map<string, Revision>();
|
|
@@ -326,52 +368,62 @@ export class RevisionAutoFixer {
|
|
|
326
368
|
}
|
|
327
369
|
}
|
|
328
370
|
|
|
329
|
-
// Remove orphaned moveFrom
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
371
|
+
// Remove orphaned moveFrom (REV003)
|
|
372
|
+
if (fixMoveFrom) {
|
|
373
|
+
for (const [moveId, rev] of moveFromIds) {
|
|
374
|
+
if (!moveToIds.has(moveId)) {
|
|
375
|
+
const issue: ValidationIssue = {
|
|
376
|
+
code: REVISION_RULES.ORPHANED_MOVE_FROM.code,
|
|
377
|
+
severity: 'error',
|
|
378
|
+
message: `Orphaned moveFrom with moveId="${moveId}"`,
|
|
379
|
+
location: { revisionId: rev.getId() },
|
|
380
|
+
autoFixable: true,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
actions.push({
|
|
384
|
+
issue,
|
|
385
|
+
action: `Removed orphaned moveFrom (ID: ${rev.getId()}, moveId: ${moveId})`,
|
|
386
|
+
before: { type: 'moveFrom', moveId },
|
|
387
|
+
after: null,
|
|
388
|
+
success: true,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
if (!dryRun) {
|
|
392
|
+
revisionManager.removeById(rev.getId());
|
|
393
|
+
if (doc) {
|
|
394
|
+
this.removeRevisionFromContent(doc, rev);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
350
397
|
}
|
|
351
398
|
}
|
|
352
399
|
}
|
|
353
400
|
|
|
354
|
-
// Remove orphaned moveTo
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
401
|
+
// Remove orphaned moveTo (REV004)
|
|
402
|
+
if (fixMoveTo) {
|
|
403
|
+
for (const [moveId, rev] of moveToIds) {
|
|
404
|
+
if (!moveFromIds.has(moveId)) {
|
|
405
|
+
const issue: ValidationIssue = {
|
|
406
|
+
code: REVISION_RULES.ORPHANED_MOVE_TO.code,
|
|
407
|
+
severity: 'error',
|
|
408
|
+
message: `Orphaned moveTo with moveId="${moveId}"`,
|
|
409
|
+
location: { revisionId: rev.getId() },
|
|
410
|
+
autoFixable: true,
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
actions.push({
|
|
414
|
+
issue,
|
|
415
|
+
action: `Removed orphaned moveTo (ID: ${rev.getId()}, moveId: ${moveId})`,
|
|
416
|
+
before: { type: 'moveTo', moveId },
|
|
417
|
+
after: null,
|
|
418
|
+
success: true,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
if (!dryRun) {
|
|
422
|
+
revisionManager.removeById(rev.getId());
|
|
423
|
+
if (doc) {
|
|
424
|
+
this.removeRevisionFromContent(doc, rev);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
375
427
|
}
|
|
376
428
|
}
|
|
377
429
|
}
|
|
@@ -385,38 +437,27 @@ export class RevisionAutoFixer {
|
|
|
385
437
|
* @param revisionManager - RevisionManager instance
|
|
386
438
|
* @param revisions - Array of revisions
|
|
387
439
|
* @param dryRun - If true, only report changes without applying
|
|
440
|
+
* @param doc - Document to remove the revision content from; without it
|
|
441
|
+
* only the manager registry is pruned and saved XML keeps the markup
|
|
388
442
|
* @returns Array of fix actions
|
|
389
443
|
*/
|
|
390
444
|
static fixEmptyRevisions(
|
|
391
445
|
revisionManager: RevisionManager,
|
|
392
446
|
revisions: Revision[],
|
|
393
|
-
dryRun?: boolean
|
|
447
|
+
dryRun?: boolean,
|
|
448
|
+
doc?: Document
|
|
394
449
|
): FixAction[] {
|
|
395
450
|
const actions: FixAction[] = [];
|
|
396
451
|
|
|
397
|
-
const propertyChangeTypes = [
|
|
398
|
-
'runPropertiesChange',
|
|
399
|
-
'paragraphPropertiesChange',
|
|
400
|
-
'tablePropertiesChange',
|
|
401
|
-
'tableExceptionPropertiesChange',
|
|
402
|
-
'tableRowPropertiesChange',
|
|
403
|
-
'tableCellPropertiesChange',
|
|
404
|
-
'sectionPropertiesChange',
|
|
405
|
-
'numberingChange',
|
|
406
|
-
];
|
|
407
|
-
|
|
408
452
|
for (const rev of revisions) {
|
|
409
453
|
const type = rev.getType();
|
|
410
454
|
|
|
411
|
-
//
|
|
412
|
-
if (
|
|
455
|
+
// Property changes and cell markers are contentless by design
|
|
456
|
+
if (CONTENTLESS_REVISION_TYPES.has(type)) {
|
|
413
457
|
continue;
|
|
414
458
|
}
|
|
415
459
|
|
|
416
|
-
|
|
417
|
-
const hasContent = runs.length > 0 && runs.some((r) => r.getText().length > 0);
|
|
418
|
-
|
|
419
|
-
if (!hasContent) {
|
|
460
|
+
if (!revisionHasContent(rev)) {
|
|
420
461
|
const issue: ValidationIssue = {
|
|
421
462
|
code: REVISION_RULES.EMPTY_REVISION.code,
|
|
422
463
|
severity: 'warning',
|
|
@@ -435,6 +476,9 @@ export class RevisionAutoFixer {
|
|
|
435
476
|
|
|
436
477
|
if (!dryRun) {
|
|
437
478
|
revisionManager.removeById(rev.getId());
|
|
479
|
+
if (doc) {
|
|
480
|
+
this.removeRevisionFromContent(doc, rev);
|
|
481
|
+
}
|
|
438
482
|
}
|
|
439
483
|
}
|
|
440
484
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Document } from '../core/Document.js';
|
|
11
11
|
import type { Revision } from '../elements/Revision.js';
|
|
12
|
+
import { isHyperlinkContent, isImageRunContent } from '../elements/RevisionContent.js';
|
|
12
13
|
import {
|
|
13
14
|
REVISION_RULES,
|
|
14
15
|
ValidationIssue,
|
|
@@ -18,6 +19,44 @@ import {
|
|
|
18
19
|
} from './ValidationRules.js';
|
|
19
20
|
import { ValidationRuleRegistry } from './ValidationRuleRegistry.js';
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Revision types that are structurally contentless per ECMA-376: property
|
|
24
|
+
* change markers carry before/after property snapshots rather than runs, and
|
|
25
|
+
* table cell markers (w:cellIns/w:cellDel/w:cellMerge) annotate the cell
|
|
26
|
+
* itself. The REV103 empty-content rule must never flag these.
|
|
27
|
+
*
|
|
28
|
+
* Shared with RevisionAutoFixer so the validator and fixer cannot drift
|
|
29
|
+
* apart on which revision types are allowed to be empty.
|
|
30
|
+
*/
|
|
31
|
+
export const CONTENTLESS_REVISION_TYPES: ReadonlySet<string> = new Set([
|
|
32
|
+
'runPropertiesChange',
|
|
33
|
+
'paragraphPropertiesChange',
|
|
34
|
+
'tablePropertiesChange',
|
|
35
|
+
'tableExceptionPropertiesChange',
|
|
36
|
+
'tableRowPropertiesChange',
|
|
37
|
+
'tableCellPropertiesChange',
|
|
38
|
+
'sectionPropertiesChange',
|
|
39
|
+
'numberingChange',
|
|
40
|
+
'tableCellInsert',
|
|
41
|
+
'tableCellDelete',
|
|
42
|
+
'tableCellMerge',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* True when a revision carries real content. Per ECMA-376, w:ins/w:del may
|
|
47
|
+
* contain w:hyperlink and image runs (w:r with w:drawing) in addition to
|
|
48
|
+
* text runs, so a runs-only text check would falsely flag hyperlink-only
|
|
49
|
+
* and image-only tracked changes as empty. Shared with RevisionAutoFixer.
|
|
50
|
+
*/
|
|
51
|
+
export function revisionHasContent(rev: Revision): boolean {
|
|
52
|
+
// Covers text runs and hyperlink display text
|
|
53
|
+
if (rev.getText().length > 0) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// Hyperlinks count even without display text; ImageRuns have no text at all
|
|
57
|
+
return rev.getContent().some((item) => isHyperlinkContent(item) || isImageRunContent(item));
|
|
58
|
+
}
|
|
59
|
+
|
|
21
60
|
/**
|
|
22
61
|
* Validates document revisions for ECMA-376 compliance.
|
|
23
62
|
*
|
|
@@ -67,15 +106,15 @@ export class RevisionValidator {
|
|
|
67
106
|
if (!skipRules.has('REV001')) {
|
|
68
107
|
allIssues.push(...this.validateRevisionIds(revisions));
|
|
69
108
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
109
|
+
// validateMovePairs emits both REV003 and REV004 from one pairing pass;
|
|
110
|
+
// filter per issue code so skipping one rule does not silently disable
|
|
111
|
+
// the other (REV004 is error-severity and corruption-relevant).
|
|
112
|
+
allIssues.push(...this.validateMovePairs(revisions).filter((i) => !skipRules.has(i.code)));
|
|
73
113
|
if (!skipRules.has('REV002')) {
|
|
74
114
|
allIssues.push(...this.validateAuthors(revisions));
|
|
75
115
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
116
|
+
// validateDates emits both REV101 and REV102 — same per-code filtering.
|
|
117
|
+
allIssues.push(...this.validateDates(revisions).filter((i) => !skipRules.has(i.code)));
|
|
79
118
|
if (!skipRules.has('REV103')) {
|
|
80
119
|
allIssues.push(...this.validateContent(revisions));
|
|
81
120
|
}
|
|
@@ -302,29 +341,15 @@ export class RevisionValidator {
|
|
|
302
341
|
static validateContent(revisions: Revision[]): ValidationIssue[] {
|
|
303
342
|
const issues: ValidationIssue[] = [];
|
|
304
343
|
|
|
305
|
-
const propertyChangeTypes = [
|
|
306
|
-
'runPropertiesChange',
|
|
307
|
-
'paragraphPropertiesChange',
|
|
308
|
-
'tablePropertiesChange',
|
|
309
|
-
'tableExceptionPropertiesChange',
|
|
310
|
-
'tableRowPropertiesChange',
|
|
311
|
-
'tableCellPropertiesChange',
|
|
312
|
-
'sectionPropertiesChange',
|
|
313
|
-
'numberingChange',
|
|
314
|
-
];
|
|
315
|
-
|
|
316
344
|
for (const rev of revisions) {
|
|
317
345
|
const type = rev.getType();
|
|
318
346
|
|
|
319
|
-
// Property changes
|
|
320
|
-
if (
|
|
347
|
+
// Property changes and cell markers are contentless by design
|
|
348
|
+
if (CONTENTLESS_REVISION_TYPES.has(type)) {
|
|
321
349
|
continue;
|
|
322
350
|
}
|
|
323
351
|
|
|
324
|
-
|
|
325
|
-
const hasContent = runs.length > 0 && runs.some((r) => r.getText().length > 0);
|
|
326
|
-
|
|
327
|
-
if (!hasContent) {
|
|
352
|
+
if (!revisionHasContent(rev)) {
|
|
328
353
|
issues.push(
|
|
329
354
|
createIssueFromRule(
|
|
330
355
|
REVISION_RULES.EMPTY_REVISION,
|
package/src/xml/XMLBuilder.ts
CHANGED
|
@@ -162,9 +162,12 @@ export class XMLBuilder {
|
|
|
162
162
|
// Add attributes
|
|
163
163
|
if (element.attributes) {
|
|
164
164
|
for (const [key, value] of Object.entries(element.attributes)) {
|
|
165
|
-
if (value !== undefined && value !== null
|
|
166
|
-
//
|
|
167
|
-
|
|
165
|
+
if (value !== undefined && value !== null) {
|
|
166
|
+
// Booleans serialize as ST_OnOff literals (ECMA-376 §22.9.2.13).
|
|
167
|
+
// Explicit false must emit "0" — dropping it would flip an explicit
|
|
168
|
+
// override to inherited/on under CT_OnOff presence semantics.
|
|
169
|
+
// Callers signal absence with undefined, never false.
|
|
170
|
+
const attrValue = value === true ? '1' : value === false ? '0' : String(value);
|
|
168
171
|
// Use escapeXmlAttribute for attribute values (Issue #8)
|
|
169
172
|
xml += ` ${key}="${XMLBuilder.escapeXmlAttribute(attrValue)}"`;
|
|
170
173
|
}
|
|
@@ -245,18 +248,47 @@ export class XMLBuilder {
|
|
|
245
248
|
.replace(/'/g, ''');
|
|
246
249
|
}
|
|
247
250
|
|
|
251
|
+
/** The five predefined XML 1.0 named entities */
|
|
252
|
+
private static readonly NAMED_ENTITIES: Record<string, string> = {
|
|
253
|
+
lt: '<',
|
|
254
|
+
gt: '>',
|
|
255
|
+
quot: '"',
|
|
256
|
+
apos: "'",
|
|
257
|
+
amp: '&',
|
|
258
|
+
};
|
|
259
|
+
|
|
248
260
|
/**
|
|
249
261
|
* Unescapes XML entities back to original characters
|
|
262
|
+
* Decodes the five predefined named entities plus numeric character
|
|
263
|
+
* references (’ /  ), which XML 1.0 §4.1 requires every
|
|
264
|
+
* conforming processor to expand — third-party producers emit them, and
|
|
265
|
+
* leaving them raw double-escapes the ampersand on save.
|
|
250
266
|
* @param text Text with XML entities
|
|
251
267
|
* @returns Unescaped text
|
|
252
268
|
*/
|
|
253
269
|
static unescapeXml(text: string): string {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
270
|
+
// Single pass so text produced by one decode is never re-decoded by a
|
|
271
|
+
// later one (e.g. '&amp;' must yield '&', not '&').
|
|
272
|
+
return text.replace(
|
|
273
|
+
/&(?:(lt|gt|quot|apos|amp)|#x([0-9A-Fa-f]+)|#(\d+));/g,
|
|
274
|
+
(match, named?: string, hex?: string, dec?: string) => {
|
|
275
|
+
if (named !== undefined) {
|
|
276
|
+
return XMLBuilder.NAMED_ENTITIES[named] ?? match;
|
|
277
|
+
}
|
|
278
|
+
const code = hex !== undefined ? parseInt(hex, 16) : Number(dec);
|
|
279
|
+
// Code points outside the XML 1.0 Char production (NUL, surrogates,
|
|
280
|
+
// beyond U+10FFFF) would make the decoded string unserializable —
|
|
281
|
+
// keep the raw reference instead of injecting an invalid character.
|
|
282
|
+
const isXmlChar =
|
|
283
|
+
code === 0x9 ||
|
|
284
|
+
code === 0xa ||
|
|
285
|
+
code === 0xd ||
|
|
286
|
+
(code >= 0x20 && code <= 0xd7ff) ||
|
|
287
|
+
(code >= 0xe000 && code <= 0xfffd) ||
|
|
288
|
+
(code >= 0x10000 && code <= 0x10ffff);
|
|
289
|
+
return isXmlChar ? String.fromCodePoint(code) : match;
|
|
290
|
+
}
|
|
291
|
+
);
|
|
260
292
|
}
|
|
261
293
|
|
|
262
294
|
/**
|
|
@@ -633,11 +665,7 @@ export class XMLBuilder {
|
|
|
633
665
|
const builder = new XMLBuilder();
|
|
634
666
|
const element = XMLBuilder.objectToElement(obj, rootName);
|
|
635
667
|
if (element) {
|
|
636
|
-
|
|
637
|
-
builder.text(element);
|
|
638
|
-
} else {
|
|
639
|
-
builder.elements.push(element);
|
|
640
|
-
}
|
|
668
|
+
builder.elements.push(element);
|
|
641
669
|
}
|
|
642
670
|
return builder.build();
|
|
643
671
|
}
|
|
@@ -649,22 +677,21 @@ export class XMLBuilder {
|
|
|
649
677
|
private static objectToElement(
|
|
650
678
|
obj: ParsedXmlObject | string | number | boolean | null | undefined,
|
|
651
679
|
name: string
|
|
652
|
-
): XMLElement |
|
|
680
|
+
): XMLElement | null {
|
|
653
681
|
if (obj === null || obj === undefined) {
|
|
654
682
|
return null;
|
|
655
683
|
}
|
|
656
684
|
|
|
657
|
-
if (typeof obj !== 'object'
|
|
658
|
-
|
|
685
|
+
if (typeof obj !== 'object') {
|
|
686
|
+
// The parser collapses text-only elements to their primitive value, so
|
|
687
|
+
// the wrapping tag must be re-emitted here — bare text inside container
|
|
688
|
+
// elements (e.g. w:r) is invalid WordprocessingML.
|
|
689
|
+
return { name, children: [String(obj)] };
|
|
659
690
|
}
|
|
660
691
|
|
|
661
692
|
const attributes: Record<string, string | number | boolean> = {};
|
|
662
693
|
const children: (XMLElement | string)[] = [];
|
|
663
694
|
|
|
664
|
-
if (obj['#text'] && Object.keys(obj).length === 1) {
|
|
665
|
-
return String(obj['#text']);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
695
|
for (const key in obj) {
|
|
669
696
|
if (key.startsWith('@_')) {
|
|
670
697
|
const attrName = key.substring(2);
|
|
@@ -674,7 +701,40 @@ export class XMLBuilder {
|
|
|
674
701
|
}
|
|
675
702
|
} else if (key === '#text') {
|
|
676
703
|
children.push(String(obj[key]));
|
|
677
|
-
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// _orderedChildren is parser metadata, never document content. When
|
|
708
|
+
// present, it restores the original interleaving of same-named siblings
|
|
709
|
+
// (e.g. w:t / w:tab / w:t in a run) that the keyed object form loses.
|
|
710
|
+
const orderedChildren = obj['_orderedChildren'] as
|
|
711
|
+
| { type: string; index: number }[]
|
|
712
|
+
| undefined;
|
|
713
|
+
|
|
714
|
+
if (orderedChildren && orderedChildren.length > 0) {
|
|
715
|
+
for (const childInfo of orderedChildren) {
|
|
716
|
+
const childValue = obj[childInfo.type];
|
|
717
|
+
if (childValue === undefined) {
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
const item = Array.isArray(childValue)
|
|
721
|
+
? childValue[childInfo.index]
|
|
722
|
+
: childInfo.index === 0
|
|
723
|
+
? childValue
|
|
724
|
+
: undefined;
|
|
725
|
+
if (item === undefined) {
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
const childElement = XMLBuilder.objectToElement(item, childInfo.type);
|
|
729
|
+
if (childElement) {
|
|
730
|
+
children.push(childElement);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
} else {
|
|
734
|
+
for (const key in obj) {
|
|
735
|
+
if (key.startsWith('@_') || key === '#text' || key === '_orderedChildren') {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
678
738
|
const childObj = obj[key];
|
|
679
739
|
if (Array.isArray(childObj)) {
|
|
680
740
|
childObj.forEach((item) => {
|