docxmlater 10.0.2 → 10.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/constants/legacyCompatFlags.d.ts.map +1 -1
- package/dist/constants/legacyCompatFlags.js.map +1 -1
- package/dist/constants/limits.d.ts +0 -27
- package/dist/constants/limits.d.ts.map +1 -1
- package/dist/constants/limits.js +13 -13
- package/dist/constants/limits.js.map +1 -1
- package/dist/core/Document.d.ts +23 -19
- package/dist/core/Document.d.ts.map +1 -1
- package/dist/core/Document.js +197 -63
- package/dist/core/Document.js.map +1 -1
- package/dist/core/DocumentContent.d.ts.map +1 -1
- package/dist/core/DocumentContent.js.map +1 -1
- package/dist/core/DocumentGenerator.d.ts.map +1 -1
- package/dist/core/DocumentGenerator.js +59 -24
- package/dist/core/DocumentGenerator.js.map +1 -1
- package/dist/core/DocumentIdManager.d.ts.map +1 -1
- package/dist/core/DocumentIdManager.js.map +1 -1
- package/dist/core/DocumentParser.d.ts +6 -6
- package/dist/core/DocumentParser.d.ts.map +1 -1
- package/dist/core/DocumentParser.js +60 -54
- package/dist/core/DocumentParser.js.map +1 -1
- package/dist/core/DocumentValidator.d.ts.map +1 -1
- package/dist/core/DocumentValidator.js.map +1 -1
- package/dist/core/Relationship.d.ts.map +1 -1
- package/dist/core/Relationship.js +1 -1
- package/dist/core/Relationship.js.map +1 -1
- package/dist/core/RelationshipManager.js +3 -3
- package/dist/core/RelationshipManager.js.map +1 -1
- package/dist/elements/AlternateContent.js.map +1 -1
- package/dist/elements/Bookmark.d.ts.map +1 -1
- package/dist/elements/Bookmark.js.map +1 -1
- package/dist/elements/BookmarkManager.d.ts.map +1 -1
- package/dist/elements/BookmarkManager.js.map +1 -1
- package/dist/elements/Comment.js +1 -1
- package/dist/elements/Comment.js.map +1 -1
- package/dist/elements/CommentManager.d.ts.map +1 -1
- package/dist/elements/CommentManager.js +8 -2
- package/dist/elements/CommentManager.js.map +1 -1
- package/dist/elements/CommonTypes.d.ts.map +1 -1
- package/dist/elements/CommonTypes.js +1 -2
- package/dist/elements/CommonTypes.js.map +1 -1
- package/dist/elements/CustomXml.js.map +1 -1
- package/dist/elements/Endnote.d.ts.map +1 -1
- package/dist/elements/Endnote.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.map +1 -1
- package/dist/elements/Field.js +31 -28
- package/dist/elements/Field.js.map +1 -1
- package/dist/elements/FieldHelpers.d.ts.map +1 -1
- package/dist/elements/FieldHelpers.js +6 -6
- package/dist/elements/FieldHelpers.js.map +1 -1
- package/dist/elements/FontManager.d.ts.map +1 -1
- package/dist/elements/FontManager.js.map +1 -1
- package/dist/elements/Footer.js.map +1 -1
- package/dist/elements/Footnote.d.ts.map +1 -1
- package/dist/elements/Footnote.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.js.map +1 -1
- package/dist/elements/HeaderFooterManager.js.map +1 -1
- package/dist/elements/Hyperlink.d.ts.map +1 -1
- package/dist/elements/Hyperlink.js +5 -5
- package/dist/elements/Hyperlink.js.map +1 -1
- package/dist/elements/Image.d.ts +2 -2
- package/dist/elements/Image.d.ts.map +1 -1
- package/dist/elements/Image.js +21 -5
- package/dist/elements/Image.js.map +1 -1
- package/dist/elements/ImageManager.d.ts.map +1 -1
- package/dist/elements/ImageManager.js +2 -2
- package/dist/elements/ImageManager.js.map +1 -1
- package/dist/elements/ImageRun.js.map +1 -1
- package/dist/elements/MathElement.js.map +1 -1
- package/dist/elements/Paragraph.d.ts.map +1 -1
- package/dist/elements/Paragraph.js +128 -117
- package/dist/elements/Paragraph.js.map +1 -1
- package/dist/elements/PreservedElement.js.map +1 -1
- package/dist/elements/PropertyChangeTypes.js.map +1 -1
- package/dist/elements/RangeMarker.js.map +1 -1
- package/dist/elements/Revision.d.ts +1 -0
- package/dist/elements/Revision.d.ts.map +1 -1
- package/dist/elements/Revision.js +44 -5
- package/dist/elements/Revision.js.map +1 -1
- package/dist/elements/RevisionContent.js.map +1 -1
- package/dist/elements/RevisionManager.d.ts.map +1 -1
- package/dist/elements/RevisionManager.js.map +1 -1
- package/dist/elements/Run.d.ts.map +1 -1
- package/dist/elements/Run.js +1 -3
- package/dist/elements/Run.js.map +1 -1
- package/dist/elements/Section.d.ts.map +1 -1
- package/dist/elements/Section.js +127 -118
- package/dist/elements/Section.js.map +1 -1
- package/dist/elements/Shape.d.ts.map +1 -1
- package/dist/elements/Shape.js +21 -0
- package/dist/elements/Shape.js.map +1 -1
- package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
- package/dist/elements/StructuredDocumentTag.js +20 -8
- package/dist/elements/StructuredDocumentTag.js.map +1 -1
- package/dist/elements/Table.d.ts +2 -2
- package/dist/elements/Table.d.ts.map +1 -1
- package/dist/elements/Table.js +29 -35
- package/dist/elements/Table.js.map +1 -1
- package/dist/elements/TableCell.d.ts +2 -2
- package/dist/elements/TableCell.d.ts.map +1 -1
- package/dist/elements/TableCell.js +63 -67
- package/dist/elements/TableCell.js.map +1 -1
- package/dist/elements/TableGridChange.js.map +1 -1
- package/dist/elements/TableOfContents.d.ts +6 -6
- package/dist/elements/TableOfContents.d.ts.map +1 -1
- package/dist/elements/TableOfContents.js.map +1 -1
- package/dist/elements/TableOfContentsElement.js.map +1 -1
- package/dist/elements/TableRow.d.ts.map +1 -1
- package/dist/elements/TableRow.js +65 -47
- package/dist/elements/TableRow.js.map +1 -1
- package/dist/elements/TextBox.d.ts.map +1 -1
- package/dist/elements/TextBox.js +1 -1
- package/dist/elements/TextBox.js.map +1 -1
- package/dist/formatting/AbstractNumbering.d.ts +1 -1
- package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
- package/dist/formatting/AbstractNumbering.js +11 -11
- package/dist/formatting/AbstractNumbering.js.map +1 -1
- package/dist/formatting/NumberingInstance.d.ts.map +1 -1
- package/dist/formatting/NumberingInstance.js +4 -4
- package/dist/formatting/NumberingInstance.js.map +1 -1
- package/dist/formatting/NumberingLevel.d.ts.map +1 -1
- package/dist/formatting/NumberingLevel.js +26 -26
- package/dist/formatting/NumberingLevel.js.map +1 -1
- package/dist/formatting/NumberingManager.d.ts +1 -1
- package/dist/formatting/NumberingManager.d.ts.map +1 -1
- package/dist/formatting/NumberingManager.js.map +1 -1
- package/dist/formatting/Style.d.ts.map +1 -1
- package/dist/formatting/Style.js +87 -95
- package/dist/formatting/Style.js.map +1 -1
- package/dist/formatting/StylesManager.d.ts +3 -3
- package/dist/formatting/StylesManager.d.ts.map +1 -1
- package/dist/formatting/StylesManager.js.map +1 -1
- package/dist/helpers/CleanupHelper.js.map +1 -1
- package/dist/images/ImageOptimizer.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/managers/DrawingManager.d.ts.map +1 -1
- package/dist/managers/DrawingManager.js.map +1 -1
- package/dist/tracking/DocumentTrackingContext.js.map +1 -1
- package/dist/tracking/TrackingContext.js.map +1 -1
- package/dist/types/compatibility-types.js.map +1 -1
- package/dist/types/formatting.js.map +1 -1
- package/dist/types/list-types.d.ts +4 -4
- package/dist/types/list-types.d.ts.map +1 -1
- package/dist/types/list-types.js.map +1 -1
- package/dist/types/settings-types.js.map +1 -1
- package/dist/types/styleConfig.js.map +1 -1
- package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
- package/dist/utils/ChangelogGenerator.js.map +1 -1
- package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
- package/dist/utils/CompatibilityUpgrader.js +7 -7
- package/dist/utils/CompatibilityUpgrader.js.map +1 -1
- package/dist/utils/InMemoryRevisionAcceptor.js +1 -1
- package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
- package/dist/utils/MoveOperationHelper.js.map +1 -1
- package/dist/utils/RevisionAwareProcessor.js.map +1 -1
- package/dist/utils/RevisionWalker.js.map +1 -1
- package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
- package/dist/utils/ShadingResolver.js +1 -1
- package/dist/utils/ShadingResolver.js.map +1 -1
- package/dist/utils/acceptRevisions.d.ts +0 -28
- package/dist/utils/acceptRevisions.d.ts.map +1 -1
- package/dist/utils/acceptRevisions.js +5 -7
- package/dist/utils/acceptRevisions.js.map +1 -1
- package/dist/utils/cnfStyleDecoder.js +1 -1
- package/dist/utils/cnfStyleDecoder.js.map +1 -1
- package/dist/utils/corruptionDetection.js.map +1 -1
- package/dist/utils/dateFormatting.js.map +1 -1
- package/dist/utils/deepClone.d.ts +0 -1
- package/dist/utils/deepClone.d.ts.map +1 -1
- package/dist/utils/deepClone.js +0 -7
- package/dist/utils/deepClone.js.map +1 -1
- package/dist/utils/diagnostics.d.ts +2 -2
- package/dist/utils/diagnostics.d.ts.map +1 -1
- package/dist/utils/diagnostics.js.map +1 -1
- package/dist/utils/errorHandling.js.map +1 -1
- package/dist/utils/formatting.js.map +1 -1
- package/dist/utils/list-detection.d.ts +2 -2
- package/dist/utils/list-detection.d.ts.map +1 -1
- package/dist/utils/list-detection.js +3 -3
- package/dist/utils/list-detection.js.map +1 -1
- package/dist/utils/logger.d.ts +2 -4
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +0 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/parsingHelpers.js.map +1 -1
- package/dist/utils/stripTrackedChanges.d.ts +0 -19
- package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
- package/dist/utils/stripTrackedChanges.js +0 -2
- package/dist/utils/stripTrackedChanges.js.map +1 -1
- package/dist/utils/textDiff.js.map +1 -1
- package/dist/utils/units.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/xmlSanitization.js.map +1 -1
- package/dist/validation/RevisionAutoFixer.js.map +1 -1
- package/dist/validation/RevisionValidator.js.map +1 -1
- package/dist/validation/ValidationRules.js.map +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/xml/XMLBuilder.d.ts.map +1 -1
- package/dist/xml/XMLBuilder.js +10 -0
- package/dist/xml/XMLBuilder.js.map +1 -1
- package/dist/xml/XMLParser.d.ts.map +1 -1
- package/dist/xml/XMLParser.js +4 -5
- package/dist/xml/XMLParser.js.map +1 -1
- package/dist/zip/ZipHandler.js.map +1 -1
- package/dist/zip/ZipReader.js.map +1 -1
- package/dist/zip/ZipWriter.js.map +1 -1
- package/dist/zip/errors.js.map +1 -1
- package/dist/zip/types.js.map +1 -1
- package/package.json +34 -4
- package/src/__tests__/helper-methods.test.ts +512 -0
- package/src/constants/legacyCompatFlags.ts +138 -0
- package/src/constants/limits.ts +50 -0
- package/src/core/CLAUDE.md +109 -0
- package/src/core/Document.ts +15569 -0
- package/src/core/DocumentContent.ts +467 -0
- package/src/core/DocumentGenerator.ts +1104 -0
- package/src/core/DocumentIdManager.ts +158 -0
- package/src/core/DocumentParser.ts +10107 -0
- package/src/core/DocumentValidator.ts +372 -0
- package/src/core/Relationship.ts +367 -0
- package/src/core/RelationshipManager.ts +428 -0
- package/src/elements/AlternateContent.ts +42 -0
- package/src/elements/Bookmark.ts +210 -0
- package/src/elements/BookmarkManager.ts +250 -0
- package/src/elements/CLAUDE.md +126 -0
- package/src/elements/Comment.ts +359 -0
- package/src/elements/CommentManager.ts +502 -0
- package/src/elements/CommonTypes.ts +549 -0
- package/src/elements/CustomXml.ts +36 -0
- package/src/elements/Endnote.ts +217 -0
- package/src/elements/EndnoteManager.ts +249 -0
- package/src/elements/Field.ts +1233 -0
- package/src/elements/FieldHelpers.ts +333 -0
- package/src/elements/FontManager.ts +339 -0
- package/src/elements/Footer.ts +269 -0
- package/src/elements/Footnote.ts +217 -0
- package/src/elements/FootnoteManager.ts +249 -0
- package/src/elements/Header.ts +269 -0
- package/src/elements/HeaderFooterManager.ts +219 -0
- package/src/elements/Hyperlink.ts +1146 -0
- package/src/elements/Image.ts +1756 -0
- package/src/elements/ImageManager.ts +432 -0
- package/src/elements/ImageRun.ts +59 -0
- package/src/elements/MathElement.ts +65 -0
- package/src/elements/Paragraph.ts +4227 -0
- package/src/elements/PreservedElement.ts +53 -0
- package/src/elements/PropertyChangeTypes.ts +442 -0
- package/src/elements/RangeMarker.ts +400 -0
- package/src/elements/Revision.ts +1217 -0
- package/src/elements/RevisionContent.ts +73 -0
- package/src/elements/RevisionManager.ts +1070 -0
- package/src/elements/Run.ts +3068 -0
- package/src/elements/Section.ts +1421 -0
- package/src/elements/Shape.ts +873 -0
- package/src/elements/StructuredDocumentTag.ts +978 -0
- package/src/elements/Table.ts +2524 -0
- package/src/elements/TableCell.ts +1586 -0
- package/src/elements/TableGridChange.ts +151 -0
- package/src/elements/TableOfContents.ts +691 -0
- package/src/elements/TableOfContentsElement.ts +89 -0
- package/src/elements/TableRow.ts +906 -0
- package/src/elements/TextBox.ts +768 -0
- package/src/formatting/AbstractNumbering.ts +548 -0
- package/src/formatting/CLAUDE.md +74 -0
- package/src/formatting/NumberingInstance.ts +212 -0
- package/src/formatting/NumberingLevel.ts +1006 -0
- package/src/formatting/NumberingManager.ts +827 -0
- package/src/formatting/Style.ts +1833 -0
- package/src/formatting/StylesManager.ts +1005 -0
- package/src/helpers/CleanupHelper.ts +524 -0
- package/src/images/ImageOptimizer.ts +274 -0
- package/src/index.ts +554 -0
- package/src/managers/CLAUDE.md +47 -0
- package/src/managers/DrawingManager.ts +319 -0
- package/src/tracking/DocumentTrackingContext.ts +643 -0
- package/src/tracking/TrackingContext.ts +173 -0
- package/src/types/compatibility-types.ts +49 -0
- package/src/types/formatting.ts +210 -0
- package/src/types/list-types.ts +152 -0
- package/src/types/settings-types.ts +59 -0
- package/src/types/styleConfig.ts +189 -0
- package/src/utils/CLAUDE.md +153 -0
- package/src/utils/ChangelogGenerator.ts +1581 -0
- package/src/utils/CompatibilityUpgrader.ts +237 -0
- package/src/utils/InMemoryRevisionAcceptor.ts +668 -0
- package/src/utils/MoveOperationHelper.ts +238 -0
- package/src/utils/RevisionAwareProcessor.ts +526 -0
- package/src/utils/RevisionWalker.ts +457 -0
- package/src/utils/SelectiveRevisionAcceptor.ts +613 -0
- package/src/utils/ShadingResolver.ts +107 -0
- package/src/utils/acceptRevisions.ts +714 -0
- package/src/utils/cnfStyleDecoder.ts +217 -0
- package/src/utils/corruptionDetection.ts +345 -0
- package/src/utils/dateFormatting.ts +20 -0
- package/src/utils/deepClone.ts +78 -0
- package/src/utils/diagnostics.ts +129 -0
- package/src/utils/errorHandling.ts +80 -0
- package/src/utils/formatting.ts +213 -0
- package/src/utils/list-detection.ts +274 -0
- package/src/utils/logger.ts +404 -0
- package/src/utils/parsingHelpers.ts +190 -0
- package/src/utils/stripTrackedChanges.ts +353 -0
- package/src/utils/textDiff.ts +100 -0
- package/src/utils/units.ts +421 -0
- package/src/utils/validation.ts +542 -0
- package/src/utils/xmlSanitization.ts +182 -0
- package/src/validation/RevisionAutoFixer.ts +542 -0
- package/src/validation/RevisionValidator.ts +460 -0
- package/src/validation/ValidationRules.ts +338 -0
- package/src/validation/index.ts +30 -0
- package/src/xml/CLAUDE.md +65 -0
- package/src/xml/XMLBuilder.ts +871 -0
- package/src/xml/XMLParser.ts +919 -0
- package/src/zip/CLAUDE.md +55 -0
- package/src/zip/ZipHandler.ts +637 -0
- package/src/zip/ZipReader.ts +299 -0
- package/src/zip/ZipWriter.ts +390 -0
- package/src/zip/errors.ts +69 -0
- package/src/zip/types.ts +116 -0
- package/dist/core/ListNormalizer.d.ts +0 -23
- package/dist/core/ListNormalizer.d.ts.map +0 -1
- package/dist/core/ListNormalizer.js +0 -624
- package/dist/core/ListNormalizer.js.map +0 -1
- package/dist/images/index.d.ts +0 -2
- package/dist/images/index.d.ts.map +0 -1
- package/dist/images/index.js +0 -8
- package/dist/images/index.js.map +0 -1
- package/dist/ms-doc/cfb/CFBReader.d.ts +0 -35
- package/dist/ms-doc/cfb/CFBReader.d.ts.map +0 -1
- package/dist/ms-doc/cfb/CFBReader.js +0 -360
- package/dist/ms-doc/cfb/CFBReader.js.map +0 -1
- package/dist/ms-doc/converter/DocToDocxConverter.d.ts +0 -55
- package/dist/ms-doc/converter/DocToDocxConverter.d.ts.map +0 -1
- package/dist/ms-doc/converter/DocToDocxConverter.js +0 -324
- package/dist/ms-doc/converter/DocToDocxConverter.js.map +0 -1
- package/dist/ms-doc/fib/FIB.d.ts +0 -18
- package/dist/ms-doc/fib/FIB.d.ts.map +0 -1
- package/dist/ms-doc/fib/FIB.js +0 -342
- package/dist/ms-doc/fib/FIB.js.map +0 -1
- package/dist/ms-doc/fields/FieldParser.d.ts +0 -31
- package/dist/ms-doc/fields/FieldParser.d.ts.map +0 -1
- package/dist/ms-doc/fields/FieldParser.js +0 -266
- package/dist/ms-doc/fields/FieldParser.js.map +0 -1
- package/dist/ms-doc/images/PictureExtractor.d.ts +0 -22
- package/dist/ms-doc/images/PictureExtractor.d.ts.map +0 -1
- package/dist/ms-doc/images/PictureExtractor.js +0 -233
- package/dist/ms-doc/images/PictureExtractor.js.map +0 -1
- package/dist/ms-doc/index.d.ts +0 -20
- package/dist/ms-doc/index.d.ts.map +0 -1
- package/dist/ms-doc/index.js +0 -59
- package/dist/ms-doc/index.js.map +0 -1
- package/dist/ms-doc/properties/SPRM.d.ts +0 -210
- package/dist/ms-doc/properties/SPRM.d.ts.map +0 -1
- package/dist/ms-doc/properties/SPRM.js +0 -633
- package/dist/ms-doc/properties/SPRM.js.map +0 -1
- package/dist/ms-doc/sections/SectionParser.d.ts +0 -25
- package/dist/ms-doc/sections/SectionParser.d.ts.map +0 -1
- package/dist/ms-doc/sections/SectionParser.js +0 -214
- package/dist/ms-doc/sections/SectionParser.js.map +0 -1
- package/dist/ms-doc/styles/StyleSheet.d.ts +0 -23
- package/dist/ms-doc/styles/StyleSheet.d.ts.map +0 -1
- package/dist/ms-doc/styles/StyleSheet.js +0 -268
- package/dist/ms-doc/styles/StyleSheet.js.map +0 -1
- package/dist/ms-doc/subdocuments/SubdocumentParser.d.ts +0 -61
- package/dist/ms-doc/subdocuments/SubdocumentParser.d.ts.map +0 -1
- package/dist/ms-doc/subdocuments/SubdocumentParser.js +0 -208
- package/dist/ms-doc/subdocuments/SubdocumentParser.js.map +0 -1
- package/dist/ms-doc/tables/TableParser.d.ts +0 -29
- package/dist/ms-doc/tables/TableParser.d.ts.map +0 -1
- package/dist/ms-doc/tables/TableParser.js +0 -176
- package/dist/ms-doc/tables/TableParser.js.map +0 -1
- package/dist/ms-doc/text/PieceTable.d.ts +0 -21
- package/dist/ms-doc/text/PieceTable.d.ts.map +0 -1
- package/dist/ms-doc/text/PieceTable.js +0 -171
- package/dist/ms-doc/text/PieceTable.js.map +0 -1
- package/dist/ms-doc/types/Constants.d.ts +0 -99
- package/dist/ms-doc/types/Constants.d.ts.map +0 -1
- package/dist/ms-doc/types/Constants.js +0 -102
- package/dist/ms-doc/types/Constants.js.map +0 -1
- package/dist/ms-doc/types/DocTypes.d.ts +0 -368
- package/dist/ms-doc/types/DocTypes.d.ts.map +0 -1
- package/dist/ms-doc/types/DocTypes.js +0 -3
- package/dist/ms-doc/types/DocTypes.js.map +0 -1
- package/dist/tracking/index.d.ts +0 -3
- package/dist/tracking/index.d.ts.map +0 -1
- package/dist/tracking/index.js +0 -6
- package/dist/tracking/index.js.map +0 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML Sanitization Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for validating and sanitizing text content per XML 1.0 specification.
|
|
5
|
+
* Per XML 1.0, certain control characters are invalid and must be removed before
|
|
6
|
+
* including text in XML documents.
|
|
7
|
+
*
|
|
8
|
+
* Valid characters in XML 1.0:
|
|
9
|
+
* - 0x09 (tab), 0x0A (newline), 0x0D (carriage return)
|
|
10
|
+
* - 0x20-0xD7FF, 0xE000-0xFFFD, 0x10000-0x10FFFF
|
|
11
|
+
*
|
|
12
|
+
* Invalid characters (control characters that must be removed):
|
|
13
|
+
* - 0x00-0x08 (NULL through BACKSPACE)
|
|
14
|
+
* - 0x0B-0x0C (VERTICAL TAB and FORM FEED)
|
|
15
|
+
* - 0x0E-0x1F (SHIFT OUT through UNIT SEPARATOR)
|
|
16
|
+
* - 0x7F (DELETE)
|
|
17
|
+
*
|
|
18
|
+
* @module xmlSanitization
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { getGlobalLogger } from "./logger";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Regular expression matching invalid XML 1.0 control characters.
|
|
25
|
+
* Matches: 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F
|
|
26
|
+
* Does NOT match valid chars: 0x09 (tab), 0x0A (newline), 0x0D (CR)
|
|
27
|
+
*/
|
|
28
|
+
const INVALID_XML_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Removes invalid XML 1.0 control characters from text.
|
|
32
|
+
*
|
|
33
|
+
* Per XML 1.0 spec, characters 0x00-0x08, 0x0B-0x0C, 0x0E-0x1F, 0x7F are invalid
|
|
34
|
+
* and cannot appear in XML documents. This function removes them.
|
|
35
|
+
*
|
|
36
|
+
* Valid control characters are preserved:
|
|
37
|
+
* - Tab (0x09)
|
|
38
|
+
* - Line Feed / Newline (0x0A)
|
|
39
|
+
* - Carriage Return (0x0D)
|
|
40
|
+
*
|
|
41
|
+
* @param text - Input text to sanitize
|
|
42
|
+
* @param logWarning - If true, logs a warning when invalid chars are found (default: true)
|
|
43
|
+
* @returns Sanitized text with invalid control characters removed
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* // Remove NULL byte from text
|
|
48
|
+
* const clean = removeInvalidXmlChars("Hello\x00World");
|
|
49
|
+
* // Returns: "HelloWorld"
|
|
50
|
+
*
|
|
51
|
+
* // Tab and newline are preserved
|
|
52
|
+
* const preserved = removeInvalidXmlChars("Hello\tWorld\n");
|
|
53
|
+
* // Returns: "Hello\tWorld\n"
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function removeInvalidXmlChars(
|
|
57
|
+
text: string,
|
|
58
|
+
logWarning = true
|
|
59
|
+
): string {
|
|
60
|
+
// Reset regex lastIndex for global regex
|
|
61
|
+
INVALID_XML_CHAR_REGEX.lastIndex = 0;
|
|
62
|
+
|
|
63
|
+
if (logWarning && INVALID_XML_CHAR_REGEX.test(text)) {
|
|
64
|
+
// Reset regex lastIndex after test
|
|
65
|
+
INVALID_XML_CHAR_REGEX.lastIndex = 0;
|
|
66
|
+
|
|
67
|
+
const invalidChars = findInvalidXmlChars(text);
|
|
68
|
+
const hexCodes = invalidChars
|
|
69
|
+
.map((c) => `0x${c.toString(16).toUpperCase().padStart(2, "0")}`)
|
|
70
|
+
.join(", ");
|
|
71
|
+
getGlobalLogger().warn(
|
|
72
|
+
`[XMLSanitization] Removing invalid XML control characters: ${hexCodes}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Reset regex lastIndex before replace
|
|
77
|
+
INVALID_XML_CHAR_REGEX.lastIndex = 0;
|
|
78
|
+
return text.replace(INVALID_XML_CHAR_REGEX, "");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Finds all invalid XML 1.0 control characters in text.
|
|
83
|
+
*
|
|
84
|
+
* Returns an array of unique character codes that are invalid per XML 1.0 spec.
|
|
85
|
+
* This is useful for diagnostics and error reporting.
|
|
86
|
+
*
|
|
87
|
+
* @param text - Text to scan for invalid characters
|
|
88
|
+
* @returns Array of unique invalid character codes found, or empty array if text is valid
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const invalid = findInvalidXmlChars("Hello\x00\x08World");
|
|
93
|
+
* // Returns: [0, 8] - NULL and BACKSPACE codes
|
|
94
|
+
*
|
|
95
|
+
* const valid = findInvalidXmlChars("Hello\tWorld");
|
|
96
|
+
* // Returns: [] - tab is valid
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function findInvalidXmlChars(text: string): number[] {
|
|
100
|
+
const invalid: number[] = [];
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < text.length; i++) {
|
|
103
|
+
const code = text.charCodeAt(i);
|
|
104
|
+
|
|
105
|
+
// Check if character is in invalid ranges
|
|
106
|
+
if (
|
|
107
|
+
(code >= 0x00 && code <= 0x08) || // NULL through BACKSPACE
|
|
108
|
+
(code >= 0x0b && code <= 0x0c) || // VERTICAL TAB and FORM FEED
|
|
109
|
+
(code >= 0x0e && code <= 0x1f) || // SHIFT OUT through UNIT SEPARATOR
|
|
110
|
+
code === 0x7f // DELETE
|
|
111
|
+
) {
|
|
112
|
+
// Only add unique codes
|
|
113
|
+
if (!invalid.includes(code)) {
|
|
114
|
+
invalid.push(code);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return invalid;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Checks if text contains any invalid XML 1.0 control characters.
|
|
124
|
+
*
|
|
125
|
+
* This is a fast check that returns true/false without identifying specific characters.
|
|
126
|
+
* Use `findInvalidXmlChars()` if you need to know which characters are invalid.
|
|
127
|
+
*
|
|
128
|
+
* @param text - Text to check
|
|
129
|
+
* @returns true if text contains invalid characters, false otherwise
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* hasInvalidXmlChars("Hello\x00World"); // true - NULL byte
|
|
134
|
+
* hasInvalidXmlChars("Hello\tWorld"); // false - tab is valid
|
|
135
|
+
* hasInvalidXmlChars("Normal text"); // false
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export function hasInvalidXmlChars(text: string): boolean {
|
|
139
|
+
// Reset regex lastIndex for global regex
|
|
140
|
+
INVALID_XML_CHAR_REGEX.lastIndex = 0;
|
|
141
|
+
return INVALID_XML_CHAR_REGEX.test(text);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Character code constants for documentation and testing.
|
|
146
|
+
*/
|
|
147
|
+
export const XML_CONTROL_CHARS = {
|
|
148
|
+
/** NULL (0x00) - Invalid */
|
|
149
|
+
NULL: 0x00,
|
|
150
|
+
/** Start of Heading (0x01) - Invalid */
|
|
151
|
+
SOH: 0x01,
|
|
152
|
+
/** Start of Text (0x02) - Invalid */
|
|
153
|
+
STX: 0x02,
|
|
154
|
+
/** End of Text (0x03) - Invalid */
|
|
155
|
+
ETX: 0x03,
|
|
156
|
+
/** End of Transmission (0x04) - Invalid */
|
|
157
|
+
EOT: 0x04,
|
|
158
|
+
/** Enquiry (0x05) - Invalid */
|
|
159
|
+
ENQ: 0x05,
|
|
160
|
+
/** Acknowledge (0x06) - Invalid */
|
|
161
|
+
ACK: 0x06,
|
|
162
|
+
/** Bell (0x07) - Invalid */
|
|
163
|
+
BEL: 0x07,
|
|
164
|
+
/** Backspace (0x08) - Invalid */
|
|
165
|
+
BS: 0x08,
|
|
166
|
+
/** Horizontal Tab (0x09) - VALID */
|
|
167
|
+
TAB: 0x09,
|
|
168
|
+
/** Line Feed / Newline (0x0A) - VALID */
|
|
169
|
+
LF: 0x0a,
|
|
170
|
+
/** Vertical Tab (0x0B) - Invalid */
|
|
171
|
+
VT: 0x0b,
|
|
172
|
+
/** Form Feed (0x0C) - Invalid */
|
|
173
|
+
FF: 0x0c,
|
|
174
|
+
/** Carriage Return (0x0D) - VALID */
|
|
175
|
+
CR: 0x0d,
|
|
176
|
+
/** Shift Out (0x0E) - Invalid */
|
|
177
|
+
SO: 0x0e,
|
|
178
|
+
/** Unit Separator (0x1F) - Invalid */
|
|
179
|
+
US: 0x1f,
|
|
180
|
+
/** Delete (0x7F) - Invalid */
|
|
181
|
+
DEL: 0x7f,
|
|
182
|
+
} as const;
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RevisionAutoFixer - Automatically fixes revision validation issues
|
|
3
|
+
*
|
|
4
|
+
* Provides auto-fix capabilities for common revision compliance issues,
|
|
5
|
+
* helping to prevent document corruption while preserving user intent.
|
|
6
|
+
*
|
|
7
|
+
* @module RevisionAutoFixer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Document } from '../core/Document';
|
|
11
|
+
import type { Revision } from '../elements/Revision';
|
|
12
|
+
import {
|
|
13
|
+
REVISION_RULES,
|
|
14
|
+
AutoFixOptions,
|
|
15
|
+
AutoFixResult,
|
|
16
|
+
FixAction,
|
|
17
|
+
ValidationIssue,
|
|
18
|
+
} from './ValidationRules';
|
|
19
|
+
import { RevisionValidator } from './RevisionValidator';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Automatically fixes revision validation issues.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // Auto-fix all issues
|
|
27
|
+
* const result = RevisionAutoFixer.fix(doc);
|
|
28
|
+
* console.log(`Fixed ${result.issuesFixed} issues`);
|
|
29
|
+
*
|
|
30
|
+
* // Dry run (preview fixes without applying)
|
|
31
|
+
* const preview = RevisionAutoFixer.fix(doc, { dryRun: true });
|
|
32
|
+
* for (const action of preview.actions) {
|
|
33
|
+
* console.log(`Would fix: ${action.action}`);
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class RevisionAutoFixer {
|
|
38
|
+
/**
|
|
39
|
+
* Auto-fix all fixable issues in a document.
|
|
40
|
+
*
|
|
41
|
+
* @param doc - Document to fix
|
|
42
|
+
* @param options - Fix options
|
|
43
|
+
* @returns Result with details of all fixes applied
|
|
44
|
+
*/
|
|
45
|
+
static fix(doc: Document, options?: AutoFixOptions): AutoFixResult {
|
|
46
|
+
const actions: FixAction[] = [];
|
|
47
|
+
const errors: string[] = [];
|
|
48
|
+
const revisionManager = doc.getRevisionManager();
|
|
49
|
+
|
|
50
|
+
if (!revisionManager) {
|
|
51
|
+
return {
|
|
52
|
+
allFixed: true,
|
|
53
|
+
issuesFixed: 0,
|
|
54
|
+
issuesRemaining: 0,
|
|
55
|
+
actions: [],
|
|
56
|
+
errors: [],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const revisions = revisionManager.getAllRevisions();
|
|
61
|
+
const skipRules = new Set(options?.skipRules || []);
|
|
62
|
+
const onlyRules = options?.onlyRules ? new Set(options.onlyRules) : null;
|
|
63
|
+
|
|
64
|
+
// Helper to check if a rule should be processed
|
|
65
|
+
const shouldProcess = (ruleCode: string) => {
|
|
66
|
+
if (skipRules.has(ruleCode)) return false;
|
|
67
|
+
if (onlyRules && !onlyRules.has(ruleCode)) return false;
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// Fix duplicate IDs (REV001)
|
|
73
|
+
if (shouldProcess('REV001')) {
|
|
74
|
+
actions.push(...this.fixDuplicateIds(revisions, options?.dryRun));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Fix missing authors (REV002)
|
|
78
|
+
if (shouldProcess('REV002')) {
|
|
79
|
+
const defaultAuthor = options?.defaultAuthor || 'Unknown Author';
|
|
80
|
+
actions.push(...this.fixMissingAuthors(revisions, defaultAuthor, options?.dryRun));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Fix orphaned move markers (REV003, REV004)
|
|
84
|
+
if (shouldProcess('REV003') || shouldProcess('REV004')) {
|
|
85
|
+
actions.push(...this.fixOrphanedMoveMarkers(revisionManager, revisions, options?.dryRun));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Fix missing dates (REV101)
|
|
89
|
+
if (shouldProcess('REV101')) {
|
|
90
|
+
actions.push(...this.fixMissingDates(revisions, options?.dryRun));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Fix invalid dates (REV102)
|
|
94
|
+
if (shouldProcess('REV102')) {
|
|
95
|
+
actions.push(...this.fixInvalidDates(revisions, options?.dryRun));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Fix empty revisions (REV103)
|
|
99
|
+
if (shouldProcess('REV103')) {
|
|
100
|
+
actions.push(...this.fixEmptyRevisions(revisionManager, revisions, options?.dryRun));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Fix non-sequential IDs (REV104)
|
|
104
|
+
if (shouldProcess('REV104')) {
|
|
105
|
+
actions.push(...this.fixNonSequentialIds(revisions, options?.dryRun));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
} catch (error: unknown) {
|
|
109
|
+
errors.push(`Fix error: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Validate after fixes
|
|
113
|
+
const postValidation = RevisionValidator.validate(doc);
|
|
114
|
+
|
|
115
|
+
// Log actions if verbose
|
|
116
|
+
if (options?.verbose) {
|
|
117
|
+
for (const action of actions) {
|
|
118
|
+
console.log(`[AutoFix] ${action.action}: ${action.issue.code}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
allFixed: postValidation.valid,
|
|
124
|
+
issuesFixed: actions.filter(a => a.success).length,
|
|
125
|
+
issuesRemaining: postValidation.summary.errorCount + postValidation.summary.warningCount,
|
|
126
|
+
actions,
|
|
127
|
+
errors,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Fix duplicate revision IDs by reassigning.
|
|
133
|
+
*
|
|
134
|
+
* Assigns new unique IDs to revisions with duplicate IDs.
|
|
135
|
+
*
|
|
136
|
+
* @param revisions - Array of revisions
|
|
137
|
+
* @param dryRun - If true, only report changes without applying
|
|
138
|
+
* @returns Array of fix actions
|
|
139
|
+
*/
|
|
140
|
+
static fixDuplicateIds(revisions: Revision[], dryRun?: boolean): FixAction[] {
|
|
141
|
+
const actions: FixAction[] = [];
|
|
142
|
+
const usedIds = new Set<number>();
|
|
143
|
+
let nextId = Math.max(...revisions.map(r => r.getId()), 0) + 1;
|
|
144
|
+
|
|
145
|
+
for (const rev of revisions) {
|
|
146
|
+
const id = rev.getId();
|
|
147
|
+
|
|
148
|
+
if (usedIds.has(id)) {
|
|
149
|
+
const newId = nextId++;
|
|
150
|
+
const issue: ValidationIssue = {
|
|
151
|
+
code: REVISION_RULES.DUPLICATE_ID.code,
|
|
152
|
+
severity: 'error',
|
|
153
|
+
message: `Duplicate ID ${id}`,
|
|
154
|
+
location: { revisionId: id },
|
|
155
|
+
autoFixable: true,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
actions.push({
|
|
159
|
+
issue,
|
|
160
|
+
action: `Reassigned duplicate ID ${id} to ${newId}`,
|
|
161
|
+
before: id,
|
|
162
|
+
after: newId,
|
|
163
|
+
success: true,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (!dryRun) {
|
|
167
|
+
rev.setId(newId);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
usedIds.add(rev.getId());
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return actions;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Fix missing authors by setting a default value.
|
|
178
|
+
*
|
|
179
|
+
* @param revisions - Array of revisions
|
|
180
|
+
* @param defaultAuthor - Default author name to use
|
|
181
|
+
* @param dryRun - If true, only report changes without applying
|
|
182
|
+
* @returns Array of fix actions
|
|
183
|
+
*/
|
|
184
|
+
static fixMissingAuthors(
|
|
185
|
+
revisions: Revision[],
|
|
186
|
+
defaultAuthor: string,
|
|
187
|
+
dryRun?: boolean
|
|
188
|
+
): FixAction[] {
|
|
189
|
+
const actions: FixAction[] = [];
|
|
190
|
+
|
|
191
|
+
for (const rev of revisions) {
|
|
192
|
+
const author = rev.getAuthor();
|
|
193
|
+
|
|
194
|
+
if (!author || author.trim() === '') {
|
|
195
|
+
const issue: ValidationIssue = {
|
|
196
|
+
code: REVISION_RULES.MISSING_AUTHOR.code,
|
|
197
|
+
severity: 'error',
|
|
198
|
+
message: `Missing author for revision ${rev.getId()}`,
|
|
199
|
+
location: { revisionId: rev.getId() },
|
|
200
|
+
autoFixable: true,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
actions.push({
|
|
204
|
+
issue,
|
|
205
|
+
action: `Set author to "${defaultAuthor}" for revision ${rev.getId()}`,
|
|
206
|
+
before: author,
|
|
207
|
+
after: defaultAuthor,
|
|
208
|
+
success: true,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (!dryRun) {
|
|
212
|
+
rev.setAuthor(defaultAuthor);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return actions;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Fix missing dates by setting current date.
|
|
222
|
+
*
|
|
223
|
+
* @param revisions - Array of revisions
|
|
224
|
+
* @param dryRun - If true, only report changes without applying
|
|
225
|
+
* @returns Array of fix actions
|
|
226
|
+
*/
|
|
227
|
+
static fixMissingDates(revisions: Revision[], dryRun?: boolean): FixAction[] {
|
|
228
|
+
const actions: FixAction[] = [];
|
|
229
|
+
const now = new Date();
|
|
230
|
+
|
|
231
|
+
for (const rev of revisions) {
|
|
232
|
+
const date = rev.getDate();
|
|
233
|
+
|
|
234
|
+
if (!date) {
|
|
235
|
+
const issue: ValidationIssue = {
|
|
236
|
+
code: REVISION_RULES.MISSING_DATE.code,
|
|
237
|
+
severity: 'warning',
|
|
238
|
+
message: `Missing date for revision ${rev.getId()}`,
|
|
239
|
+
location: { revisionId: rev.getId() },
|
|
240
|
+
autoFixable: true,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
actions.push({
|
|
244
|
+
issue,
|
|
245
|
+
action: `Set date to ${now.toISOString()} for revision ${rev.getId()}`,
|
|
246
|
+
before: null,
|
|
247
|
+
after: now,
|
|
248
|
+
success: true,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (!dryRun) {
|
|
252
|
+
rev.setDate(now);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return actions;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Fix invalid dates by replacing with current date.
|
|
262
|
+
*
|
|
263
|
+
* @param revisions - Array of revisions
|
|
264
|
+
* @param dryRun - If true, only report changes without applying
|
|
265
|
+
* @returns Array of fix actions
|
|
266
|
+
*/
|
|
267
|
+
static fixInvalidDates(revisions: Revision[], dryRun?: boolean): FixAction[] {
|
|
268
|
+
const actions: FixAction[] = [];
|
|
269
|
+
const now = new Date();
|
|
270
|
+
|
|
271
|
+
for (const rev of revisions) {
|
|
272
|
+
const date = rev.getDate();
|
|
273
|
+
|
|
274
|
+
if (date && isNaN(date.getTime())) {
|
|
275
|
+
const issue: ValidationIssue = {
|
|
276
|
+
code: REVISION_RULES.INVALID_DATE_FORMAT.code,
|
|
277
|
+
severity: 'warning',
|
|
278
|
+
message: `Invalid date for revision ${rev.getId()}`,
|
|
279
|
+
location: { revisionId: rev.getId() },
|
|
280
|
+
autoFixable: true,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
actions.push({
|
|
284
|
+
issue,
|
|
285
|
+
action: `Replaced invalid date with ${now.toISOString()} for revision ${rev.getId()}`,
|
|
286
|
+
before: date,
|
|
287
|
+
after: now,
|
|
288
|
+
success: true,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (!dryRun) {
|
|
292
|
+
rev.setDate(now);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return actions;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Fix orphaned move markers by removing them.
|
|
302
|
+
*
|
|
303
|
+
* @param revisionManager - RevisionManager instance
|
|
304
|
+
* @param revisions - Array of revisions
|
|
305
|
+
* @param dryRun - If true, only report changes without applying
|
|
306
|
+
* @returns Array of fix actions
|
|
307
|
+
*/
|
|
308
|
+
static fixOrphanedMoveMarkers(
|
|
309
|
+
revisionManager: { removeById(id: number): boolean },
|
|
310
|
+
revisions: Revision[],
|
|
311
|
+
dryRun?: boolean
|
|
312
|
+
): FixAction[] {
|
|
313
|
+
const actions: FixAction[] = [];
|
|
314
|
+
|
|
315
|
+
const moveFromIds = new Map<string, Revision>();
|
|
316
|
+
const moveToIds = new Map<string, Revision>();
|
|
317
|
+
|
|
318
|
+
for (const rev of revisions) {
|
|
319
|
+
const moveId = rev.getMoveId();
|
|
320
|
+
if (!moveId) continue;
|
|
321
|
+
|
|
322
|
+
if (rev.getType() === 'moveFrom') {
|
|
323
|
+
moveFromIds.set(moveId, rev);
|
|
324
|
+
} else if (rev.getType() === 'moveTo') {
|
|
325
|
+
moveToIds.set(moveId, rev);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Remove orphaned moveFrom
|
|
330
|
+
for (const [moveId, rev] of moveFromIds) {
|
|
331
|
+
if (!moveToIds.has(moveId)) {
|
|
332
|
+
const issue: ValidationIssue = {
|
|
333
|
+
code: REVISION_RULES.ORPHANED_MOVE_FROM.code,
|
|
334
|
+
severity: 'error',
|
|
335
|
+
message: `Orphaned moveFrom with moveId="${moveId}"`,
|
|
336
|
+
location: { revisionId: rev.getId() },
|
|
337
|
+
autoFixable: true,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
actions.push({
|
|
341
|
+
issue,
|
|
342
|
+
action: `Removed orphaned moveFrom (ID: ${rev.getId()}, moveId: ${moveId})`,
|
|
343
|
+
before: { type: 'moveFrom', moveId },
|
|
344
|
+
after: null,
|
|
345
|
+
success: true,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (!dryRun) {
|
|
349
|
+
revisionManager.removeById(rev.getId());
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Remove orphaned moveTo
|
|
355
|
+
for (const [moveId, rev] of moveToIds) {
|
|
356
|
+
if (!moveFromIds.has(moveId)) {
|
|
357
|
+
const issue: ValidationIssue = {
|
|
358
|
+
code: REVISION_RULES.ORPHANED_MOVE_TO.code,
|
|
359
|
+
severity: 'error',
|
|
360
|
+
message: `Orphaned moveTo with moveId="${moveId}"`,
|
|
361
|
+
location: { revisionId: rev.getId() },
|
|
362
|
+
autoFixable: true,
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
actions.push({
|
|
366
|
+
issue,
|
|
367
|
+
action: `Removed orphaned moveTo (ID: ${rev.getId()}, moveId: ${moveId})`,
|
|
368
|
+
before: { type: 'moveTo', moveId },
|
|
369
|
+
after: null,
|
|
370
|
+
success: true,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (!dryRun) {
|
|
374
|
+
revisionManager.removeById(rev.getId());
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return actions;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Fix empty revisions by removing them.
|
|
384
|
+
*
|
|
385
|
+
* @param revisionManager - RevisionManager instance
|
|
386
|
+
* @param revisions - Array of revisions
|
|
387
|
+
* @param dryRun - If true, only report changes without applying
|
|
388
|
+
* @returns Array of fix actions
|
|
389
|
+
*/
|
|
390
|
+
static fixEmptyRevisions(
|
|
391
|
+
revisionManager: any,
|
|
392
|
+
revisions: Revision[],
|
|
393
|
+
dryRun?: boolean
|
|
394
|
+
): FixAction[] {
|
|
395
|
+
const actions: FixAction[] = [];
|
|
396
|
+
|
|
397
|
+
const propertyChangeTypes = [
|
|
398
|
+
'runPropertiesChange',
|
|
399
|
+
'paragraphPropertiesChange',
|
|
400
|
+
'tablePropertiesChange',
|
|
401
|
+
'tableExceptionPropertiesChange',
|
|
402
|
+
'tableRowPropertiesChange',
|
|
403
|
+
'tableCellPropertiesChange',
|
|
404
|
+
'sectionPropertiesChange',
|
|
405
|
+
'numberingChange',
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
for (const rev of revisions) {
|
|
409
|
+
const type = rev.getType();
|
|
410
|
+
|
|
411
|
+
// Skip property changes
|
|
412
|
+
if (propertyChangeTypes.includes(type)) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const runs = rev.getRuns();
|
|
417
|
+
const hasContent = runs.length > 0 && runs.some(r => r.getText().length > 0);
|
|
418
|
+
|
|
419
|
+
if (!hasContent) {
|
|
420
|
+
const issue: ValidationIssue = {
|
|
421
|
+
code: REVISION_RULES.EMPTY_REVISION.code,
|
|
422
|
+
severity: 'warning',
|
|
423
|
+
message: `Empty revision ${rev.getId()} (type: ${type})`,
|
|
424
|
+
location: { revisionId: rev.getId() },
|
|
425
|
+
autoFixable: true,
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
actions.push({
|
|
429
|
+
issue,
|
|
430
|
+
action: `Removed empty revision (ID: ${rev.getId()}, type: ${type})`,
|
|
431
|
+
before: { id: rev.getId(), type },
|
|
432
|
+
after: null,
|
|
433
|
+
success: true,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (!dryRun) {
|
|
437
|
+
revisionManager.removeById(rev.getId());
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return actions;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Fix non-sequential IDs by reassigning.
|
|
447
|
+
*
|
|
448
|
+
* @param revisions - Array of revisions
|
|
449
|
+
* @param dryRun - If true, only report changes without applying
|
|
450
|
+
* @returns Array of fix actions
|
|
451
|
+
*/
|
|
452
|
+
static fixNonSequentialIds(revisions: Revision[], dryRun?: boolean): FixAction[] {
|
|
453
|
+
const actions: FixAction[] = [];
|
|
454
|
+
|
|
455
|
+
if (revisions.length === 0) return actions;
|
|
456
|
+
|
|
457
|
+
// Check if IDs are already sequential
|
|
458
|
+
const ids = revisions.map(r => r.getId()).sort((a, b) => a - b);
|
|
459
|
+
let isSequential = true;
|
|
460
|
+
|
|
461
|
+
for (let i = 1; i < ids.length; i++) {
|
|
462
|
+
const currentId = ids[i]!;
|
|
463
|
+
const prevId = ids[i - 1]!;
|
|
464
|
+
if (currentId !== prevId + 1) {
|
|
465
|
+
isSequential = false;
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!isSequential) {
|
|
471
|
+
const issue: ValidationIssue = {
|
|
472
|
+
code: REVISION_RULES.NON_SEQUENTIAL_IDS.code,
|
|
473
|
+
severity: 'warning',
|
|
474
|
+
message: 'Revision IDs are not sequential',
|
|
475
|
+
autoFixable: true,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const oldIds = revisions.map(r => r.getId());
|
|
479
|
+
const newIds: number[] = [];
|
|
480
|
+
|
|
481
|
+
revisions.forEach((rev, index) => {
|
|
482
|
+
newIds.push(index);
|
|
483
|
+
if (!dryRun) {
|
|
484
|
+
rev.setId(index);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
actions.push({
|
|
489
|
+
issue,
|
|
490
|
+
action: `Reassigned ${revisions.length} revision IDs to be sequential (0 to ${revisions.length - 1})`,
|
|
491
|
+
before: oldIds,
|
|
492
|
+
after: newIds,
|
|
493
|
+
success: true,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return actions;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Preview fixes without applying them.
|
|
502
|
+
*
|
|
503
|
+
* Convenience method that calls fix() with dryRun: true.
|
|
504
|
+
*
|
|
505
|
+
* @param doc - Document to preview fixes for
|
|
506
|
+
* @param options - Fix options (dryRun is forced to true)
|
|
507
|
+
* @returns Result showing what would be fixed
|
|
508
|
+
*/
|
|
509
|
+
static preview(doc: Document, options?: Omit<AutoFixOptions, 'dryRun'>): AutoFixResult {
|
|
510
|
+
return this.fix(doc, { ...options, dryRun: true });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Format fix result as a human-readable string.
|
|
515
|
+
*
|
|
516
|
+
* @param result - AutoFixResult to format
|
|
517
|
+
* @returns Formatted string
|
|
518
|
+
*/
|
|
519
|
+
static formatResult(result: AutoFixResult): string {
|
|
520
|
+
const lines: string[] = [];
|
|
521
|
+
|
|
522
|
+
lines.push(`Auto-Fix ${result.allFixed ? 'COMPLETE' : 'PARTIAL'}`);
|
|
523
|
+
lines.push(`Fixed: ${result.issuesFixed}, Remaining: ${result.issuesRemaining}`);
|
|
524
|
+
|
|
525
|
+
if (result.actions.length > 0) {
|
|
526
|
+
lines.push('\nActions taken:');
|
|
527
|
+
for (const action of result.actions) {
|
|
528
|
+
const status = action.success ? 'OK' : 'FAILED';
|
|
529
|
+
lines.push(` [${status}] ${action.action}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (result.errors.length > 0) {
|
|
534
|
+
lines.push('\nErrors:');
|
|
535
|
+
for (const error of result.errors) {
|
|
536
|
+
lines.push(` - ${error}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return lines.join('\n');
|
|
541
|
+
}
|
|
542
|
+
}
|