docxmlater 10.4.1 → 11.0.4
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 +3 -3
- package/dist/constants/legacyCompatFlags.d.ts +1 -1
- package/dist/constants/legacyCompatFlags.d.ts.map +1 -1
- package/dist/constants/legacyCompatFlags.js.map +1 -1
- package/dist/core/Document.d.ts +74 -67
- package/dist/core/Document.d.ts.map +1 -1
- package/dist/core/Document.js +605 -414
- package/dist/core/Document.js.map +1 -1
- package/dist/core/DocumentContent.d.ts +11 -10
- package/dist/core/DocumentContent.d.ts.map +1 -1
- package/dist/core/DocumentContent.js +19 -19
- package/dist/core/DocumentContent.js.map +1 -1
- package/dist/core/DocumentEvents.d.ts +39 -0
- package/dist/core/DocumentEvents.d.ts.map +1 -0
- package/dist/core/DocumentEvents.js +51 -0
- package/dist/core/DocumentEvents.js.map +1 -0
- package/dist/core/DocumentGenerator.d.ts +11 -11
- package/dist/core/DocumentGenerator.d.ts.map +1 -1
- package/dist/core/DocumentGenerator.js +72 -52
- package/dist/core/DocumentGenerator.js.map +1 -1
- package/dist/core/DocumentParser.d.ts +15 -15
- package/dist/core/DocumentParser.d.ts.map +1 -1
- package/dist/core/DocumentParser.js +2056 -1073
- package/dist/core/DocumentParser.js.map +1 -1
- package/dist/core/DocumentValidator.d.ts +3 -3
- package/dist/core/DocumentValidator.d.ts.map +1 -1
- package/dist/core/DocumentValidator.js +31 -31
- package/dist/core/DocumentValidator.js.map +1 -1
- package/dist/core/ElementRegistry.d.ts +22 -0
- package/dist/core/ElementRegistry.d.ts.map +1 -0
- package/dist/core/ElementRegistry.js +27 -0
- package/dist/core/ElementRegistry.js.map +1 -0
- package/dist/core/Relationship.js +4 -4
- package/dist/core/Relationship.js.map +1 -1
- package/dist/core/RelationshipManager.d.ts +1 -1
- package/dist/core/RelationshipManager.d.ts.map +1 -1
- package/dist/core/RelationshipManager.js +32 -32
- package/dist/core/RelationshipManager.js.map +1 -1
- package/dist/elements/AlternateContent.d.ts +1 -1
- package/dist/elements/AlternateContent.d.ts.map +1 -1
- package/dist/elements/AlternateContent.js.map +1 -1
- package/dist/elements/Bookmark.d.ts +6 -1
- package/dist/elements/Bookmark.d.ts.map +1 -1
- package/dist/elements/Bookmark.js +19 -3
- package/dist/elements/Bookmark.js.map +1 -1
- package/dist/elements/BookmarkManager.d.ts +1 -1
- package/dist/elements/BookmarkManager.d.ts.map +1 -1
- package/dist/elements/BookmarkManager.js +7 -7
- package/dist/elements/BookmarkManager.js.map +1 -1
- package/dist/elements/Comment.d.ts +2 -2
- package/dist/elements/Comment.d.ts.map +1 -1
- package/dist/elements/Comment.js +4 -4
- package/dist/elements/Comment.js.map +1 -1
- package/dist/elements/CommentManager.d.ts +2 -2
- package/dist/elements/CommentManager.d.ts.map +1 -1
- package/dist/elements/CommentManager.js +9 -9
- package/dist/elements/CommentManager.js.map +1 -1
- package/dist/elements/CommonTypes.d.ts +9 -4
- package/dist/elements/CommonTypes.d.ts.map +1 -1
- package/dist/elements/CommonTypes.js +1 -0
- package/dist/elements/CommonTypes.js.map +1 -1
- package/dist/elements/CustomXml.d.ts +1 -1
- package/dist/elements/CustomXml.d.ts.map +1 -1
- package/dist/elements/CustomXml.js.map +1 -1
- package/dist/elements/Endnote.d.ts +2 -2
- package/dist/elements/Endnote.d.ts.map +1 -1
- package/dist/elements/Endnote.js +9 -9
- package/dist/elements/Endnote.js.map +1 -1
- package/dist/elements/EndnoteManager.d.ts +1 -1
- package/dist/elements/EndnoteManager.d.ts.map +1 -1
- package/dist/elements/EndnoteManager.js +11 -11
- package/dist/elements/EndnoteManager.js.map +1 -1
- package/dist/elements/Field.d.ts +9 -5
- package/dist/elements/Field.d.ts.map +1 -1
- package/dist/elements/Field.js +21 -9
- package/dist/elements/Field.js.map +1 -1
- package/dist/elements/FieldHelpers.d.ts +1 -1
- package/dist/elements/FieldHelpers.d.ts.map +1 -1
- package/dist/elements/FieldHelpers.js +10 -10
- package/dist/elements/FieldHelpers.js.map +1 -1
- package/dist/elements/Footer.d.ts +3 -3
- package/dist/elements/Footer.d.ts.map +1 -1
- package/dist/elements/Footer.js +5 -5
- package/dist/elements/Footer.js.map +1 -1
- package/dist/elements/Footnote.d.ts +2 -2
- package/dist/elements/Footnote.d.ts.map +1 -1
- package/dist/elements/Footnote.js +9 -9
- package/dist/elements/Footnote.js.map +1 -1
- package/dist/elements/FootnoteManager.d.ts +1 -1
- package/dist/elements/FootnoteManager.d.ts.map +1 -1
- package/dist/elements/FootnoteManager.js +11 -11
- package/dist/elements/FootnoteManager.js.map +1 -1
- package/dist/elements/Header.d.ts +3 -3
- package/dist/elements/Header.d.ts.map +1 -1
- package/dist/elements/Header.js +5 -5
- package/dist/elements/Header.js.map +1 -1
- package/dist/elements/HeaderFooterManager.d.ts +2 -2
- package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
- package/dist/elements/HeaderFooterManager.js.map +1 -1
- package/dist/elements/Hyperlink.d.ts +5 -5
- package/dist/elements/Hyperlink.d.ts.map +1 -1
- package/dist/elements/Hyperlink.js +29 -29
- package/dist/elements/Hyperlink.js.map +1 -1
- package/dist/elements/Image.d.ts +1 -1
- package/dist/elements/Image.d.ts.map +1 -1
- package/dist/elements/Image.js +67 -67
- package/dist/elements/Image.js.map +1 -1
- package/dist/elements/ImageManager.d.ts +1 -1
- package/dist/elements/ImageManager.d.ts.map +1 -1
- package/dist/elements/ImageManager.js +4 -4
- package/dist/elements/ImageManager.js.map +1 -1
- package/dist/elements/ImageRun.d.ts +3 -3
- package/dist/elements/ImageRun.d.ts.map +1 -1
- package/dist/elements/ImageRun.js +2 -2
- package/dist/elements/ImageRun.js.map +1 -1
- package/dist/elements/MathElement.d.ts +1 -1
- package/dist/elements/MathElement.d.ts.map +1 -1
- package/dist/elements/MathElement.js.map +1 -1
- package/dist/elements/Paragraph.d.ts +34 -19
- package/dist/elements/Paragraph.d.ts.map +1 -1
- package/dist/elements/Paragraph.js +286 -231
- package/dist/elements/Paragraph.js.map +1 -1
- package/dist/elements/PreservedElement.d.ts +1 -1
- package/dist/elements/PreservedElement.d.ts.map +1 -1
- package/dist/elements/PreservedElement.js.map +1 -1
- package/dist/elements/PropertyChangeTypes.d.ts +2 -2
- package/dist/elements/PropertyChangeTypes.d.ts.map +1 -1
- package/dist/elements/PropertyChangeTypes.js.map +1 -1
- package/dist/elements/RangeMarker.d.ts +14 -1
- package/dist/elements/RangeMarker.d.ts.map +1 -1
- package/dist/elements/RangeMarker.js +46 -8
- package/dist/elements/RangeMarker.js.map +1 -1
- package/dist/elements/RegisteredBodyElement.d.ts +15 -0
- package/dist/elements/RegisteredBodyElement.d.ts.map +1 -0
- package/dist/elements/RegisteredBodyElement.js +44 -0
- package/dist/elements/RegisteredBodyElement.js.map +1 -0
- package/dist/elements/Revision.d.ts +8 -8
- package/dist/elements/Revision.d.ts.map +1 -1
- package/dist/elements/Revision.js +12 -12
- package/dist/elements/Revision.js.map +1 -1
- package/dist/elements/RevisionContent.d.ts +3 -3
- package/dist/elements/RevisionContent.d.ts.map +1 -1
- package/dist/elements/RevisionContent.js.map +1 -1
- package/dist/elements/RevisionManager.d.ts +2 -2
- package/dist/elements/RevisionManager.d.ts.map +1 -1
- package/dist/elements/RevisionManager.js +2 -2
- package/dist/elements/RevisionManager.js.map +1 -1
- package/dist/elements/Run.d.ts +16 -10
- package/dist/elements/Run.d.ts.map +1 -1
- package/dist/elements/Run.js +199 -173
- package/dist/elements/Run.js.map +1 -1
- package/dist/elements/Section.d.ts +4 -2
- package/dist/elements/Section.d.ts.map +1 -1
- package/dist/elements/Section.js +152 -145
- package/dist/elements/Section.js.map +1 -1
- package/dist/elements/Shape.d.ts +3 -3
- package/dist/elements/Shape.d.ts.map +1 -1
- package/dist/elements/Shape.js +12 -12
- package/dist/elements/Shape.js.map +1 -1
- package/dist/elements/StructuredDocumentTag.d.ts +3 -3
- package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
- package/dist/elements/StructuredDocumentTag.js +39 -39
- package/dist/elements/StructuredDocumentTag.js.map +1 -1
- package/dist/elements/Table.d.ts +16 -10
- package/dist/elements/Table.d.ts.map +1 -1
- package/dist/elements/Table.js +118 -89
- package/dist/elements/Table.js.map +1 -1
- package/dist/elements/TableCell.d.ts +11 -11
- package/dist/elements/TableCell.d.ts.map +1 -1
- package/dist/elements/TableCell.js +108 -78
- package/dist/elements/TableCell.js.map +1 -1
- package/dist/elements/TableGridChange.d.ts +1 -1
- package/dist/elements/TableGridChange.d.ts.map +1 -1
- package/dist/elements/TableGridChange.js +3 -3
- package/dist/elements/TableGridChange.js.map +1 -1
- package/dist/elements/TableOfContents.d.ts +1 -1
- package/dist/elements/TableOfContents.d.ts.map +1 -1
- package/dist/elements/TableOfContents.js +2 -2
- package/dist/elements/TableOfContents.js.map +1 -1
- package/dist/elements/TableOfContentsElement.d.ts +2 -2
- package/dist/elements/TableOfContentsElement.d.ts.map +1 -1
- package/dist/elements/TableOfContentsElement.js +5 -5
- package/dist/elements/TableOfContentsElement.js.map +1 -1
- package/dist/elements/TableRow.d.ts +18 -7
- package/dist/elements/TableRow.d.ts.map +1 -1
- package/dist/elements/TableRow.js +127 -74
- package/dist/elements/TableRow.js.map +1 -1
- package/dist/elements/TextBox.d.ts +4 -4
- package/dist/elements/TextBox.d.ts.map +1 -1
- package/dist/elements/TextBox.js +6 -6
- package/dist/elements/TextBox.js.map +1 -1
- package/dist/esm/constants/legacyCompatFlags.js +97 -0
- package/dist/esm/constants/legacyCompatFlags.js.map +1 -0
- package/dist/esm/constants/limits.js +36 -0
- package/dist/esm/constants/limits.js.map +1 -0
- package/dist/esm/core/Document.js +8498 -0
- package/dist/esm/core/Document.js.map +1 -0
- package/dist/esm/core/DocumentContent.js +190 -0
- package/dist/esm/core/DocumentContent.js.map +1 -0
- package/dist/esm/core/DocumentEvents.js +47 -0
- package/dist/esm/core/DocumentEvents.js.map +1 -0
- package/dist/esm/core/DocumentGenerator.js +764 -0
- package/dist/esm/core/DocumentGenerator.js.map +1 -0
- package/dist/esm/core/DocumentIdManager.js +67 -0
- package/dist/esm/core/DocumentIdManager.js.map +1 -0
- package/dist/esm/core/DocumentParser.js +8760 -0
- package/dist/esm/core/DocumentParser.js.map +1 -0
- package/dist/esm/core/DocumentValidator.js +222 -0
- package/dist/esm/core/DocumentValidator.js.map +1 -0
- package/dist/esm/core/ElementRegistry.js +24 -0
- package/dist/esm/core/ElementRegistry.js.map +1 -0
- package/dist/esm/core/Relationship.js +177 -0
- package/dist/esm/core/Relationship.js.map +1 -0
- package/dist/esm/core/RelationshipManager.js +202 -0
- package/dist/esm/core/RelationshipManager.js.map +1 -0
- package/dist/esm/elements/AlternateContent.js +19 -0
- package/dist/esm/elements/AlternateContent.js.map +1 -0
- package/dist/esm/elements/Bookmark.js +115 -0
- package/dist/esm/elements/Bookmark.js.map +1 -0
- package/dist/esm/elements/BookmarkManager.js +99 -0
- package/dist/esm/elements/BookmarkManager.js.map +1 -0
- package/dist/esm/elements/Comment.js +181 -0
- package/dist/esm/elements/Comment.js.map +1 -0
- package/dist/esm/elements/CommentManager.js +233 -0
- package/dist/esm/elements/CommentManager.js.map +1 -0
- package/dist/esm/elements/CommonTypes.js +106 -0
- package/dist/esm/elements/CommonTypes.js.map +1 -0
- package/dist/esm/elements/CustomXml.js +19 -0
- package/dist/esm/elements/CustomXml.js.map +1 -0
- package/dist/esm/elements/Endnote.js +107 -0
- package/dist/esm/elements/Endnote.js.map +1 -0
- package/dist/esm/elements/EndnoteManager.js +119 -0
- package/dist/esm/elements/EndnoteManager.js.map +1 -0
- package/dist/esm/elements/Field.js +856 -0
- package/dist/esm/elements/Field.js.map +1 -0
- package/dist/esm/elements/FieldHelpers.js +134 -0
- package/dist/esm/elements/FieldHelpers.js.map +1 -0
- package/dist/esm/elements/FontManager.js +158 -0
- package/dist/esm/elements/FontManager.js.map +1 -0
- package/dist/esm/elements/Footer.js +141 -0
- package/dist/esm/elements/Footer.js.map +1 -0
- package/dist/esm/elements/Footnote.js +107 -0
- package/dist/esm/elements/Footnote.js.map +1 -0
- package/dist/esm/elements/FootnoteManager.js +119 -0
- package/dist/esm/elements/FootnoteManager.js.map +1 -0
- package/dist/esm/elements/Header.js +141 -0
- package/dist/esm/elements/Header.js.map +1 -0
- package/dist/esm/elements/HeaderFooterManager.js +87 -0
- package/dist/esm/elements/HeaderFooterManager.js.map +1 -0
- package/dist/esm/elements/Hyperlink.js +586 -0
- package/dist/esm/elements/Hyperlink.js.map +1 -0
- package/dist/esm/elements/Image.js +1288 -0
- package/dist/esm/elements/Image.js.map +1 -0
- package/dist/esm/elements/ImageManager.js +223 -0
- package/dist/esm/elements/ImageManager.js.map +1 -0
- package/dist/esm/elements/ImageRun.js +29 -0
- package/dist/esm/elements/ImageRun.js.map +1 -0
- package/dist/esm/elements/MathElement.js +37 -0
- package/dist/esm/elements/MathElement.js.map +1 -0
- package/dist/esm/elements/Paragraph.js +2308 -0
- package/dist/esm/elements/Paragraph.js.map +1 -0
- package/dist/esm/elements/PreservedElement.js +29 -0
- package/dist/esm/elements/PreservedElement.js.map +1 -0
- package/dist/esm/elements/PropertyChangeTypes.js +53 -0
- package/dist/esm/elements/PropertyChangeTypes.js.map +1 -0
- package/dist/esm/elements/RangeMarker.js +219 -0
- package/dist/esm/elements/RangeMarker.js.map +1 -0
- package/dist/esm/elements/RegisteredBodyElement.js +40 -0
- package/dist/esm/elements/RegisteredBodyElement.js.map +1 -0
- package/dist/esm/elements/Revision.js +498 -0
- package/dist/esm/elements/Revision.js.map +1 -0
- package/dist/esm/elements/RevisionContent.js +18 -0
- package/dist/esm/elements/RevisionContent.js.map +1 -0
- package/dist/esm/elements/RevisionManager.js +486 -0
- package/dist/esm/elements/RevisionManager.js.map +1 -0
- package/dist/esm/elements/Run.js +1465 -0
- package/dist/esm/elements/Run.js.map +1 -0
- package/dist/esm/elements/Section.js +978 -0
- package/dist/esm/elements/Section.js.map +1 -0
- package/dist/esm/elements/Shape.js +493 -0
- package/dist/esm/elements/Shape.js.map +1 -0
- package/dist/esm/elements/StructuredDocumentTag.js +471 -0
- package/dist/esm/elements/StructuredDocumentTag.js.map +1 -0
- package/dist/esm/elements/Table.js +1456 -0
- package/dist/esm/elements/Table.js.map +1 -0
- package/dist/esm/elements/TableCell.js +835 -0
- package/dist/esm/elements/TableCell.js.map +1 -0
- package/dist/esm/elements/TableGridChange.js +52 -0
- package/dist/esm/elements/TableGridChange.js.map +1 -0
- package/dist/esm/elements/TableOfContents.js +389 -0
- package/dist/esm/elements/TableOfContents.js.map +1 -0
- package/dist/esm/elements/TableOfContentsElement.js +29 -0
- package/dist/esm/elements/TableOfContentsElement.js.map +1 -0
- package/dist/esm/elements/TableRow.js +555 -0
- package/dist/esm/elements/TableRow.js.map +1 -0
- package/dist/esm/elements/TextBox.js +459 -0
- package/dist/esm/elements/TextBox.js.map +1 -0
- package/dist/esm/formatting/AbstractNumbering.js +325 -0
- package/dist/esm/formatting/AbstractNumbering.js.map +1 -0
- package/dist/esm/formatting/NumberingInstance.js +150 -0
- package/dist/esm/formatting/NumberingInstance.js.map +1 -0
- package/dist/esm/formatting/NumberingLevel.js +608 -0
- package/dist/esm/formatting/NumberingLevel.js.map +1 -0
- package/dist/esm/formatting/NumberingManager.js +423 -0
- package/dist/esm/formatting/NumberingManager.js.map +1 -0
- package/dist/esm/formatting/Style.js +1151 -0
- package/dist/esm/formatting/Style.js.map +1 -0
- package/dist/esm/formatting/StylesManager.js +557 -0
- package/dist/esm/formatting/StylesManager.js.map +1 -0
- package/dist/esm/helpers/CleanupHelper.js +350 -0
- package/dist/esm/helpers/CleanupHelper.js.map +1 -0
- package/dist/esm/images/ImageOptimizer.js +161 -0
- package/dist/esm/images/ImageOptimizer.js.map +1 -0
- package/dist/esm/index.js +75 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal.js +16 -0
- package/dist/esm/internal.js.map +1 -0
- package/dist/esm/managers/DrawingManager.js +163 -0
- package/dist/esm/managers/DrawingManager.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/processors/ChangelogGenerator.js +970 -0
- package/dist/esm/processors/ChangelogGenerator.js.map +1 -0
- package/dist/esm/processors/CompatibilityUpgrader.js +130 -0
- package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -0
- package/dist/esm/processors/InMemoryRevisionAcceptor.js +530 -0
- package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -0
- package/dist/esm/processors/MoveOperationHelper.js +57 -0
- package/dist/esm/processors/MoveOperationHelper.js.map +1 -0
- package/dist/esm/processors/RevisionAwareProcessor.js +232 -0
- package/dist/esm/processors/RevisionAwareProcessor.js.map +1 -0
- package/dist/esm/processors/RevisionWalker.js +278 -0
- package/dist/esm/processors/RevisionWalker.js.map +1 -0
- package/dist/{utils → esm/processors}/SelectiveRevisionAcceptor.js +81 -42
- package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -0
- package/dist/esm/processors/ShadingResolver.js +66 -0
- package/dist/esm/processors/ShadingResolver.js.map +1 -0
- package/dist/esm/processors/acceptRevisions.js +416 -0
- package/dist/esm/processors/acceptRevisions.js.map +1 -0
- package/dist/esm/processors/cnfStyleDecoder.js +89 -0
- package/dist/esm/processors/cnfStyleDecoder.js.map +1 -0
- package/dist/esm/processors/stripTrackedChanges.js +201 -0
- package/dist/esm/processors/stripTrackedChanges.js.map +1 -0
- package/dist/esm/tracking/DocumentTrackingContext.js +531 -0
- package/dist/esm/tracking/DocumentTrackingContext.js.map +1 -0
- package/dist/esm/tracking/TrackingContext.js +2 -0
- package/dist/esm/tracking/TrackingContext.js.map +1 -0
- package/dist/esm/types/compatibility-types.js +8 -0
- package/dist/esm/types/compatibility-types.js.map +1 -0
- package/dist/esm/types/document-types.js +2 -0
- package/dist/esm/types/document-types.js.map +1 -0
- package/dist/esm/types/formatting.js +2 -0
- package/dist/esm/types/formatting.js.map +1 -0
- package/dist/esm/types/list-types.js +2 -0
- package/dist/esm/types/list-types.js.map +1 -0
- package/dist/esm/types/settings-types.js +2 -0
- package/dist/esm/types/settings-types.js.map +1 -0
- package/dist/esm/types/styleConfig.js +2 -0
- package/dist/esm/types/styleConfig.js.map +1 -0
- package/dist/esm/utils/KeyedRegistry.js +32 -0
- package/dist/esm/utils/KeyedRegistry.js.map +1 -0
- package/dist/esm/utils/corruptionDetection.js +155 -0
- package/dist/esm/utils/corruptionDetection.js.map +1 -0
- package/dist/esm/utils/dateFormatting.js +4 -0
- package/dist/esm/utils/dateFormatting.js.map +1 -0
- package/dist/esm/utils/deepClone.js +40 -0
- package/dist/esm/utils/deepClone.js.map +1 -0
- package/dist/esm/utils/deepEqual.js +47 -0
- package/dist/esm/utils/deepEqual.js.map +1 -0
- package/dist/esm/utils/diagnostics.js +69 -0
- package/dist/esm/utils/diagnostics.js.map +1 -0
- package/dist/esm/utils/errorHandling.js +36 -0
- package/dist/esm/utils/errorHandling.js.map +1 -0
- package/dist/esm/utils/formatting.js +93 -0
- package/dist/esm/utils/formatting.js.map +1 -0
- package/dist/esm/utils/list-detection.js +148 -0
- package/dist/esm/utils/list-detection.js.map +1 -0
- package/dist/esm/utils/logger.js +205 -0
- package/dist/esm/utils/logger.js.map +1 -0
- package/dist/esm/utils/parsingHelpers.js +56 -0
- package/dist/esm/utils/parsingHelpers.js.map +1 -0
- package/dist/esm/utils/textDiff.js +42 -0
- package/dist/esm/utils/textDiff.js.map +1 -0
- package/dist/esm/utils/units.js +152 -0
- package/dist/esm/utils/units.js.map +1 -0
- package/dist/esm/utils/validation.js +285 -0
- package/dist/esm/utils/validation.js.map +1 -0
- package/dist/esm/utils/xmlSanitization.js +54 -0
- package/dist/esm/utils/xmlSanitization.js.map +1 -0
- package/dist/esm/validation/RevisionAutoFixer.js +340 -0
- package/dist/esm/validation/RevisionAutoFixer.js.map +1 -0
- package/dist/esm/validation/RevisionValidator.js +240 -0
- package/dist/esm/validation/RevisionValidator.js.map +1 -0
- package/dist/esm/validation/ValidationRuleRegistry.js +40 -0
- package/dist/esm/validation/ValidationRuleRegistry.js.map +1 -0
- package/dist/esm/validation/ValidationRules.js +92 -0
- package/dist/esm/validation/ValidationRules.js.map +1 -0
- package/dist/esm/validation/index.js +4 -0
- package/dist/esm/validation/index.js.map +1 -0
- package/dist/esm/xml/XMLBuilder.js +434 -0
- package/dist/esm/xml/XMLBuilder.js.map +1 -0
- package/dist/esm/xml/XMLParser.js +486 -0
- package/dist/esm/xml/XMLParser.js.map +1 -0
- package/dist/esm/zip/ZipHandler.js +298 -0
- package/dist/esm/zip/ZipHandler.js.map +1 -0
- package/dist/esm/zip/ZipReader.js +147 -0
- package/dist/esm/zip/ZipReader.js.map +1 -0
- package/dist/esm/zip/ZipWriter.js +199 -0
- package/dist/esm/zip/ZipWriter.js.map +1 -0
- package/dist/esm/zip/errors.js +43 -0
- package/dist/esm/zip/errors.js.map +1 -0
- package/dist/esm/zip/types.js +31 -0
- package/dist/esm/zip/types.js.map +1 -0
- package/dist/formatting/AbstractNumbering.d.ts +2 -2
- package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
- package/dist/formatting/AbstractNumbering.js +33 -33
- package/dist/formatting/AbstractNumbering.js.map +1 -1
- package/dist/formatting/NumberingInstance.d.ts +2 -2
- package/dist/formatting/NumberingInstance.d.ts.map +1 -1
- package/dist/formatting/NumberingInstance.js +7 -7
- package/dist/formatting/NumberingInstance.js.map +1 -1
- package/dist/formatting/NumberingLevel.d.ts +11 -2
- package/dist/formatting/NumberingLevel.d.ts.map +1 -1
- package/dist/formatting/NumberingLevel.js +111 -25
- package/dist/formatting/NumberingLevel.js.map +1 -1
- package/dist/formatting/NumberingManager.d.ts +4 -4
- package/dist/formatting/NumberingManager.d.ts.map +1 -1
- package/dist/formatting/NumberingManager.js +28 -28
- package/dist/formatting/NumberingManager.js.map +1 -1
- package/dist/formatting/Style.d.ts +14 -7
- package/dist/formatting/Style.d.ts.map +1 -1
- package/dist/formatting/Style.js +309 -112
- package/dist/formatting/Style.js.map +1 -1
- package/dist/formatting/StylesManager.d.ts +2 -2
- package/dist/formatting/StylesManager.d.ts.map +1 -1
- package/dist/formatting/StylesManager.js +52 -52
- package/dist/formatting/StylesManager.js.map +1 -1
- package/dist/helpers/CleanupHelper.d.ts +1 -1
- package/dist/helpers/CleanupHelper.d.ts.map +1 -1
- package/dist/helpers/CleanupHelper.js +15 -15
- package/dist/helpers/CleanupHelper.js.map +1 -1
- package/dist/index.d.ts +81 -90
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +286 -317
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +16 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +42 -0
- package/dist/internal.js.map +1 -0
- package/dist/managers/DrawingManager.d.ts +3 -3
- package/dist/managers/DrawingManager.d.ts.map +1 -1
- package/dist/managers/DrawingManager.js +12 -12
- package/dist/managers/DrawingManager.js.map +1 -1
- package/dist/{utils → processors}/ChangelogGenerator.d.ts +2 -2
- package/dist/processors/ChangelogGenerator.d.ts.map +1 -0
- package/dist/{utils → processors}/ChangelogGenerator.js +2 -2
- package/dist/processors/ChangelogGenerator.js.map +1 -0
- package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -0
- package/dist/{utils → processors}/CompatibilityUpgrader.js +10 -10
- package/dist/processors/CompatibilityUpgrader.js.map +1 -0
- package/dist/{utils → processors}/InMemoryRevisionAcceptor.d.ts +3 -3
- package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -0
- package/dist/{utils → processors}/InMemoryRevisionAcceptor.js +84 -27
- package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -0
- package/dist/{utils → processors}/MoveOperationHelper.d.ts +4 -4
- package/dist/processors/MoveOperationHelper.d.ts.map +1 -0
- package/dist/{utils → processors}/MoveOperationHelper.js +10 -10
- package/dist/processors/MoveOperationHelper.js.map +1 -0
- package/dist/{utils → processors}/RevisionAwareProcessor.d.ts +3 -3
- package/dist/processors/RevisionAwareProcessor.d.ts.map +1 -0
- package/dist/{utils → processors}/RevisionAwareProcessor.js +2 -2
- package/dist/processors/RevisionAwareProcessor.js.map +1 -0
- package/dist/{utils → processors}/RevisionWalker.d.ts +2 -1
- package/dist/processors/RevisionWalker.d.ts.map +1 -0
- package/dist/{utils → processors}/RevisionWalker.js +28 -0
- package/dist/processors/RevisionWalker.js.map +1 -0
- package/dist/{utils → processors}/SelectiveRevisionAcceptor.d.ts +4 -3
- package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -0
- package/dist/processors/SelectiveRevisionAcceptor.js +402 -0
- package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -0
- package/dist/processors/ShadingResolver.d.ts +6 -0
- package/dist/processors/ShadingResolver.d.ts.map +1 -0
- package/dist/{utils → processors}/ShadingResolver.js +2 -2
- package/dist/processors/ShadingResolver.js.map +1 -0
- package/dist/{utils → processors}/acceptRevisions.d.ts +1 -1
- package/dist/processors/acceptRevisions.d.ts.map +1 -0
- package/dist/{utils → processors}/acceptRevisions.js +24 -4
- package/dist/processors/acceptRevisions.js.map +1 -0
- package/dist/{utils → processors}/cnfStyleDecoder.d.ts +1 -1
- package/dist/processors/cnfStyleDecoder.d.ts.map +1 -0
- package/dist/processors/cnfStyleDecoder.js.map +1 -0
- package/dist/processors/stripTrackedChanges.d.ts +3 -0
- package/dist/processors/stripTrackedChanges.d.ts.map +1 -0
- package/dist/{utils → processors}/stripTrackedChanges.js +16 -6
- package/dist/processors/stripTrackedChanges.js.map +1 -0
- package/dist/tracking/DocumentTrackingContext.d.ts +4 -4
- package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
- package/dist/tracking/DocumentTrackingContext.js +38 -43
- package/dist/tracking/DocumentTrackingContext.js.map +1 -1
- package/dist/tracking/TrackingContext.d.ts +8 -8
- package/dist/tracking/TrackingContext.d.ts.map +1 -1
- package/dist/tracking/TrackingContext.js.map +1 -1
- package/dist/types/document-types.d.ts +28 -0
- package/dist/types/document-types.d.ts.map +1 -0
- package/dist/types/document-types.js +3 -0
- package/dist/types/document-types.js.map +1 -0
- package/dist/types/formatting.d.ts +4 -4
- package/dist/types/formatting.d.ts.map +1 -1
- package/dist/types/formatting.js.map +1 -1
- package/dist/types/settings-types.d.ts +6 -0
- package/dist/types/settings-types.d.ts.map +1 -1
- package/dist/types/settings-types.js.map +1 -1
- package/dist/utils/KeyedRegistry.d.ts +13 -0
- package/dist/utils/KeyedRegistry.d.ts.map +1 -0
- package/dist/utils/KeyedRegistry.js +36 -0
- package/dist/utils/KeyedRegistry.js.map +1 -0
- package/dist/utils/corruptionDetection.d.ts +1 -1
- package/dist/utils/corruptionDetection.d.ts.map +1 -1
- package/dist/utils/corruptionDetection.js +4 -4
- package/dist/utils/corruptionDetection.js.map +1 -1
- package/dist/utils/deepEqual.d.ts +2 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +50 -0
- package/dist/utils/deepEqual.js.map +1 -0
- 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.map +1 -1
- package/dist/utils/parsingHelpers.d.ts +1 -1
- package/dist/utils/parsingHelpers.d.ts.map +1 -1
- package/dist/utils/parsingHelpers.js +2 -2
- package/dist/utils/parsingHelpers.js.map +1 -1
- package/dist/utils/validation.js +7 -7
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/xmlSanitization.js +2 -2
- package/dist/utils/xmlSanitization.js.map +1 -1
- package/dist/validation/RevisionAutoFixer.d.ts +4 -4
- package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
- package/dist/validation/RevisionAutoFixer.js +11 -11
- package/dist/validation/RevisionAutoFixer.js.map +1 -1
- package/dist/validation/RevisionValidator.d.ts +5 -4
- package/dist/validation/RevisionValidator.d.ts.map +1 -1
- package/dist/validation/RevisionValidator.js +29 -30
- package/dist/validation/RevisionValidator.js.map +1 -1
- package/dist/validation/ValidationRuleRegistry.d.ts +27 -0
- package/dist/validation/ValidationRuleRegistry.d.ts.map +1 -0
- package/dist/validation/ValidationRuleRegistry.js +43 -0
- package/dist/validation/ValidationRuleRegistry.js.map +1 -0
- package/dist/validation/index.d.ts +3 -3
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +10 -10
- package/dist/validation/index.js.map +1 -1
- package/dist/xml/XMLBuilder.d.ts +6 -1
- package/dist/xml/XMLBuilder.d.ts.map +1 -1
- package/dist/xml/XMLBuilder.js +11 -6
- package/dist/xml/XMLBuilder.js.map +1 -1
- package/dist/xml/XMLParser.js +6 -6
- package/dist/xml/XMLParser.js.map +1 -1
- package/dist/zip/ZipHandler.d.ts +1 -1
- package/dist/zip/ZipHandler.d.ts.map +1 -1
- package/dist/zip/ZipHandler.js +8 -8
- package/dist/zip/ZipHandler.js.map +1 -1
- package/dist/zip/ZipReader.d.ts +1 -1
- package/dist/zip/ZipReader.d.ts.map +1 -1
- package/dist/zip/ZipReader.js +14 -14
- package/dist/zip/ZipReader.js.map +1 -1
- package/dist/zip/ZipWriter.d.ts +1 -1
- package/dist/zip/ZipWriter.d.ts.map +1 -1
- package/dist/zip/ZipWriter.js +10 -10
- package/dist/zip/ZipWriter.js.map +1 -1
- package/package.json +20 -4
- package/src/constants/legacyCompatFlags.ts +1 -1
- package/src/core/Document.ts +461 -167
- package/src/core/DocumentContent.ts +14 -11
- package/src/core/DocumentEvents.ts +90 -0
- package/src/core/DocumentGenerator.ts +49 -22
- package/src/core/DocumentParser.ts +2180 -617
- package/src/core/DocumentValidator.ts +7 -7
- package/src/core/ElementRegistry.ts +69 -0
- package/src/core/Relationship.ts +1 -1
- package/src/core/RelationshipManager.ts +4 -4
- package/src/elements/AlternateContent.ts +1 -1
- package/src/elements/Bookmark.ts +52 -4
- package/src/elements/BookmarkManager.ts +2 -2
- package/src/elements/Comment.ts +3 -3
- package/src/elements/CommentManager.ts +4 -4
- package/src/elements/CommonTypes.ts +45 -7
- package/src/elements/CustomXml.ts +1 -1
- package/src/elements/Endnote.ts +2 -2
- package/src/elements/EndnoteManager.ts +3 -3
- package/src/elements/Field.ts +44 -10
- package/src/elements/FieldHelpers.ts +2 -2
- package/src/elements/Footer.ts +4 -4
- package/src/elements/Footnote.ts +2 -2
- package/src/elements/FootnoteManager.ts +3 -3
- package/src/elements/Header.ts +4 -4
- package/src/elements/HeaderFooterManager.ts +2 -2
- package/src/elements/Hyperlink.ts +16 -12
- package/src/elements/Image.ts +3 -3
- package/src/elements/ImageManager.ts +2 -2
- package/src/elements/ImageRun.ts +3 -3
- package/src/elements/MathElement.ts +1 -1
- package/src/elements/Paragraph.ts +221 -88
- package/src/elements/PreservedElement.ts +1 -1
- package/src/elements/PropertyChangeTypes.ts +2 -2
- package/src/elements/RangeMarker.ts +153 -12
- package/src/elements/RegisteredBodyElement.ts +52 -0
- package/src/elements/Revision.ts +14 -14
- package/src/elements/RevisionContent.ts +3 -3
- package/src/elements/RevisionManager.ts +3 -3
- package/src/elements/Run.ts +221 -94
- package/src/elements/Section.ts +136 -69
- package/src/elements/Shape.ts +4 -4
- package/src/elements/StructuredDocumentTag.ts +3 -3
- package/src/elements/Table.ts +91 -27
- package/src/elements/TableCell.ts +62 -34
- package/src/elements/TableGridChange.ts +1 -1
- package/src/elements/TableOfContents.ts +1 -1
- package/src/elements/TableOfContentsElement.ts +2 -2
- package/src/elements/TableRow.ts +192 -48
- package/src/elements/TextBox.ts +5 -5
- package/src/formatting/AbstractNumbering.ts +3 -3
- package/src/formatting/NumberingInstance.ts +2 -2
- package/src/formatting/NumberingLevel.ts +201 -10
- package/src/formatting/NumberingManager.ts +5 -5
- package/src/formatting/Style.ts +382 -86
- package/src/formatting/StylesManager.ts +4 -4
- package/src/helpers/CleanupHelper.ts +6 -6
- package/src/index.ts +118 -127
- package/src/internal.ts +79 -0
- package/src/managers/DrawingManager.ts +3 -3
- package/src/{utils → processors}/ChangelogGenerator.ts +3 -3
- package/src/{utils → processors}/CompatibilityUpgrader.ts +2 -2
- package/src/{utils → processors}/InMemoryRevisionAcceptor.ts +100 -12
- package/src/{utils → processors}/MoveOperationHelper.ts +5 -5
- package/src/{utils → processors}/RevisionAwareProcessor.ts +3 -3
- package/src/{utils → processors}/RevisionWalker.ts +42 -1
- package/src/{utils → processors}/SelectiveRevisionAcceptor.ts +98 -39
- package/src/{utils → processors}/ShadingResolver.ts +5 -5
- package/src/{utils → processors}/acceptRevisions.ts +77 -9
- package/src/{utils → processors}/cnfStyleDecoder.ts +1 -1
- package/src/{utils → processors}/stripTrackedChanges.ts +35 -10
- package/src/tracking/DocumentTrackingContext.ts +12 -14
- package/src/tracking/TrackingContext.ts +8 -8
- package/src/types/document-types.ts +53 -0
- package/src/types/formatting.ts +4 -4
- package/src/types/settings-types.ts +32 -0
- package/src/utils/KeyedRegistry.ts +41 -0
- package/src/utils/corruptionDetection.ts +2 -2
- package/src/utils/deepEqual.ts +58 -0
- package/src/utils/list-detection.ts +2 -2
- package/src/utils/parsingHelpers.ts +11 -3
- package/src/utils/validation.ts +3 -3
- package/src/utils/xmlSanitization.ts +1 -1
- package/src/validation/RevisionAutoFixer.ts +5 -5
- package/src/validation/RevisionValidator.ts +39 -28
- package/src/validation/ValidationRuleRegistry.ts +86 -0
- package/src/validation/index.ts +3 -3
- package/src/xml/XMLBuilder.ts +13 -3
- package/src/xml/XMLParser.ts +2 -2
- package/src/zip/ZipHandler.ts +4 -4
- package/src/zip/ZipReader.ts +3 -3
- package/src/zip/ZipWriter.ts +3 -3
- package/dist/utils/ChangelogGenerator.d.ts.map +0 -1
- package/dist/utils/ChangelogGenerator.js.map +0 -1
- package/dist/utils/CompatibilityUpgrader.d.ts.map +0 -1
- package/dist/utils/CompatibilityUpgrader.js.map +0 -1
- package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +0 -1
- package/dist/utils/InMemoryRevisionAcceptor.js.map +0 -1
- package/dist/utils/MoveOperationHelper.d.ts.map +0 -1
- package/dist/utils/MoveOperationHelper.js.map +0 -1
- package/dist/utils/RevisionAwareProcessor.d.ts.map +0 -1
- package/dist/utils/RevisionAwareProcessor.js.map +0 -1
- package/dist/utils/RevisionWalker.d.ts.map +0 -1
- package/dist/utils/RevisionWalker.js.map +0 -1
- package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +0 -1
- package/dist/utils/SelectiveRevisionAcceptor.js.map +0 -1
- package/dist/utils/ShadingResolver.d.ts +0 -6
- package/dist/utils/ShadingResolver.d.ts.map +0 -1
- package/dist/utils/ShadingResolver.js.map +0 -1
- package/dist/utils/acceptRevisions.d.ts.map +0 -1
- package/dist/utils/acceptRevisions.js.map +0 -1
- package/dist/utils/cnfStyleDecoder.d.ts.map +0 -1
- package/dist/utils/cnfStyleDecoder.js.map +0 -1
- package/dist/utils/stripTrackedChanges.d.ts +0 -3
- package/dist/utils/stripTrackedChanges.d.ts.map +0 -1
- package/dist/utils/stripTrackedChanges.js.map +0 -1
- package/src/__tests__/helper-methods.test.ts +0 -512
- package/src/constants/CLAUDE.md +0 -28
- package/src/core/CLAUDE.md +0 -113
- package/src/elements/CLAUDE.md +0 -142
- package/src/formatting/CLAUDE.md +0 -78
- package/src/managers/CLAUDE.md +0 -47
- package/src/tracking/CLAUDE.md +0 -30
- package/src/types/CLAUDE.md +0 -39
- package/src/utils/CLAUDE.md +0 -168
- package/src/validation/CLAUDE.md +0 -40
- package/src/xml/CLAUDE.md +0 -65
- package/src/zip/CLAUDE.md +0 -55
- /package/dist/{utils → processors}/CompatibilityUpgrader.d.ts +0 -0
- /package/dist/{utils → processors}/cnfStyleDecoder.js +0 -0
|
@@ -3,24 +3,26 @@
|
|
|
3
3
|
* Extracts content from ZIP archives and converts XML to structured data
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { AlternateContent } from '../elements/AlternateContent';
|
|
7
|
-
import { Bookmark } from '../elements/Bookmark';
|
|
8
|
-
import { Endnote, EndnoteType } from '../elements/Endnote';
|
|
9
|
-
import { Footnote, FootnoteType } from '../elements/Footnote';
|
|
10
|
-
import { BookmarkManager } from '../elements/BookmarkManager';
|
|
11
|
-
import { Comment } from '../elements/Comment';
|
|
12
|
-
import { CustomXmlBlock } from '../elements/CustomXml';
|
|
13
|
-
import { PreservedElement } from '../elements/PreservedElement';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
6
|
+
import { AlternateContent } from '../elements/AlternateContent.js';
|
|
7
|
+
import { Bookmark } from '../elements/Bookmark.js';
|
|
8
|
+
import { Endnote, EndnoteType } from '../elements/Endnote.js';
|
|
9
|
+
import { Footnote, FootnoteType } from '../elements/Footnote.js';
|
|
10
|
+
import { BookmarkManager } from '../elements/BookmarkManager.js';
|
|
11
|
+
import { Comment } from '../elements/Comment.js';
|
|
12
|
+
import { CustomXmlBlock } from '../elements/CustomXml.js';
|
|
13
|
+
import { PreservedElement } from '../elements/PreservedElement.js';
|
|
14
|
+
import { RegisteredBodyElement } from '../elements/RegisteredBodyElement.js';
|
|
15
|
+
import { ElementRegistry } from './ElementRegistry.js';
|
|
16
|
+
import { MathParagraph } from '../elements/MathElement.js';
|
|
17
|
+
import { ComplexField, Field } from '../elements/Field.js';
|
|
18
|
+
import { isHyperlinkInstruction, parseHyperlinkInstruction } from '../elements/FieldHelpers.js';
|
|
19
|
+
import { Footer } from '../elements/Footer.js';
|
|
20
|
+
import { Header } from '../elements/Header.js';
|
|
21
|
+
import { Hyperlink } from '../elements/Hyperlink.js';
|
|
22
|
+
import { ImageManager } from '../elements/ImageManager.js';
|
|
23
|
+
import { ImageRun } from '../elements/ImageRun.js';
|
|
24
|
+
import { Paragraph, ParagraphFormatting, ParagraphContent } from '../elements/Paragraph.js';
|
|
25
|
+
import { Revision } from '../elements/Revision.js';
|
|
24
26
|
import {
|
|
25
27
|
BreakType,
|
|
26
28
|
FormFieldCheckBox,
|
|
@@ -30,35 +32,40 @@ import {
|
|
|
30
32
|
Run,
|
|
31
33
|
RunContent,
|
|
32
34
|
RunFormatting,
|
|
33
|
-
} from '../elements/Run';
|
|
34
|
-
import { Section, SectionProperties, SectionType } from '../elements/Section';
|
|
35
|
-
import { StructuredDocumentTag } from '../elements/StructuredDocumentTag';
|
|
36
|
-
import { Table, TableBorder } from '../elements/Table';
|
|
37
|
-
import { TableCell } from '../elements/TableCell';
|
|
38
|
-
import { TableOfContents } from '../elements/TableOfContents';
|
|
39
|
-
import { TableOfContentsElement } from '../elements/TableOfContentsElement';
|
|
40
|
-
import { TableGridChange } from '../elements/TableGridChange';
|
|
41
|
-
import { TableRow } from '../elements/TableRow';
|
|
42
|
-
import { AbstractNumbering } from '../formatting/AbstractNumbering';
|
|
43
|
-
import { NumberingInstance } from '../formatting/NumberingInstance';
|
|
44
|
-
import { Style, StyleProperties, StyleType } from '../formatting/Style';
|
|
45
|
-
import { logParagraphContent, logParsing, logTextDirection } from '../utils/diagnostics';
|
|
46
|
-
import { getGlobalLogger, createScopedLogger, ILogger, defaultLogger } from '../utils/logger';
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
} from '../elements/Run.js';
|
|
36
|
+
import { Section, SectionProperties, SectionType } from '../elements/Section.js';
|
|
37
|
+
import { StructuredDocumentTag } from '../elements/StructuredDocumentTag.js';
|
|
38
|
+
import { Table, TableBorder } from '../elements/Table.js';
|
|
39
|
+
import { TableCell } from '../elements/TableCell.js';
|
|
40
|
+
import { TableOfContents } from '../elements/TableOfContents.js';
|
|
41
|
+
import { TableOfContentsElement } from '../elements/TableOfContentsElement.js';
|
|
42
|
+
import { TableGridChange } from '../elements/TableGridChange.js';
|
|
43
|
+
import { TableRow } from '../elements/TableRow.js';
|
|
44
|
+
import { AbstractNumbering } from '../formatting/AbstractNumbering.js';
|
|
45
|
+
import { NumberingInstance } from '../formatting/NumberingInstance.js';
|
|
46
|
+
import { Style, StyleProperties, StyleType } from '../formatting/Style.js';
|
|
47
|
+
import { logParagraphContent, logParsing, logTextDirection } from '../utils/diagnostics.js';
|
|
48
|
+
import { getGlobalLogger, createScopedLogger, ILogger, defaultLogger } from '../utils/logger.js';
|
|
49
|
+
import {
|
|
50
|
+
safeParseInt,
|
|
51
|
+
isExplicitlySet,
|
|
52
|
+
parseOoxmlBoolean,
|
|
53
|
+
parseOnOffAttribute,
|
|
54
|
+
} from '../utils/parsingHelpers.js';
|
|
55
|
+
import { halfPointsToPoints } from '../utils/units.js';
|
|
56
|
+
import type { ShadingConfig } from '../elements/CommonTypes.js';
|
|
50
57
|
|
|
51
58
|
// Create scoped logger for DocumentParser operations
|
|
52
59
|
function getLogger(): ILogger {
|
|
53
60
|
return createScopedLogger(getGlobalLogger(), 'DocumentParser');
|
|
54
61
|
}
|
|
55
|
-
import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
|
|
56
|
-
import { XMLParser } from '../xml/XMLParser';
|
|
57
|
-
import { ZipHandler } from '../zip/ZipHandler';
|
|
58
|
-
import { DOCX_PATHS } from '../zip/types';
|
|
59
|
-
import { DocumentProperties } from '
|
|
60
|
-
import { BodyElement } from './DocumentContent';
|
|
61
|
-
import { RelationshipManager } from './RelationshipManager';
|
|
62
|
+
import { XMLBuilder, XMLElement } from '../xml/XMLBuilder.js';
|
|
63
|
+
import { XMLParser } from '../xml/XMLParser.js';
|
|
64
|
+
import { ZipHandler } from '../zip/ZipHandler.js';
|
|
65
|
+
import { DOCX_PATHS } from '../zip/types.js';
|
|
66
|
+
import type { DocumentProperties } from '../types/document-types.js';
|
|
67
|
+
import { BodyElement } from './DocumentContent.js';
|
|
68
|
+
import { RelationshipManager } from './RelationshipManager.js';
|
|
62
69
|
|
|
63
70
|
/**
|
|
64
71
|
* Parse error tracking
|
|
@@ -242,7 +249,7 @@ export class DocumentParser {
|
|
|
242
249
|
const nextCXml = this.findNextTopLevelTag(bodyContent, 'w:customXml', pos);
|
|
243
250
|
const nextAltChunk = this.findNextTopLevelTag(bodyContent, 'w:altChunk', pos);
|
|
244
251
|
|
|
245
|
-
const candidates = [];
|
|
252
|
+
const candidates: { type: string; pos: number; registeredTag?: string }[] = [];
|
|
246
253
|
if (nextP !== -1) candidates.push({ type: 'p', pos: nextP });
|
|
247
254
|
if (nextTbl !== -1) candidates.push({ type: 'tbl', pos: nextTbl });
|
|
248
255
|
if (nextSdt !== -1) candidates.push({ type: 'sdt', pos: nextSdt });
|
|
@@ -251,6 +258,15 @@ export class DocumentParser {
|
|
|
251
258
|
if (nextCXml !== -1) candidates.push({ type: 'customXml', pos: nextCXml });
|
|
252
259
|
if (nextAltChunk !== -1) candidates.push({ type: 'altChunk', pos: nextAltChunk });
|
|
253
260
|
|
|
261
|
+
// ElementRegistry plugin tags — consumer-registered handlers for
|
|
262
|
+
// qualified-name body elements outside the framework's native set.
|
|
263
|
+
// We scan each registered tag at every step so the position-based
|
|
264
|
+
// dispatch picks up registered elements interleaved with native ones.
|
|
265
|
+
for (const tag of ElementRegistry.registeredTags()) {
|
|
266
|
+
const p = this.findNextTopLevelTag(bodyContent, tag, pos);
|
|
267
|
+
if (p !== -1) candidates.push({ type: 'registered', pos: p, registeredTag: tag });
|
|
268
|
+
}
|
|
269
|
+
|
|
254
270
|
if (candidates.length === 0) break;
|
|
255
271
|
|
|
256
272
|
candidates.sort((a, b) => a.pos - b.pos);
|
|
@@ -399,6 +415,29 @@ export class DocumentParser {
|
|
|
399
415
|
} else {
|
|
400
416
|
pos = next.pos + 1;
|
|
401
417
|
}
|
|
418
|
+
} else if (next.type === 'registered' && next.registeredTag) {
|
|
419
|
+
// Consumer-registered element via ElementRegistry. Hand the raw XML
|
|
420
|
+
// to the handler's parse(); on save the model is round-tripped via
|
|
421
|
+
// handler.serialize(). A throwing parse degrades gracefully to a
|
|
422
|
+
// PreservedElement so a buggy custom parser cannot fail the load.
|
|
423
|
+
const tag = next.registeredTag;
|
|
424
|
+
const elementXml = this.extractSingleElement(bodyContent, tag, next.pos);
|
|
425
|
+
if (elementXml) {
|
|
426
|
+
const handler = ElementRegistry.get(tag);
|
|
427
|
+
if (handler) {
|
|
428
|
+
try {
|
|
429
|
+
const model = handler.parse(elementXml);
|
|
430
|
+
bodyElements.push(new RegisteredBodyElement(tag, model, handler, elementXml));
|
|
431
|
+
} catch {
|
|
432
|
+
bodyElements.push(new PreservedElement(elementXml, tag, 'block'));
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
bodyElements.push(new PreservedElement(elementXml, tag, 'block'));
|
|
436
|
+
}
|
|
437
|
+
pos = next.pos + elementXml.length;
|
|
438
|
+
} else {
|
|
439
|
+
pos = next.pos + 1;
|
|
440
|
+
}
|
|
402
441
|
}
|
|
403
442
|
|
|
404
443
|
// Attach any pending body-level bookmarkStarts to the just-parsed element
|
|
@@ -507,10 +546,18 @@ export class DocumentParser {
|
|
|
507
546
|
if (idAttr) {
|
|
508
547
|
const id = parseInt(idAttr, 10);
|
|
509
548
|
if (!isNaN(id)) {
|
|
549
|
+
// CT_MarkupRange §17.13.5 — preserve w:displacedByCustomXml.
|
|
550
|
+
const displacedAttr = XMLParser.extractAttribute(
|
|
551
|
+
bookmarkEndXml,
|
|
552
|
+
'w:displacedByCustomXml'
|
|
553
|
+
);
|
|
554
|
+
const displacedByCustomXml =
|
|
555
|
+
displacedAttr === 'next' || displacedAttr === 'prev' ? displacedAttr : undefined;
|
|
510
556
|
const bookmark = new Bookmark({
|
|
511
557
|
name: `_end_${id}`,
|
|
512
558
|
id: id,
|
|
513
559
|
skipNormalization: true,
|
|
560
|
+
displacedByCustomXml,
|
|
514
561
|
});
|
|
515
562
|
bookmarks.push(bookmark);
|
|
516
563
|
}
|
|
@@ -905,14 +952,27 @@ export class DocumentParser {
|
|
|
905
952
|
}
|
|
906
953
|
}
|
|
907
954
|
|
|
908
|
-
// Parse w14:paraId and w14:textId
|
|
909
|
-
|
|
955
|
+
// Parse w14:paraId and w14:textId (Word 2010+ paragraph identifiers
|
|
956
|
+
// per MC-DOCX §2.6.19, ST_LongHexNumber — 8-char hex string). These
|
|
957
|
+
// are XML *attributes* on w:p, so XMLParser stores them under the
|
|
958
|
+
// @_-prefixed keys. The previous lookup (`pElement['w14:paraId']`)
|
|
959
|
+
// accessed an element-shaped key that never exists, silently
|
|
960
|
+
// dropping both IDs on every load → save cycle. XMLParser's
|
|
961
|
+
// numeric coercion of purely-digit hex strings (e.g. "00000001" →
|
|
962
|
+
// 1) means we normalise back to the zero-padded 8-char form so
|
|
963
|
+
// validators accept the output.
|
|
964
|
+
const normaliseHexId = (raw: unknown): string | undefined => {
|
|
965
|
+
if (raw === undefined || raw === null) return undefined;
|
|
966
|
+
const asStr = typeof raw === 'number' ? raw.toString(16) : String(raw);
|
|
967
|
+
return asStr.toUpperCase().padStart(8, '0');
|
|
968
|
+
};
|
|
969
|
+
const paraId = normaliseHexId(pElement['@_w14:paraId']);
|
|
910
970
|
if (paraId) {
|
|
911
|
-
paragraph.formatting.paraId = paraId
|
|
971
|
+
paragraph.formatting.paraId = paraId;
|
|
912
972
|
}
|
|
913
|
-
const textId = pElement['
|
|
973
|
+
const textId = normaliseHexId(pElement['@_w14:textId']);
|
|
914
974
|
if (textId) {
|
|
915
|
-
paragraph.formatting.textId = textId
|
|
975
|
+
paragraph.formatting.textId = textId;
|
|
916
976
|
}
|
|
917
977
|
|
|
918
978
|
// CRITICAL FIX: Preserve document order of paragraph children (runs, hyperlinks, fields)
|
|
@@ -1533,7 +1593,7 @@ export class DocumentParser {
|
|
|
1533
1593
|
} = { revision: null, bookmarkStarts: [], bookmarkEnds: [] };
|
|
1534
1594
|
try {
|
|
1535
1595
|
// Map XML tag to RevisionType
|
|
1536
|
-
let revisionType: import('../elements/Revision').RevisionType;
|
|
1596
|
+
let revisionType: import('../elements/Revision.js').RevisionType;
|
|
1537
1597
|
switch (tagName) {
|
|
1538
1598
|
case 'w:ins':
|
|
1539
1599
|
revisionType = 'insert';
|
|
@@ -1581,7 +1641,7 @@ export class DocumentParser {
|
|
|
1581
1641
|
const runXmls = XMLParser.extractElements(xmlWithoutHyperlinks, 'w:r');
|
|
1582
1642
|
|
|
1583
1643
|
// Use RevisionContent to hold both Run and Hyperlink objects
|
|
1584
|
-
const content: import('../elements/RevisionContent').RevisionContent[] = [];
|
|
1644
|
+
const content: import('../elements/RevisionContent.js').RevisionContent[] = [];
|
|
1585
1645
|
|
|
1586
1646
|
// Parse standalone runs (not inside hyperlinks)
|
|
1587
1647
|
for (const runXml of runXmls) {
|
|
@@ -1727,8 +1787,8 @@ export class DocumentParser {
|
|
|
1727
1787
|
const id = parseInt(idAttr, 10);
|
|
1728
1788
|
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
1729
1789
|
const parentId = parentIdAttr ? parseInt(parentIdAttr, 10) : undefined;
|
|
1730
|
-
// Per ECMA-376, w:done
|
|
1731
|
-
const done = doneAttr
|
|
1790
|
+
// Per ECMA-376 §17.17.4, w:done is ST_OnOff — accept 1/0/true/false/on/off
|
|
1791
|
+
const done = parseOnOffAttribute(doneAttr);
|
|
1732
1792
|
|
|
1733
1793
|
// Parse content (runs from paragraphs within the comment)
|
|
1734
1794
|
const runs: Run[] = [];
|
|
@@ -1921,12 +1981,20 @@ export class DocumentParser {
|
|
|
1921
1981
|
// Parse optional column range for table bookmarks (ECMA-376 §17.16.5)
|
|
1922
1982
|
const colFirstAttr = XMLParser.extractAttribute(bookmarkXml, 'w:colFirst');
|
|
1923
1983
|
const colLastAttr = XMLParser.extractAttribute(bookmarkXml, 'w:colLast');
|
|
1984
|
+
// Parse optional w:displacedByCustomXml per CT_MarkupRange (§17.13.5).
|
|
1985
|
+
// Without this the attribute was dropped on load, so any Word document
|
|
1986
|
+
// with custom-XML-displaced bookmarks lost the disambiguator even
|
|
1987
|
+
// though the model now supports round-tripping it.
|
|
1988
|
+
const displacedAttr = XMLParser.extractAttribute(bookmarkXml, 'w:displacedByCustomXml');
|
|
1989
|
+
const displacedByCustomXml =
|
|
1990
|
+
displacedAttr === 'next' || displacedAttr === 'prev' ? displacedAttr : undefined;
|
|
1924
1991
|
const bookmark = new Bookmark({
|
|
1925
1992
|
name: nameAttr,
|
|
1926
1993
|
id: id,
|
|
1927
1994
|
skipNormalization: true,
|
|
1928
1995
|
colFirst: colFirstAttr ? parseInt(colFirstAttr, 10) : undefined,
|
|
1929
1996
|
colLast: colLastAttr ? parseInt(colLastAttr, 10) : undefined,
|
|
1997
|
+
displacedByCustomXml,
|
|
1930
1998
|
});
|
|
1931
1999
|
|
|
1932
2000
|
// Register with BookmarkManager to enable hasBookmark() checks
|
|
@@ -1969,12 +2037,22 @@ export class DocumentParser {
|
|
|
1969
2037
|
|
|
1970
2038
|
const id = parseInt(idAttr, 10);
|
|
1971
2039
|
|
|
2040
|
+
// CT_MarkupRange (§17.13.5) also permits w:displacedByCustomXml on
|
|
2041
|
+
// the end marker. Previously dropped on load, so a Word document
|
|
2042
|
+
// whose bookmark-end was displaced across a custom-XML node lost
|
|
2043
|
+
// the disambiguator even though the Bookmark model already emits
|
|
2044
|
+
// it from toEndXML().
|
|
2045
|
+
const displacedAttr = XMLParser.extractAttribute(bookmarkXml, 'w:displacedByCustomXml');
|
|
2046
|
+
const displacedByCustomXml =
|
|
2047
|
+
displacedAttr === 'next' || displacedAttr === 'prev' ? displacedAttr : undefined;
|
|
2048
|
+
|
|
1972
2049
|
// Create a placeholder bookmark for the end marker
|
|
1973
2050
|
// The name doesn't matter for bookmarkEnd as it only uses the ID
|
|
1974
2051
|
const bookmark = new Bookmark({
|
|
1975
2052
|
name: `_end_${id}`,
|
|
1976
2053
|
id: id,
|
|
1977
2054
|
skipNormalization: true,
|
|
2055
|
+
displacedByCustomXml,
|
|
1978
2056
|
});
|
|
1979
2057
|
|
|
1980
2058
|
return bookmark;
|
|
@@ -1996,12 +2074,23 @@ export class DocumentParser {
|
|
|
1996
2074
|
try {
|
|
1997
2075
|
const paragraph = new Paragraph();
|
|
1998
2076
|
|
|
1999
|
-
// Parse w14:paraId and w14:textId attributes from paragraph element
|
|
2000
|
-
|
|
2077
|
+
// Parse w14:paraId and w14:textId attributes from paragraph element
|
|
2078
|
+
// (Word 2010+, ST_LongHexNumber 8-char hex). XMLParser keys
|
|
2079
|
+
// attributes under the @_ prefix and may numeric-coerce purely-
|
|
2080
|
+
// digit hex strings like "00000001" to the number 1 — normalise
|
|
2081
|
+
// back to 8-char uppercase hex so the output passes strict
|
|
2082
|
+
// validation. The prior code used the un-prefixed element-shaped
|
|
2083
|
+
// keys and always saw `undefined`.
|
|
2084
|
+
const normaliseHexId = (raw: unknown): string | undefined => {
|
|
2085
|
+
if (raw === undefined || raw === null) return undefined;
|
|
2086
|
+
const asStr = typeof raw === 'number' ? raw.toString(16) : String(raw);
|
|
2087
|
+
return asStr.toUpperCase().padStart(8, '0');
|
|
2088
|
+
};
|
|
2089
|
+
const paraId = normaliseHexId(paraObj['@_w14:paraId']);
|
|
2001
2090
|
if (paraId) {
|
|
2002
2091
|
paragraph.formatting.paraId = paraId;
|
|
2003
2092
|
}
|
|
2004
|
-
const textId = paraObj['
|
|
2093
|
+
const textId = normaliseHexId(paraObj['@_w14:textId']);
|
|
2005
2094
|
if (textId) {
|
|
2006
2095
|
paragraph.formatting.textId = textId;
|
|
2007
2096
|
}
|
|
@@ -2171,6 +2260,22 @@ export class DocumentParser {
|
|
|
2171
2260
|
// Extract the formatting and set it as paragraph mark properties
|
|
2172
2261
|
paragraph.setParagraphMarkFormatting(tempRun.getFormatting());
|
|
2173
2262
|
|
|
2263
|
+
// Transfer w:rPrChange (CT_ParaRPrChange, §17.3.1.30) from the
|
|
2264
|
+
// temp run onto the paragraph's formatting. Without this the
|
|
2265
|
+
// paragraph-mark rPrChange is silently dropped because
|
|
2266
|
+
// `tempRun.getFormatting()` exposes RunFormatting fields only —
|
|
2267
|
+
// `propertyChangeRevision` is a separate field on Run that was
|
|
2268
|
+
// previously discarded along with the temp run.
|
|
2269
|
+
const rPrChangeRev = tempRun.getPropertyChangeRevision();
|
|
2270
|
+
if (rPrChangeRev) {
|
|
2271
|
+
paragraph.formatting.paragraphMarkRunPropertiesChange = {
|
|
2272
|
+
id: rPrChangeRev.id,
|
|
2273
|
+
author: rPrChangeRev.author,
|
|
2274
|
+
date: rPrChangeRev.date,
|
|
2275
|
+
previousProperties: rPrChangeRev.previousProperties,
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2174
2279
|
// Parse paragraph mark deletion tracking (w:del in w:pPr/w:rPr)
|
|
2175
2280
|
// Per ECMA-376 Part 1 §17.13.5.14 - indicates the paragraph mark was deleted
|
|
2176
2281
|
if (rPrObj['w:del']) {
|
|
@@ -2210,9 +2315,14 @@ export class DocumentParser {
|
|
|
2210
2315
|
paragraph.setAlignment(pPrObj['w:jc']['@_w:val']);
|
|
2211
2316
|
}
|
|
2212
2317
|
|
|
2213
|
-
// Style
|
|
2214
|
-
|
|
2215
|
-
|
|
2318
|
+
// Style (w:pStyle per ECMA-376 §17.3.1.27 — `w:val` is ST_String
|
|
2319
|
+
// referencing a style ID). Cast via String(...) so purely-numeric
|
|
2320
|
+
// style IDs that XMLParser's `parseAttributeValue: true` coerces to
|
|
2321
|
+
// JS numbers (e.g., a custom styleId of "1") survive as strings,
|
|
2322
|
+
// matching the `style?: string` field contract on
|
|
2323
|
+
// ParagraphFormatting.
|
|
2324
|
+
if (pPrObj['w:pStyle']?.['@_w:val'] !== undefined) {
|
|
2325
|
+
paragraph.setStyle(String(pPrObj['w:pStyle']['@_w:val']));
|
|
2216
2326
|
}
|
|
2217
2327
|
|
|
2218
2328
|
// Indentation
|
|
@@ -2231,6 +2341,24 @@ export class DocumentParser {
|
|
|
2231
2341
|
// Parse hanging indent per ECMA-376 Part 1 §17.3.1.17
|
|
2232
2342
|
if (isExplicitlySet(ind['@_w:hanging']))
|
|
2233
2343
|
paragraph.setHangingIndent(safeParseInt(ind['@_w:hanging']));
|
|
2344
|
+
|
|
2345
|
+
// CJK character-unit indentation attributes per ECMA-376 §17.3.1.12.
|
|
2346
|
+
// start/endChars are bidi-aware alternatives to left/rightChars; collapse
|
|
2347
|
+
// them onto the leftChars/rightChars fields the same way the twips parser
|
|
2348
|
+
// collapses w:start → left. Values are ST_DecimalNumber (hundredths of a
|
|
2349
|
+
// character unit), and 0 is a legitimate value — use isExplicitlySet so
|
|
2350
|
+
// number-0 from XMLParser.parseAttributeValue is preserved.
|
|
2351
|
+
if (!paragraph.formatting.indentation) paragraph.formatting.indentation = {};
|
|
2352
|
+
const leftCharsVal = ind['@_w:startChars'] ?? ind['@_w:leftChars'];
|
|
2353
|
+
const rightCharsVal = ind['@_w:endChars'] ?? ind['@_w:rightChars'];
|
|
2354
|
+
if (isExplicitlySet(leftCharsVal))
|
|
2355
|
+
paragraph.formatting.indentation.leftChars = safeParseInt(leftCharsVal);
|
|
2356
|
+
if (isExplicitlySet(rightCharsVal))
|
|
2357
|
+
paragraph.formatting.indentation.rightChars = safeParseInt(rightCharsVal);
|
|
2358
|
+
if (isExplicitlySet(ind['@_w:firstLineChars']))
|
|
2359
|
+
paragraph.formatting.indentation.firstLineChars = safeParseInt(ind['@_w:firstLineChars']);
|
|
2360
|
+
if (isExplicitlySet(ind['@_w:hangingChars']))
|
|
2361
|
+
paragraph.formatting.indentation.hangingChars = safeParseInt(ind['@_w:hangingChars']);
|
|
2234
2362
|
}
|
|
2235
2363
|
|
|
2236
2364
|
// Spacing (ECMA-376 §17.3.1.33 — 8 attributes)
|
|
@@ -2251,14 +2379,13 @@ export class DocumentParser {
|
|
|
2251
2379
|
paragraph.formatting.spacing.beforeLines = safeParseInt(spacing['@_w:beforeLines']);
|
|
2252
2380
|
if (isExplicitlySet(spacing['@_w:afterLines']))
|
|
2253
2381
|
paragraph.formatting.spacing.afterLines = safeParseInt(spacing['@_w:afterLines']);
|
|
2382
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
2254
2383
|
const beforeAuto = spacing['@_w:beforeAutospacing'];
|
|
2255
2384
|
if (beforeAuto !== undefined)
|
|
2256
|
-
paragraph.formatting.spacing.beforeAutospacing =
|
|
2257
|
-
String(beforeAuto) === '1' || String(beforeAuto) === 'true';
|
|
2385
|
+
paragraph.formatting.spacing.beforeAutospacing = parseOnOffAttribute(beforeAuto);
|
|
2258
2386
|
const afterAuto = spacing['@_w:afterAutospacing'];
|
|
2259
2387
|
if (afterAuto !== undefined)
|
|
2260
|
-
paragraph.formatting.spacing.afterAutospacing =
|
|
2261
|
-
String(afterAuto) === '1' || String(afterAuto) === 'true';
|
|
2388
|
+
paragraph.formatting.spacing.afterAutospacing = parseOnOffAttribute(afterAuto);
|
|
2262
2389
|
}
|
|
2263
2390
|
|
|
2264
2391
|
// Keep properties — preserve explicit val="0" to override style inheritance
|
|
@@ -2306,7 +2433,12 @@ export class DocumentParser {
|
|
|
2306
2433
|
const pBdr = pPrObj['w:pBdr'];
|
|
2307
2434
|
const borders: any = {};
|
|
2308
2435
|
|
|
2309
|
-
// Helper function to parse border definition
|
|
2436
|
+
// Helper function to parse border definition.
|
|
2437
|
+
// Covers the full CT_Border attribute set per ECMA-376 §17.18.2:
|
|
2438
|
+
// w:val, w:sz, w:color, w:space, w:themeColor, w:themeTint,
|
|
2439
|
+
// w:themeShade, w:shadow, w:frame. The last two are ST_OnOff —
|
|
2440
|
+
// route through parseOnOffAttribute so "off"/"false"/"0"/"on"
|
|
2441
|
+
// all resolve correctly even after XMLParser numeric coercion.
|
|
2310
2442
|
const parseBorder = (borderObj: any): any => {
|
|
2311
2443
|
if (!borderObj) return undefined;
|
|
2312
2444
|
const border: any = {};
|
|
@@ -2315,6 +2447,15 @@ export class DocumentParser {
|
|
|
2315
2447
|
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
2316
2448
|
if (borderObj['@_w:space'] !== undefined)
|
|
2317
2449
|
border.space = safeParseInt(borderObj['@_w:space']);
|
|
2450
|
+
if (borderObj['@_w:themeColor']) border.themeColor = String(borderObj['@_w:themeColor']);
|
|
2451
|
+
if (borderObj['@_w:themeTint']) border.themeTint = String(borderObj['@_w:themeTint']);
|
|
2452
|
+
if (borderObj['@_w:themeShade']) border.themeShade = String(borderObj['@_w:themeShade']);
|
|
2453
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
2454
|
+
border.shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
2455
|
+
}
|
|
2456
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
2457
|
+
border.frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
2458
|
+
}
|
|
2318
2459
|
return Object.keys(border).length > 0 ? border : undefined;
|
|
2319
2460
|
};
|
|
2320
2461
|
|
|
@@ -2353,7 +2494,15 @@ export class DocumentParser {
|
|
|
2353
2494
|
|
|
2354
2495
|
for (const tabObj of tabElements) {
|
|
2355
2496
|
const tab: any = {};
|
|
2356
|
-
|
|
2497
|
+
// w:pos is REQUIRED per §17.3.1.38 and is ST_SignedTwipsMeasure — 0 and
|
|
2498
|
+
// negative values are both valid. Use `!== undefined` so that XMLParser's
|
|
2499
|
+
// parseAttributeValue coercion of "0" to number 0 doesn't silently drop
|
|
2500
|
+
// tabs at the left margin (the previous `if (tabObj['@_w:pos'])` truthy
|
|
2501
|
+
// check turned pos=0 into an invisible tab-loss bug).
|
|
2502
|
+
if (tabObj['@_w:pos'] !== undefined) {
|
|
2503
|
+
const parsed = parseInt(String(tabObj['@_w:pos']), 10);
|
|
2504
|
+
if (!isNaN(parsed)) tab.position = parsed;
|
|
2505
|
+
}
|
|
2357
2506
|
if (tabObj['@_w:val']) tab.val = tabObj['@_w:val'];
|
|
2358
2507
|
if (tabObj['@_w:leader']) tab.leader = tabObj['@_w:leader'];
|
|
2359
2508
|
|
|
@@ -2369,19 +2518,10 @@ export class DocumentParser {
|
|
|
2369
2518
|
|
|
2370
2519
|
// Widow control per ECMA-376 Part 1 §17.3.1.40
|
|
2371
2520
|
if (pPrObj['w:widowControl'] !== undefined) {
|
|
2372
|
-
|
|
2373
|
-
//
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
widowControlVal === 'false' ||
|
|
2377
|
-
widowControlVal === false ||
|
|
2378
|
-
widowControlVal === 0
|
|
2379
|
-
) {
|
|
2380
|
-
paragraph.setWidowControl(false);
|
|
2381
|
-
} else {
|
|
2382
|
-
// If w:val is "1", "true", true, 1, or undefined (element present without val), default to true
|
|
2383
|
-
paragraph.setWidowControl(true);
|
|
2384
|
-
}
|
|
2521
|
+
// Delegate to parseOoxmlBoolean so every ST_OnOff literal — including
|
|
2522
|
+
// "off" / "on" — resolves correctly. The previous bespoke check missed
|
|
2523
|
+
// "off", silently flipping explicit-off to explicit-on.
|
|
2524
|
+
paragraph.setWidowControl(parseOoxmlBoolean(pPrObj['w:widowControl']));
|
|
2385
2525
|
}
|
|
2386
2526
|
|
|
2387
2527
|
// Outline level per ECMA-376 Part 1 §17.3.1.19
|
|
@@ -2397,15 +2537,11 @@ export class DocumentParser {
|
|
|
2397
2537
|
paragraph.setSuppressLineNumbers(parseOoxmlBoolean(pPrObj['w:suppressLineNumbers']));
|
|
2398
2538
|
}
|
|
2399
2539
|
|
|
2400
|
-
// Bidirectional layout per ECMA-376 Part 1 §17.3.1.6
|
|
2540
|
+
// Bidirectional layout per ECMA-376 Part 1 §17.3.1.6 — delegate to
|
|
2541
|
+
// parseOoxmlBoolean so "off"/"on" literals resolve correctly (the
|
|
2542
|
+
// previous bespoke check missed them).
|
|
2401
2543
|
if (pPrObj['w:bidi'] !== undefined) {
|
|
2402
|
-
|
|
2403
|
-
if (bidiVal === '0' || bidiVal === 'false' || bidiVal === false || bidiVal === 0) {
|
|
2404
|
-
paragraph.setBidi(false);
|
|
2405
|
-
} else {
|
|
2406
|
-
// Default is true when element present without val attribute or val="1"
|
|
2407
|
-
paragraph.setBidi(true);
|
|
2408
|
-
}
|
|
2544
|
+
paragraph.setBidi(parseOoxmlBoolean(pPrObj['w:bidi']));
|
|
2409
2545
|
}
|
|
2410
2546
|
|
|
2411
2547
|
// Text direction per ECMA-376 Part 1 §17.3.1.36
|
|
@@ -2423,20 +2559,10 @@ export class DocumentParser {
|
|
|
2423
2559
|
paragraph.setMirrorIndents(parseOoxmlBoolean(pPrObj['w:mirrorIndents']));
|
|
2424
2560
|
}
|
|
2425
2561
|
|
|
2426
|
-
// Auto-adjust right indent per ECMA-376 Part 1 §17.3.1.1
|
|
2562
|
+
// Auto-adjust right indent per ECMA-376 Part 1 §17.3.1.1 — delegate to
|
|
2563
|
+
// parseOoxmlBoolean so "off"/"on" literals resolve correctly.
|
|
2427
2564
|
if (pPrObj['w:adjustRightInd'] !== undefined) {
|
|
2428
|
-
|
|
2429
|
-
if (
|
|
2430
|
-
adjustRightIndVal === '0' ||
|
|
2431
|
-
adjustRightIndVal === 'false' ||
|
|
2432
|
-
adjustRightIndVal === false ||
|
|
2433
|
-
adjustRightIndVal === 0
|
|
2434
|
-
) {
|
|
2435
|
-
paragraph.setAdjustRightInd(false);
|
|
2436
|
-
} else {
|
|
2437
|
-
// Default is true when element present without val attribute or val="1"
|
|
2438
|
-
paragraph.setAdjustRightInd(true);
|
|
2439
|
-
}
|
|
2565
|
+
paragraph.setAdjustRightInd(parseOoxmlBoolean(pPrObj['w:adjustRightInd']));
|
|
2440
2566
|
}
|
|
2441
2567
|
|
|
2442
2568
|
// Text frame properties per ECMA-376 Part 1 §17.3.1.11
|
|
@@ -2510,11 +2636,16 @@ export class DocumentParser {
|
|
|
2510
2636
|
}
|
|
2511
2637
|
}
|
|
2512
2638
|
|
|
2513
|
-
// HTML div ID per ECMA-376 Part 1 §17.3.1.
|
|
2639
|
+
// HTML div ID per ECMA-376 Part 1 §17.3.1.10 (CT_DivId). `w:val` is
|
|
2640
|
+
// ST_DecimalNumber — 0 is a valid ID referencing the first div in
|
|
2641
|
+
// web settings. XMLParser coerces `"0"` to the number 0, and the
|
|
2642
|
+
// previous `if (divIdVal)` truthy check silently dropped it, breaking
|
|
2643
|
+
// the paragraph's link to div index 0 on every round-trip.
|
|
2514
2644
|
if (pPrObj['w:divId']) {
|
|
2515
2645
|
const divIdVal = pPrObj['w:divId']?.['@_w:val'];
|
|
2516
|
-
if (divIdVal) {
|
|
2517
|
-
|
|
2646
|
+
if (isExplicitlySet(divIdVal)) {
|
|
2647
|
+
const parsed = safeParseInt(divIdVal);
|
|
2648
|
+
if (!isNaN(parsed)) paragraph.setDivId(parsed);
|
|
2518
2649
|
}
|
|
2519
2650
|
}
|
|
2520
2651
|
|
|
@@ -2529,13 +2660,27 @@ export class DocumentParser {
|
|
|
2529
2660
|
}
|
|
2530
2661
|
}
|
|
2531
2662
|
|
|
2532
|
-
// Paragraph property change tracking per ECMA-376 Part 1 §17.3.1.27
|
|
2663
|
+
// Paragraph property change tracking per ECMA-376 Part 1 §17.3.1.27.
|
|
2664
|
+
// CT_TrackChange attributes — `w:id` (ST_DecimalNumber, required),
|
|
2665
|
+
// `w:author` (ST_String, required), `w:date` (ST_DateTime, optional).
|
|
2666
|
+
// XMLParser coerces `w:id="0"` to the number 0; the previous
|
|
2667
|
+
// `if (changeObj['@_w:id'])` truthy gate silently dropped id=0,
|
|
2668
|
+
// producing `<w:pPrChange w:author="…" w:date="…"/>` on emission —
|
|
2669
|
+
// missing the required `w:id` and failing strict validation. The
|
|
2670
|
+
// sibling `trPrChange` / `tblPrChange` / `tcPrChange` / `sectPrChange`
|
|
2671
|
+
// parsers already use `|| '0'` or `!== undefined` for the same reason.
|
|
2533
2672
|
if (pPrObj['w:pPrChange']) {
|
|
2534
2673
|
const changeObj = pPrObj['w:pPrChange'];
|
|
2535
2674
|
const change: any = {};
|
|
2536
|
-
if (changeObj['@_w:author']
|
|
2537
|
-
|
|
2538
|
-
|
|
2675
|
+
if (changeObj['@_w:author'] !== undefined) {
|
|
2676
|
+
change.author = String(changeObj['@_w:author']);
|
|
2677
|
+
}
|
|
2678
|
+
if (changeObj['@_w:date'] !== undefined) {
|
|
2679
|
+
change.date = String(changeObj['@_w:date']);
|
|
2680
|
+
}
|
|
2681
|
+
if (changeObj['@_w:id'] !== undefined) {
|
|
2682
|
+
change.id = String(changeObj['@_w:id']);
|
|
2683
|
+
}
|
|
2539
2684
|
|
|
2540
2685
|
// Parse child w:pPr for previousProperties to preserve tracked change history
|
|
2541
2686
|
if (changeObj['w:pPr']) {
|
|
@@ -2566,7 +2711,11 @@ export class DocumentParser {
|
|
|
2566
2711
|
}
|
|
2567
2712
|
|
|
2568
2713
|
// Parse previous indentation
|
|
2569
|
-
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to w:left/w:right
|
|
2714
|
+
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to w:left/w:right.
|
|
2715
|
+
// Also parse the six CJK character-unit variants (ST_DecimalNumber) per §17.3.1.12;
|
|
2716
|
+
// these round-trip alongside the twips so Word's rendering of the tracked "previous"
|
|
2717
|
+
// state stays locale-accurate for CJK-authored documents. Matches the iteration-21
|
|
2718
|
+
// fix on the main-path parser.
|
|
2570
2719
|
if (prevPPr['w:ind']) {
|
|
2571
2720
|
const ind = prevPPr['w:ind'];
|
|
2572
2721
|
previousProperties.indentation = {};
|
|
@@ -2578,6 +2727,18 @@ export class DocumentParser {
|
|
|
2578
2727
|
previousProperties.indentation.firstLine = parseInt(ind['@_w:firstLine'], 10);
|
|
2579
2728
|
if (ind['@_w:hanging'] !== undefined)
|
|
2580
2729
|
previousProperties.indentation.hanging = parseInt(ind['@_w:hanging'], 10);
|
|
2730
|
+
// CJK character-unit variants. startChars/endChars collapse onto
|
|
2731
|
+
// leftChars/rightChars (same pattern as the twips variants).
|
|
2732
|
+
const leftCharsVal = ind['@_w:startChars'] ?? ind['@_w:leftChars'];
|
|
2733
|
+
const rightCharsVal = ind['@_w:endChars'] ?? ind['@_w:rightChars'];
|
|
2734
|
+
if (leftCharsVal !== undefined)
|
|
2735
|
+
previousProperties.indentation.leftChars = parseInt(leftCharsVal, 10);
|
|
2736
|
+
if (rightCharsVal !== undefined)
|
|
2737
|
+
previousProperties.indentation.rightChars = parseInt(rightCharsVal, 10);
|
|
2738
|
+
if (ind['@_w:firstLineChars'] !== undefined)
|
|
2739
|
+
previousProperties.indentation.firstLineChars = parseInt(ind['@_w:firstLineChars'], 10);
|
|
2740
|
+
if (ind['@_w:hangingChars'] !== undefined)
|
|
2741
|
+
previousProperties.indentation.hangingChars = parseInt(ind['@_w:hangingChars'], 10);
|
|
2581
2742
|
}
|
|
2582
2743
|
|
|
2583
2744
|
// Parse previous alignment
|
|
@@ -2601,48 +2762,51 @@ export class DocumentParser {
|
|
|
2601
2762
|
previousProperties.spacing.beforeLines = parseInt(spacing['@_w:beforeLines'], 10);
|
|
2602
2763
|
if (spacing['@_w:afterLines'] !== undefined)
|
|
2603
2764
|
previousProperties.spacing.afterLines = parseInt(spacing['@_w:afterLines'], 10);
|
|
2765
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
2604
2766
|
const beforeAuto = spacing['@_w:beforeAutospacing'];
|
|
2605
2767
|
if (beforeAuto !== undefined)
|
|
2606
|
-
previousProperties.spacing.beforeAutospacing =
|
|
2607
|
-
String(beforeAuto) === '1' || String(beforeAuto) === 'true';
|
|
2768
|
+
previousProperties.spacing.beforeAutospacing = parseOnOffAttribute(beforeAuto);
|
|
2608
2769
|
const afterAuto = spacing['@_w:afterAutospacing'];
|
|
2609
2770
|
if (afterAuto !== undefined)
|
|
2610
|
-
previousProperties.spacing.afterAutospacing =
|
|
2611
|
-
String(afterAuto) === '1' || String(afterAuto) === 'true';
|
|
2771
|
+
previousProperties.spacing.afterAutospacing = parseOnOffAttribute(afterAuto);
|
|
2612
2772
|
}
|
|
2613
2773
|
|
|
2614
|
-
//
|
|
2774
|
+
// CT_OnOff properties per ECMA-376 §17.17.4 — accept "1"/"0"/"true"/"false"/"on"/"off"
|
|
2775
|
+
// plus the number forms produced by fast-xml-parser's parseAttributeValue. Using
|
|
2776
|
+
// parseOoxmlBoolean() keeps pPrChange round-trips consistent with the main pPr parser;
|
|
2777
|
+
// the previous `!== '0'` pattern silently flipped "false", "off", and the numeric 0.
|
|
2615
2778
|
if (prevPPr['w:keepNext']) {
|
|
2616
|
-
previousProperties.keepNext = prevPPr['w:keepNext']
|
|
2779
|
+
previousProperties.keepNext = parseOoxmlBoolean(prevPPr['w:keepNext']);
|
|
2617
2780
|
}
|
|
2618
2781
|
if (prevPPr['w:keepLines']) {
|
|
2619
|
-
previousProperties.keepLines = prevPPr['w:keepLines']
|
|
2782
|
+
previousProperties.keepLines = parseOoxmlBoolean(prevPPr['w:keepLines']);
|
|
2620
2783
|
}
|
|
2621
2784
|
if (prevPPr['w:pageBreakBefore']) {
|
|
2622
|
-
previousProperties.pageBreakBefore = prevPPr['w:pageBreakBefore']
|
|
2785
|
+
previousProperties.pageBreakBefore = parseOoxmlBoolean(prevPPr['w:pageBreakBefore']);
|
|
2623
2786
|
}
|
|
2624
2787
|
|
|
2625
2788
|
// === Extended paragraph property parsing per ECMA-376 Part 1 §17.3.1 ===
|
|
2626
2789
|
|
|
2627
2790
|
// Parse widowControl (w:widowControl) - orphan/widow control
|
|
2628
2791
|
if (prevPPr['w:widowControl']) {
|
|
2629
|
-
previousProperties.widowControl = prevPPr['w:widowControl']
|
|
2792
|
+
previousProperties.widowControl = parseOoxmlBoolean(prevPPr['w:widowControl']);
|
|
2630
2793
|
}
|
|
2631
2794
|
|
|
2632
2795
|
// Parse suppressAutoHyphens (w:suppressAutoHyphens)
|
|
2633
2796
|
if (prevPPr['w:suppressAutoHyphens']) {
|
|
2634
|
-
previousProperties.suppressAutoHyphens =
|
|
2635
|
-
prevPPr['w:suppressAutoHyphens']
|
|
2797
|
+
previousProperties.suppressAutoHyphens = parseOoxmlBoolean(
|
|
2798
|
+
prevPPr['w:suppressAutoHyphens']
|
|
2799
|
+
);
|
|
2636
2800
|
}
|
|
2637
2801
|
|
|
2638
2802
|
// Parse contextualSpacing (w:contextualSpacing)
|
|
2639
2803
|
if (prevPPr['w:contextualSpacing']) {
|
|
2640
|
-
previousProperties.contextualSpacing = prevPPr['w:contextualSpacing']
|
|
2804
|
+
previousProperties.contextualSpacing = parseOoxmlBoolean(prevPPr['w:contextualSpacing']);
|
|
2641
2805
|
}
|
|
2642
2806
|
|
|
2643
2807
|
// Parse mirrorIndents (w:mirrorIndents)
|
|
2644
2808
|
if (prevPPr['w:mirrorIndents']) {
|
|
2645
|
-
previousProperties.mirrorIndents = prevPPr['w:mirrorIndents']
|
|
2809
|
+
previousProperties.mirrorIndents = parseOoxmlBoolean(prevPPr['w:mirrorIndents']);
|
|
2646
2810
|
}
|
|
2647
2811
|
|
|
2648
2812
|
// Parse outlineLevel (w:outlineLvl @w:val)
|
|
@@ -2650,40 +2814,106 @@ export class DocumentParser {
|
|
|
2650
2814
|
previousProperties.outlineLevel = parseInt(prevPPr['w:outlineLvl']['@_w:val'], 10);
|
|
2651
2815
|
}
|
|
2652
2816
|
|
|
2817
|
+
// Parse previous text frame properties (w:framePr) per ECMA-376
|
|
2818
|
+
// Part 1 §17.3.1.11 CT_FramePr. The pPrChange emitter already
|
|
2819
|
+
// rebuilds every framePr attribute (see Paragraph.ts §3634), but
|
|
2820
|
+
// the parser never read them — so a tracked change to any
|
|
2821
|
+
// frame property (drop-cap, text-box positioning, wrap mode,
|
|
2822
|
+
// anchor lock…) silently lost the previous state on round-trip.
|
|
2823
|
+
if (prevPPr['w:framePr']) {
|
|
2824
|
+
const framePr = prevPPr['w:framePr'];
|
|
2825
|
+
const frameProps: any = {};
|
|
2826
|
+
if (isExplicitlySet(framePr['@_w:w'])) frameProps.w = safeParseInt(framePr['@_w:w']);
|
|
2827
|
+
if (isExplicitlySet(framePr['@_w:h'])) frameProps.h = safeParseInt(framePr['@_w:h']);
|
|
2828
|
+
if (framePr['@_w:hRule']) frameProps.hRule = String(framePr['@_w:hRule']);
|
|
2829
|
+
if (isExplicitlySet(framePr['@_w:x'])) frameProps.x = safeParseInt(framePr['@_w:x']);
|
|
2830
|
+
if (isExplicitlySet(framePr['@_w:y'])) frameProps.y = safeParseInt(framePr['@_w:y']);
|
|
2831
|
+
if (framePr['@_w:xAlign']) frameProps.xAlign = String(framePr['@_w:xAlign']);
|
|
2832
|
+
if (framePr['@_w:yAlign']) frameProps.yAlign = String(framePr['@_w:yAlign']);
|
|
2833
|
+
if (framePr['@_w:hAnchor']) frameProps.hAnchor = String(framePr['@_w:hAnchor']);
|
|
2834
|
+
if (framePr['@_w:vAnchor']) frameProps.vAnchor = String(framePr['@_w:vAnchor']);
|
|
2835
|
+
if (isExplicitlySet(framePr['@_w:hSpace'])) {
|
|
2836
|
+
frameProps.hSpace = safeParseInt(framePr['@_w:hSpace']);
|
|
2837
|
+
}
|
|
2838
|
+
if (isExplicitlySet(framePr['@_w:vSpace'])) {
|
|
2839
|
+
frameProps.vSpace = safeParseInt(framePr['@_w:vSpace']);
|
|
2840
|
+
}
|
|
2841
|
+
if (framePr['@_w:wrap']) frameProps.wrap = String(framePr['@_w:wrap']);
|
|
2842
|
+
if (framePr['@_w:dropCap']) frameProps.dropCap = String(framePr['@_w:dropCap']);
|
|
2843
|
+
if (isExplicitlySet(framePr['@_w:lines'])) {
|
|
2844
|
+
frameProps.lines = safeParseInt(framePr['@_w:lines']);
|
|
2845
|
+
}
|
|
2846
|
+
if (isExplicitlySet(framePr['@_w:anchorLock'])) {
|
|
2847
|
+
frameProps.anchorLock = parseOnOffAttribute(String(framePr['@_w:anchorLock']), true);
|
|
2848
|
+
}
|
|
2849
|
+
if (Object.keys(frameProps).length > 0) {
|
|
2850
|
+
previousProperties.framePr = frameProps;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2653
2854
|
// Parse bidi (w:bidi) - right-to-left paragraph
|
|
2654
2855
|
if (prevPPr['w:bidi']) {
|
|
2655
|
-
previousProperties.bidi = prevPPr['w:bidi']
|
|
2856
|
+
previousProperties.bidi = parseOoxmlBoolean(prevPPr['w:bidi']);
|
|
2656
2857
|
}
|
|
2657
2858
|
|
|
2658
2859
|
// Parse suppressLineNumbers (w:suppressLineNumbers)
|
|
2659
2860
|
if (prevPPr['w:suppressLineNumbers']) {
|
|
2660
|
-
previousProperties.suppressLineNumbers =
|
|
2661
|
-
prevPPr['w:suppressLineNumbers']
|
|
2861
|
+
previousProperties.suppressLineNumbers = parseOoxmlBoolean(
|
|
2862
|
+
prevPPr['w:suppressLineNumbers']
|
|
2863
|
+
);
|
|
2662
2864
|
}
|
|
2663
2865
|
|
|
2664
2866
|
// Parse adjustRightInd (w:adjustRightInd)
|
|
2665
2867
|
if (prevPPr['w:adjustRightInd']) {
|
|
2666
|
-
previousProperties.adjustRightInd = prevPPr['w:adjustRightInd']
|
|
2868
|
+
previousProperties.adjustRightInd = parseOoxmlBoolean(prevPPr['w:adjustRightInd']);
|
|
2667
2869
|
}
|
|
2668
2870
|
|
|
2669
2871
|
// Parse snapToGrid (w:snapToGrid)
|
|
2670
2872
|
if (prevPPr['w:snapToGrid']) {
|
|
2671
|
-
previousProperties.snapToGrid = prevPPr['w:snapToGrid']
|
|
2873
|
+
previousProperties.snapToGrid = parseOoxmlBoolean(prevPPr['w:snapToGrid']);
|
|
2672
2874
|
}
|
|
2673
2875
|
|
|
2674
2876
|
// Parse wordWrap (w:wordWrap)
|
|
2675
2877
|
if (prevPPr['w:wordWrap']) {
|
|
2676
|
-
previousProperties.wordWrap = prevPPr['w:wordWrap']
|
|
2878
|
+
previousProperties.wordWrap = parseOoxmlBoolean(prevPPr['w:wordWrap']);
|
|
2677
2879
|
}
|
|
2678
2880
|
|
|
2679
2881
|
// Parse autoSpaceDE (w:autoSpaceDE) - East Asian/numeric spacing
|
|
2680
2882
|
if (prevPPr['w:autoSpaceDE']) {
|
|
2681
|
-
previousProperties.autoSpaceDE = prevPPr['w:autoSpaceDE']
|
|
2883
|
+
previousProperties.autoSpaceDE = parseOoxmlBoolean(prevPPr['w:autoSpaceDE']);
|
|
2682
2884
|
}
|
|
2683
2885
|
|
|
2684
2886
|
// Parse autoSpaceDN (w:autoSpaceDN) - East Asian/Western spacing
|
|
2685
2887
|
if (prevPPr['w:autoSpaceDN']) {
|
|
2686
|
-
previousProperties.autoSpaceDN = prevPPr['w:autoSpaceDN']
|
|
2888
|
+
previousProperties.autoSpaceDN = parseOoxmlBoolean(prevPPr['w:autoSpaceDN']);
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
// Parse kinsoku / overflowPunct / topLinePunct / suppressOverlap —
|
|
2892
|
+
// CJK typography CT_OnOff flags. The Paragraph pPrChange generator
|
|
2893
|
+
// already emits these in the previous-properties block, but the
|
|
2894
|
+
// parser was missing the read side, so tracked paragraph-property
|
|
2895
|
+
// revisions that recorded any of these four flags were silently
|
|
2896
|
+
// dropped on load → save. Uses `parseOoxmlBoolean` to honour every
|
|
2897
|
+
// ST_OnOff literal (bare, 1/0, true/false, on/off).
|
|
2898
|
+
if (prevPPr['w:kinsoku']) {
|
|
2899
|
+
(previousProperties as { kinsoku?: boolean }).kinsoku = parseOoxmlBoolean(
|
|
2900
|
+
prevPPr['w:kinsoku']
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
if (prevPPr['w:overflowPunct']) {
|
|
2904
|
+
(previousProperties as { overflowPunct?: boolean }).overflowPunct = parseOoxmlBoolean(
|
|
2905
|
+
prevPPr['w:overflowPunct']
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2908
|
+
if (prevPPr['w:topLinePunct']) {
|
|
2909
|
+
(previousProperties as { topLinePunct?: boolean }).topLinePunct = parseOoxmlBoolean(
|
|
2910
|
+
prevPPr['w:topLinePunct']
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
if (prevPPr['w:suppressOverlap']) {
|
|
2914
|
+
(previousProperties as { suppressOverlap?: boolean }).suppressOverlap = parseOoxmlBoolean(
|
|
2915
|
+
prevPPr['w:suppressOverlap']
|
|
2916
|
+
);
|
|
2687
2917
|
}
|
|
2688
2918
|
|
|
2689
2919
|
// Parse textDirection (w:textDirection @w:val)
|
|
@@ -2696,23 +2926,68 @@ export class DocumentParser {
|
|
|
2696
2926
|
previousProperties.textAlignment = String(prevPPr['w:textAlignment']['@_w:val']);
|
|
2697
2927
|
}
|
|
2698
2928
|
|
|
2929
|
+
// Parse previous divId (w:divId) per ECMA-376 §17.3.1.10 —
|
|
2930
|
+
// ST_DecimalNumber referencing a web-settings div. Zero is a
|
|
2931
|
+
// legal ID (first div). XMLParser coerces `"0"` to number 0, so
|
|
2932
|
+
// gate via `isExplicitlySet` to preserve divId=0 on tracked
|
|
2933
|
+
// previous state. The pPrChange emitter (Paragraph.ts §3915)
|
|
2934
|
+
// re-emits prev.divId via `!== undefined`.
|
|
2935
|
+
if (prevPPr['w:divId']?.['@_w:val'] !== undefined) {
|
|
2936
|
+
const rawDivId = prevPPr['w:divId']['@_w:val'];
|
|
2937
|
+
const parsedDivId = safeParseInt(rawDivId);
|
|
2938
|
+
if (!isNaN(parsedDivId)) {
|
|
2939
|
+
previousProperties.divId = parsedDivId;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
// Parse previous cnfStyle (w:cnfStyle) per ECMA-376 §17.3.1.8 —
|
|
2944
|
+
// 12-character bitmask identifying which conditional-formatting
|
|
2945
|
+
// flags from the parent table style apply. XMLParser coerces
|
|
2946
|
+
// purely-numeric hex strings, but the custom parseValue keeps
|
|
2947
|
+
// 7+-digit strings as-is (so 12-char bitmasks survive); use
|
|
2948
|
+
// String + padStart to defensively normalise any shorter form.
|
|
2949
|
+
if (prevPPr['w:cnfStyle']?.['@_w:val'] !== undefined) {
|
|
2950
|
+
previousProperties.cnfStyle = String(prevPPr['w:cnfStyle']['@_w:val']).padStart(12, '0');
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2699
2953
|
// Parse paragraph borders (w:pBdr) per ECMA-376 Part 1 §17.3.1.24
|
|
2954
|
+
// Previous versions stored the attribute values under the wrong
|
|
2955
|
+
// field names (`val`/`sz` instead of `style`/`size`) — the
|
|
2956
|
+
// paragraph emitter reads `style`/`size`, so every tracked
|
|
2957
|
+
// previous border collapsed to `<w:top w:val="nil"/>` on
|
|
2958
|
+
// round-trip. The CT_Border attribute coverage here now matches
|
|
2959
|
+
// the main parser (§17.18.2): all nine attrs, with shadow/frame
|
|
2960
|
+
// routed through parseOnOffAttribute so ST_OnOff literals
|
|
2961
|
+
// ("on"/"off"/"true"/"false") resolve correctly.
|
|
2700
2962
|
if (prevPPr['w:pBdr']) {
|
|
2701
2963
|
const pBdr = prevPPr['w:pBdr'];
|
|
2702
2964
|
previousProperties.borders = {};
|
|
2703
2965
|
|
|
2704
2966
|
const parseBorder = (borderObj: any) => {
|
|
2705
2967
|
if (!borderObj) return undefined;
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
themeColor
|
|
2715
|
-
}
|
|
2968
|
+
const border: any = {};
|
|
2969
|
+
if (borderObj['@_w:val']) border.style = borderObj['@_w:val'];
|
|
2970
|
+
if (borderObj['@_w:sz'] !== undefined) border.size = safeParseInt(borderObj['@_w:sz']);
|
|
2971
|
+
if (borderObj['@_w:space'] !== undefined) {
|
|
2972
|
+
border.space = safeParseInt(borderObj['@_w:space']);
|
|
2973
|
+
}
|
|
2974
|
+
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
2975
|
+
if (borderObj['@_w:themeColor']) {
|
|
2976
|
+
border.themeColor = String(borderObj['@_w:themeColor']);
|
|
2977
|
+
}
|
|
2978
|
+
if (borderObj['@_w:themeTint']) {
|
|
2979
|
+
border.themeTint = String(borderObj['@_w:themeTint']);
|
|
2980
|
+
}
|
|
2981
|
+
if (borderObj['@_w:themeShade']) {
|
|
2982
|
+
border.themeShade = String(borderObj['@_w:themeShade']);
|
|
2983
|
+
}
|
|
2984
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
2985
|
+
border.shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
2986
|
+
}
|
|
2987
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
2988
|
+
border.frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
2989
|
+
}
|
|
2990
|
+
return Object.keys(border).length > 0 ? border : undefined;
|
|
2716
2991
|
};
|
|
2717
2992
|
|
|
2718
2993
|
if (pBdr['w:top']) previousProperties.borders.top = parseBorder(pBdr['w:top']);
|
|
@@ -3873,11 +4148,15 @@ export class DocumentParser {
|
|
|
3873
4148
|
return XMLBuilder.unescapeXml(String(node));
|
|
3874
4149
|
};
|
|
3875
4150
|
|
|
3876
|
-
|
|
4151
|
+
// Field-character attributes (w:dirty, w:fldLock, w:lock on w:fldChar) are
|
|
4152
|
+
// ST_OnOff per ECMA-376 §17.16.18. Delegate to parseOnOffAttribute so every
|
|
4153
|
+
// literal is honoured — the previous inline check missed "on" (silently
|
|
4154
|
+
// coerced to false) and was tighter than the spec requires.
|
|
4155
|
+
const parseBooleanAttr = (value: unknown): boolean | undefined => {
|
|
3877
4156
|
if (value === undefined || value === null) {
|
|
3878
4157
|
return undefined;
|
|
3879
4158
|
}
|
|
3880
|
-
return value
|
|
4159
|
+
return parseOnOffAttribute(value);
|
|
3881
4160
|
};
|
|
3882
4161
|
|
|
3883
4162
|
// Parse w:ffData from a fldChar object (form field data per ECMA-376 §17.16.17)
|
|
@@ -3892,15 +4171,13 @@ export class DocumentParser {
|
|
|
3892
4171
|
if (ffDataObj['w:name']?.['@_w:val'] !== undefined) {
|
|
3893
4172
|
ffd.name = String(ffDataObj['w:name']['@_w:val']);
|
|
3894
4173
|
}
|
|
3895
|
-
// w:enabled
|
|
4174
|
+
// w:enabled — CT_OnOff per ECMA-376 §17.16.11; presence = true, w:val honours ST_OnOff
|
|
3896
4175
|
if (ffDataObj['w:enabled'] !== undefined) {
|
|
3897
|
-
|
|
3898
|
-
ffd.enabled = enabledVal === '0' || enabledVal === 0 ? false : true;
|
|
4176
|
+
ffd.enabled = parseOoxmlBoolean(ffDataObj['w:enabled']);
|
|
3899
4177
|
}
|
|
3900
|
-
// w:calcOnExit
|
|
4178
|
+
// w:calcOnExit — CT_OnOff per ECMA-376 §17.16.4; presence = true, w:val honours ST_OnOff
|
|
3901
4179
|
if (ffDataObj['w:calcOnExit'] !== undefined) {
|
|
3902
|
-
|
|
3903
|
-
ffd.calcOnExit = calcVal === '1' || calcVal === 1 || calcVal === true;
|
|
4180
|
+
ffd.calcOnExit = parseOoxmlBoolean(ffDataObj['w:calcOnExit']);
|
|
3904
4181
|
}
|
|
3905
4182
|
// w:helpText
|
|
3906
4183
|
if (ffDataObj['w:helpText']?.['@_w:val'] !== undefined) {
|
|
@@ -3936,13 +4213,14 @@ export class DocumentParser {
|
|
|
3936
4213
|
if (ffDataObj['w:checkBox'] !== undefined) {
|
|
3937
4214
|
const cb: XmlNode = ffDataObj['w:checkBox'];
|
|
3938
4215
|
const checkBox: FormFieldCheckBox = { type: 'checkBox' };
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
4216
|
+
// w:default / w:checked are CT_OnOff per ECMA-376 §17.16.18 —
|
|
4217
|
+
// honour every ST_OnOff literal ("true"/"false"/"1"/"0"/"on"/"off")
|
|
4218
|
+
// and treat a bare self-closing element as true.
|
|
4219
|
+
if (cb['w:default'] !== undefined) {
|
|
4220
|
+
checkBox.defaultChecked = parseOoxmlBoolean(cb['w:default']);
|
|
3942
4221
|
}
|
|
3943
|
-
if (cb['w:checked']
|
|
3944
|
-
checkBox.checked =
|
|
3945
|
-
cb['w:checked']['@_w:val'] === '1' || cb['w:checked']['@_w:val'] === 1;
|
|
4222
|
+
if (cb['w:checked'] !== undefined) {
|
|
4223
|
+
checkBox.checked = parseOoxmlBoolean(cb['w:checked']);
|
|
3946
4224
|
}
|
|
3947
4225
|
if (cb['w:size']?.['@_w:val'] !== undefined) {
|
|
3948
4226
|
checkBox.size = Number(cb['w:size']['@_w:val']);
|
|
@@ -4146,30 +4424,47 @@ export class DocumentParser {
|
|
|
4146
4424
|
content.push({ type: 'annotationRef' });
|
|
4147
4425
|
break;
|
|
4148
4426
|
|
|
4149
|
-
// Footnote reference (w:footnoteReference) per ECMA-376 Part 1 §17.11.13
|
|
4427
|
+
// Footnote reference (w:footnoteReference) per ECMA-376 Part 1 §17.11.13.
|
|
4428
|
+
// w:customMarkFollows is ST_OnOff — honour every literal via parseOnOffAttribute.
|
|
4150
4429
|
case 'w:footnoteReference': {
|
|
4151
4430
|
const fnRefElements = toArray(runObj['w:footnoteReference']);
|
|
4152
4431
|
const fnRef = fnRefElements[elementIndex] || fnRefElements[0];
|
|
4153
4432
|
const fnId = fnRef?.['@_w:id'];
|
|
4433
|
+
const fnCustomMark = fnRef?.['@_w:customMarkFollows'];
|
|
4154
4434
|
content.push({
|
|
4155
4435
|
type: 'footnoteReference',
|
|
4156
4436
|
footnoteId: fnId !== undefined ? parseInt(fnId, 10) : undefined,
|
|
4437
|
+
customMarkFollows:
|
|
4438
|
+
fnCustomMark !== undefined ? parseOnOffAttribute(fnCustomMark) : undefined,
|
|
4157
4439
|
});
|
|
4158
4440
|
break;
|
|
4159
4441
|
}
|
|
4160
4442
|
|
|
4161
|
-
// Endnote reference (w:endnoteReference) per ECMA-376 Part 1 §17.11.2
|
|
4443
|
+
// Endnote reference (w:endnoteReference) per ECMA-376 Part 1 §17.11.2.
|
|
4444
|
+
// Same ST_OnOff treatment for w:customMarkFollows.
|
|
4162
4445
|
case 'w:endnoteReference': {
|
|
4163
4446
|
const enRefElements = toArray(runObj['w:endnoteReference']);
|
|
4164
4447
|
const enRef = enRefElements[elementIndex] || enRefElements[0];
|
|
4165
4448
|
const enId = enRef?.['@_w:id'];
|
|
4449
|
+
const enCustomMark = enRef?.['@_w:customMarkFollows'];
|
|
4166
4450
|
content.push({
|
|
4167
4451
|
type: 'endnoteReference',
|
|
4168
4452
|
endnoteId: enId !== undefined ? parseInt(enId, 10) : undefined,
|
|
4453
|
+
customMarkFollows:
|
|
4454
|
+
enCustomMark !== undefined ? parseOnOffAttribute(enCustomMark) : undefined,
|
|
4169
4455
|
});
|
|
4170
4456
|
break;
|
|
4171
4457
|
}
|
|
4172
4458
|
|
|
4459
|
+
// Auto-numbered marks INSIDE a footnote/endnote body per
|
|
4460
|
+
// ECMA-376 §17.11.14 / §17.11.3. Empty self-closing elements.
|
|
4461
|
+
case 'w:footnoteRef':
|
|
4462
|
+
content.push({ type: 'footnoteRef' });
|
|
4463
|
+
break;
|
|
4464
|
+
case 'w:endnoteRef':
|
|
4465
|
+
content.push({ type: 'endnoteRef' });
|
|
4466
|
+
break;
|
|
4467
|
+
|
|
4173
4468
|
case 'w:dayShort':
|
|
4174
4469
|
content.push({ type: 'dayShort' });
|
|
4175
4470
|
break;
|
|
@@ -4355,14 +4650,18 @@ export class DocumentParser {
|
|
|
4355
4650
|
if (runObj['w:annotationRef'] !== undefined) {
|
|
4356
4651
|
content.push({ type: 'annotationRef' });
|
|
4357
4652
|
}
|
|
4358
|
-
// Footnote/endnote reference fallback
|
|
4653
|
+
// Footnote/endnote reference fallback. w:customMarkFollows is ST_OnOff
|
|
4654
|
+
// per ECMA-376 §17.11.13 / §17.11.2 — honour every literal.
|
|
4359
4655
|
if (runObj['w:footnoteReference'] !== undefined) {
|
|
4360
4656
|
const fnRefElements = toArray(runObj['w:footnoteReference']);
|
|
4361
4657
|
for (const fnRef of fnRefElements) {
|
|
4362
4658
|
const fnId = fnRef?.['@_w:id'];
|
|
4659
|
+
const fnCustomMark = fnRef?.['@_w:customMarkFollows'];
|
|
4363
4660
|
content.push({
|
|
4364
4661
|
type: 'footnoteReference',
|
|
4365
4662
|
footnoteId: fnId !== undefined ? parseInt(fnId, 10) : undefined,
|
|
4663
|
+
customMarkFollows:
|
|
4664
|
+
fnCustomMark !== undefined ? parseOnOffAttribute(fnCustomMark) : undefined,
|
|
4366
4665
|
});
|
|
4367
4666
|
}
|
|
4368
4667
|
}
|
|
@@ -4370,12 +4669,22 @@ export class DocumentParser {
|
|
|
4370
4669
|
const enRefElements = toArray(runObj['w:endnoteReference']);
|
|
4371
4670
|
for (const enRef of enRefElements) {
|
|
4372
4671
|
const enId = enRef?.['@_w:id'];
|
|
4672
|
+
const enCustomMark = enRef?.['@_w:customMarkFollows'];
|
|
4373
4673
|
content.push({
|
|
4374
4674
|
type: 'endnoteReference',
|
|
4375
4675
|
endnoteId: enId !== undefined ? parseInt(enId, 10) : undefined,
|
|
4676
|
+
customMarkFollows:
|
|
4677
|
+
enCustomMark !== undefined ? parseOnOffAttribute(enCustomMark) : undefined,
|
|
4376
4678
|
});
|
|
4377
4679
|
}
|
|
4378
4680
|
}
|
|
4681
|
+
// Auto-numbered marks INSIDE a footnote/endnote body — empty elements.
|
|
4682
|
+
if (runObj['w:footnoteRef'] !== undefined) {
|
|
4683
|
+
content.push({ type: 'footnoteRef' });
|
|
4684
|
+
}
|
|
4685
|
+
if (runObj['w:endnoteRef'] !== undefined) {
|
|
4686
|
+
content.push({ type: 'endnoteRef' });
|
|
4687
|
+
}
|
|
4379
4688
|
if (runObj['w:dayShort'] !== undefined) {
|
|
4380
4689
|
content.push({ type: 'dayShort' });
|
|
4381
4690
|
}
|
|
@@ -4474,12 +4783,40 @@ export class DocumentParser {
|
|
|
4474
4783
|
: [hyperlinkObj['w:bookmarkStart']];
|
|
4475
4784
|
for (const bs of bookmarkStarts) {
|
|
4476
4785
|
const id = bs['@_w:id'];
|
|
4477
|
-
|
|
4786
|
+
// w:name is ST_String per §17.16.5 CT_Bookmark. XMLParser
|
|
4787
|
+
// coerces purely-numeric bookmark names ("12345") to JS
|
|
4788
|
+
// numbers; cast so Bookmark.name holds the declared string
|
|
4789
|
+
// type contract (parent parsers already do the same —
|
|
4790
|
+
// iter 125 toOptString helper).
|
|
4791
|
+
const rawName = bs['@_w:name'];
|
|
4792
|
+
const name =
|
|
4793
|
+
rawName === undefined || rawName === null || rawName === ''
|
|
4794
|
+
? undefined
|
|
4795
|
+
: String(rawName);
|
|
4478
4796
|
if (id !== undefined && name) {
|
|
4797
|
+
// CT_Bookmark per ECMA-376 §17.16.5: the object-form parser
|
|
4798
|
+
// must carry the same four "markup" attributes that the
|
|
4799
|
+
// XML-string bookmarkStart parser handles — colFirst/colLast
|
|
4800
|
+
// (table-column-scoped bookmarks) and displacedByCustomXml
|
|
4801
|
+
// (custom-XML boundary disambiguator). Previously dropped
|
|
4802
|
+
// whenever a hyperlink wrapped a bookmark, so inline
|
|
4803
|
+
// hyperlinks anchored to table-column bookmarks lost their
|
|
4804
|
+
// column range on round-trip.
|
|
4805
|
+
const rawColFirst = bs['@_w:colFirst'];
|
|
4806
|
+
const rawColLast = bs['@_w:colLast'];
|
|
4807
|
+
const rawDisplaced = bs['@_w:displacedByCustomXml'];
|
|
4808
|
+
const colFirst =
|
|
4809
|
+
rawColFirst === undefined ? undefined : parseInt(String(rawColFirst), 10);
|
|
4810
|
+
const colLast = rawColLast === undefined ? undefined : parseInt(String(rawColLast), 10);
|
|
4811
|
+
const displacedByCustomXml =
|
|
4812
|
+
rawDisplaced === 'next' || rawDisplaced === 'prev' ? rawDisplaced : undefined;
|
|
4479
4813
|
const bookmark = new Bookmark({
|
|
4480
4814
|
name: name,
|
|
4481
4815
|
id: typeof id === 'number' ? id : parseInt(id, 10),
|
|
4482
4816
|
skipNormalization: true,
|
|
4817
|
+
colFirst: Number.isNaN(colFirst as number) ? undefined : colFirst,
|
|
4818
|
+
colLast: Number.isNaN(colLast as number) ? undefined : colLast,
|
|
4819
|
+
displacedByCustomXml,
|
|
4483
4820
|
});
|
|
4484
4821
|
result.bookmarkStarts.push(bookmark);
|
|
4485
4822
|
// Also register with BookmarkManager
|
|
@@ -4501,23 +4838,47 @@ export class DocumentParser {
|
|
|
4501
4838
|
for (const be of bookmarkEnds) {
|
|
4502
4839
|
const id = be['@_w:id'];
|
|
4503
4840
|
if (id !== undefined) {
|
|
4841
|
+
// CT_MarkupRange per ECMA-376 §17.13.5 — preserve
|
|
4842
|
+
// w:displacedByCustomXml on bookmarkEnd when a custom-XML
|
|
4843
|
+
// boundary forced the marker to be displaced.
|
|
4844
|
+
const rawDisplaced = be['@_w:displacedByCustomXml'];
|
|
4845
|
+
const displacedByCustomXml =
|
|
4846
|
+
rawDisplaced === 'next' || rawDisplaced === 'prev' ? rawDisplaced : undefined;
|
|
4504
4847
|
const bookmark = new Bookmark({
|
|
4505
4848
|
name: `_end_${id}`,
|
|
4506
4849
|
id: typeof id === 'number' ? id : parseInt(id, 10),
|
|
4507
4850
|
skipNormalization: true,
|
|
4851
|
+
displacedByCustomXml,
|
|
4508
4852
|
});
|
|
4509
4853
|
result.bookmarkEnds.push(bookmark);
|
|
4510
4854
|
}
|
|
4511
4855
|
}
|
|
4512
4856
|
}
|
|
4513
4857
|
|
|
4514
|
-
// Extract hyperlink attributes
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4858
|
+
// Extract hyperlink attributes. Per ECMA-376 §17.16.22 CT_Hyperlink,
|
|
4859
|
+
// w:anchor / w:tooltip / w:tgtFrame / w:docLocation / r:id are all
|
|
4860
|
+
// ST_String. XMLParser's `parseAttributeValue: true` coerces
|
|
4861
|
+
// purely-numeric strings (e.g., a bookmark name like "12345") to
|
|
4862
|
+
// JS numbers — cast via String(...) so downstream `Hyperlink`
|
|
4863
|
+
// storage and string-method callers see the declared `string`
|
|
4864
|
+
// type contract.
|
|
4865
|
+
const toOptString = (v: unknown): string | undefined =>
|
|
4866
|
+
v === undefined || v === null ? undefined : String(v);
|
|
4867
|
+
const relationshipId = toOptString(hyperlinkObj['@_r:id']);
|
|
4868
|
+
const anchor = toOptString(hyperlinkObj['@_w:anchor']);
|
|
4869
|
+
const tooltip = toOptString(hyperlinkObj['@_w:tooltip']);
|
|
4870
|
+
const tgtFrame = toOptString(hyperlinkObj['@_w:tgtFrame']);
|
|
4871
|
+
// w:history is CT_OnOff per ECMA-376 §17.16.22 — honour every
|
|
4872
|
+
// ST_OnOff literal ("1"/"0"/"true"/"false"/"on"/"off") and every
|
|
4873
|
+
// XMLParser-coerced form (number 0/1, boolean). The Hyperlink
|
|
4874
|
+
// serializer accepts a string, so normalise to the canonical
|
|
4875
|
+
// "1"/"0" form. Without this, `w:history="0"` or `w:history="false"`
|
|
4876
|
+
// coerced to falsy values and the emitter's truthy check dropped
|
|
4877
|
+
// the attribute on round-trip.
|
|
4878
|
+
const rawHistory = hyperlinkObj['@_w:history'];
|
|
4879
|
+
const history =
|
|
4880
|
+
rawHistory === undefined ? undefined : parseOnOffAttribute(rawHistory) ? '1' : '0';
|
|
4881
|
+
const docLocation = toOptString(hyperlinkObj['@_w:docLocation']);
|
|
4521
4882
|
|
|
4522
4883
|
// Parse runs inside the hyperlink
|
|
4523
4884
|
const runs = hyperlinkObj['w:r'];
|
|
@@ -4815,8 +5176,20 @@ export class DocumentParser {
|
|
|
4815
5176
|
}
|
|
4816
5177
|
|
|
4817
5178
|
// Extract field type from instruction (first word)
|
|
4818
|
-
const typeMatch = instruction
|
|
4819
|
-
|
|
5179
|
+
const typeMatch = String(instruction)
|
|
5180
|
+
.trim()
|
|
5181
|
+
.match(/^(\w+)/);
|
|
5182
|
+
const type = (typeMatch?.[1] || 'PAGE') as import('../elements/Field.js').FieldType;
|
|
5183
|
+
|
|
5184
|
+
// CT_SimpleField (§17.16.16) carries two ST_OnOff attributes besides
|
|
5185
|
+
// the required w:instr — w:fldLock (update lock) and w:dirty
|
|
5186
|
+
// (cached-result staleness). Previously neither was parsed, so
|
|
5187
|
+
// Word's "update field" indicator and "lock field" flag were
|
|
5188
|
+
// silently cleared on every load → save round-trip.
|
|
5189
|
+
const fldLockRaw = fieldObj['@_w:fldLock'];
|
|
5190
|
+
const dirtyRaw = fieldObj['@_w:dirty'];
|
|
5191
|
+
const fldLock = fldLockRaw !== undefined ? parseOnOffAttribute(fldLockRaw) : undefined;
|
|
5192
|
+
const dirty = dirtyRaw !== undefined ? parseOnOffAttribute(dirtyRaw) : undefined;
|
|
4820
5193
|
|
|
4821
5194
|
// Parse run formatting from w:rPr if present
|
|
4822
5195
|
let formatting: RunFormatting | undefined;
|
|
@@ -4829,8 +5202,10 @@ export class DocumentParser {
|
|
|
4829
5202
|
// Create field with instruction
|
|
4830
5203
|
const field = Field.create({
|
|
4831
5204
|
type,
|
|
4832
|
-
instruction,
|
|
5205
|
+
instruction: String(instruction),
|
|
4833
5206
|
formatting,
|
|
5207
|
+
fldLock,
|
|
5208
|
+
dirty,
|
|
4834
5209
|
});
|
|
4835
5210
|
|
|
4836
5211
|
return field;
|
|
@@ -4848,15 +5223,25 @@ export class DocumentParser {
|
|
|
4848
5223
|
private parseRunPropertiesFromObject(rPrObj: any, run: Run): void {
|
|
4849
5224
|
if (!rPrObj) return;
|
|
4850
5225
|
|
|
4851
|
-
// Parse character style reference (w:rStyle) per ECMA-376 Part 1
|
|
5226
|
+
// Parse character style reference (w:rStyle) per ECMA-376 Part 1
|
|
5227
|
+
// §17.3.2.36 — `w:val` is ST_String referencing a style ID. Cast
|
|
5228
|
+
// via String(...) so a purely-numeric style ID (e.g., "1") that
|
|
5229
|
+
// XMLParser coerces to the number 1 survives as the string "1",
|
|
5230
|
+
// matching the `characterStyle?: string` field contract on
|
|
5231
|
+
// RunFormatting.
|
|
4852
5232
|
if (rPrObj['w:rStyle']) {
|
|
4853
5233
|
const styleId = rPrObj['w:rStyle']['@_w:val'];
|
|
4854
|
-
if (styleId) {
|
|
4855
|
-
run.setCharacterStyle(styleId);
|
|
5234
|
+
if (styleId !== undefined && styleId !== null && styleId !== '') {
|
|
5235
|
+
run.setCharacterStyle(String(styleId));
|
|
4856
5236
|
}
|
|
4857
5237
|
}
|
|
4858
5238
|
|
|
4859
|
-
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5
|
|
5239
|
+
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5 — CT_Border
|
|
5240
|
+
// §17.18.2 attribute set: val / sz / space / color / themeColor /
|
|
5241
|
+
// themeTint / themeShade / shadow / frame. Previously only the first
|
|
5242
|
+
// four were read, so themed character borders lost their theme linkage
|
|
5243
|
+
// on round-trip. The emitter (Run.generateRunPropertiesXML) handles
|
|
5244
|
+
// all nine since iteration 79.
|
|
4860
5245
|
if (rPrObj['w:bdr']) {
|
|
4861
5246
|
const bdr = rPrObj['w:bdr'];
|
|
4862
5247
|
const border: any = {};
|
|
@@ -4864,6 +5249,20 @@ export class DocumentParser {
|
|
|
4864
5249
|
if (bdr['@_w:sz']) border.size = parseInt(bdr['@_w:sz'], 10);
|
|
4865
5250
|
if (bdr['@_w:color']) border.color = bdr['@_w:color'];
|
|
4866
5251
|
if (bdr['@_w:space']) border.space = parseInt(bdr['@_w:space'], 10);
|
|
5252
|
+
// Per ECMA-376 §17.18.82 CT_Border: themeTint / themeShade are
|
|
5253
|
+
// ST_UcharHexNumber (2-char hex). XMLParser coerces purely-digit
|
|
5254
|
+
// hex strings like "80" / "50" to JS numbers; cast via String(...)
|
|
5255
|
+
// so the declared `string` contract on the model holds for any
|
|
5256
|
+
// downstream code that calls string methods (.toUpperCase(), etc.).
|
|
5257
|
+
if (bdr['@_w:themeColor']) border.themeColor = String(bdr['@_w:themeColor']);
|
|
5258
|
+
if (bdr['@_w:themeTint']) border.themeTint = String(bdr['@_w:themeTint']);
|
|
5259
|
+
if (bdr['@_w:themeShade']) border.themeShade = String(bdr['@_w:themeShade']);
|
|
5260
|
+
if (bdr['@_w:shadow'] !== undefined) {
|
|
5261
|
+
border.shadow = parseOnOffAttribute(String(bdr['@_w:shadow']), true);
|
|
5262
|
+
}
|
|
5263
|
+
if (bdr['@_w:frame'] !== undefined) {
|
|
5264
|
+
border.frame = parseOnOffAttribute(String(bdr['@_w:frame']), true);
|
|
5265
|
+
}
|
|
4867
5266
|
if (Object.keys(border).length > 0) {
|
|
4868
5267
|
run.setBorder(border);
|
|
4869
5268
|
}
|
|
@@ -4883,26 +5282,30 @@ export class DocumentParser {
|
|
|
4883
5282
|
if (val) run.setEmphasis(val);
|
|
4884
5283
|
}
|
|
4885
5284
|
|
|
4886
|
-
//
|
|
4887
|
-
//
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
if (
|
|
4892
|
-
if (
|
|
5285
|
+
// CT_OnOff text effects — presence + w:val both matter. Use `!== undefined`
|
|
5286
|
+
// to detect presence, then parseOoxmlBoolean() for the value, so an explicit
|
|
5287
|
+
// `<w:outline w:val="0"/>` override of a style-inherited true is preserved
|
|
5288
|
+
// (not silently dropped into "inherit"). Applies to all OnOffType rPr flags
|
|
5289
|
+
// per ECMA-376 §17.3.2.
|
|
5290
|
+
if (rPrObj['w:outline'] !== undefined) run.setOutline(parseOoxmlBoolean(rPrObj['w:outline']));
|
|
5291
|
+
if (rPrObj['w:shadow'] !== undefined) run.setShadow(parseOoxmlBoolean(rPrObj['w:shadow']));
|
|
5292
|
+
if (rPrObj['w:emboss'] !== undefined) run.setEmboss(parseOoxmlBoolean(rPrObj['w:emboss']));
|
|
5293
|
+
if (rPrObj['w:imprint'] !== undefined) run.setImprint(parseOoxmlBoolean(rPrObj['w:imprint']));
|
|
5294
|
+
if (rPrObj['w:noProof'] !== undefined) run.setNoProof(parseOoxmlBoolean(rPrObj['w:noProof']));
|
|
4893
5295
|
// snapToGrid: default when absent is true (§17.3.2.34), so explicit val="0" must be preserved
|
|
4894
5296
|
if (rPrObj['w:snapToGrid'] !== undefined) {
|
|
4895
5297
|
run.setSnapToGrid(parseOoxmlBoolean(rPrObj['w:snapToGrid']));
|
|
4896
5298
|
}
|
|
4897
|
-
if (
|
|
4898
|
-
if (
|
|
5299
|
+
if (rPrObj['w:vanish'] !== undefined) run.setVanish(parseOoxmlBoolean(rPrObj['w:vanish']));
|
|
5300
|
+
if (rPrObj['w:specVanish'] !== undefined)
|
|
5301
|
+
run.setSpecVanish(parseOoxmlBoolean(rPrObj['w:specVanish']));
|
|
4899
5302
|
|
|
4900
5303
|
// Boolean properties - use parseOoxmlBoolean helper
|
|
4901
5304
|
// Per ECMA-376: <w:b/> or <w:b w:val="1"/> or <w:b w:val="true"/> means true
|
|
4902
5305
|
// <w:b w:val="0"/> or <w:b w:val="false"/> means false (omit from document)
|
|
4903
5306
|
|
|
4904
5307
|
// Parse RTL text (w:rtl) per ECMA-376 Part 1 §17.3.2.30
|
|
4905
|
-
if (
|
|
5308
|
+
if (rPrObj['w:rtl'] !== undefined) run.setRTL(parseOoxmlBoolean(rPrObj['w:rtl']));
|
|
4906
5309
|
|
|
4907
5310
|
// b, bCs, i, iCs: preserve explicit val="0" to override style-inherited formatting
|
|
4908
5311
|
if (rPrObj['w:b'] !== undefined) run.setBold(parseOoxmlBoolean(rPrObj['w:b']));
|
|
@@ -4919,11 +5322,12 @@ export class DocumentParser {
|
|
|
4919
5322
|
run.setSmallCaps(parseOoxmlBoolean(rPrObj['w:smallCaps']));
|
|
4920
5323
|
if (rPrObj['w:caps'] !== undefined) run.setAllCaps(parseOoxmlBoolean(rPrObj['w:caps']));
|
|
4921
5324
|
|
|
4922
|
-
// Parse complex script flag (w:cs) per ECMA-376 Part 1 §17.3.2.7
|
|
4923
|
-
if (
|
|
5325
|
+
// Parse complex script flag (w:cs) per ECMA-376 Part 1 §17.3.2.7 — CT_OnOff
|
|
5326
|
+
if (rPrObj['w:cs'] !== undefined) run.setComplexScript(parseOoxmlBoolean(rPrObj['w:cs']));
|
|
4924
5327
|
|
|
4925
|
-
// Parse web hidden (w:webHidden) per ECMA-376 Part 1 §17.3.2.44
|
|
4926
|
-
if (
|
|
5328
|
+
// Parse web hidden (w:webHidden) per ECMA-376 Part 1 §17.3.2.44 — CT_OnOff
|
|
5329
|
+
if (rPrObj['w:webHidden'] !== undefined)
|
|
5330
|
+
run.setWebHidden(parseOoxmlBoolean(rPrObj['w:webHidden']));
|
|
4927
5331
|
|
|
4928
5332
|
if (rPrObj['w:u']) {
|
|
4929
5333
|
// XMLParser adds @_ prefix to attributes
|
|
@@ -4943,28 +5347,37 @@ export class DocumentParser {
|
|
|
4943
5347
|
);
|
|
4944
5348
|
}
|
|
4945
5349
|
|
|
4946
|
-
// Parse character spacing (w:spacing) per ECMA-376 Part 1 §17.3.2.
|
|
5350
|
+
// Parse character spacing (w:spacing) per ECMA-376 Part 1 §17.3.2.35.
|
|
5351
|
+
// ST_SignedTwipsMeasure — 0 and negative values are valid (default /
|
|
5352
|
+
// tighter spacing). XMLParser.parseAttributeValue coerces "0" to number 0,
|
|
5353
|
+
// which is falsy — so the previous `if (val)` truthy check silently dropped
|
|
5354
|
+
// explicit zero / baseline-reset formatting on every run that used it.
|
|
5355
|
+
// Matches the rPrChange parser below which already uses `!== undefined`.
|
|
4947
5356
|
if (rPrObj['w:spacing']) {
|
|
4948
5357
|
const val = rPrObj['w:spacing']['@_w:val'];
|
|
4949
|
-
if (val) run.setCharacterSpacing(parseInt(val, 10));
|
|
5358
|
+
if (val !== undefined) run.setCharacterSpacing(parseInt(String(val), 10));
|
|
4950
5359
|
}
|
|
4951
5360
|
|
|
4952
|
-
// Parse horizontal scaling (w:w) per ECMA-376 Part 1 §17.3.2.43
|
|
5361
|
+
// Parse horizontal scaling (w:w) per ECMA-376 Part 1 §17.3.2.43.
|
|
5362
|
+
// ST_TextScale — min 1 per schema, so value 0 is not spec-valid; keep
|
|
5363
|
+
// truthy check as a mild sanity guard against malformed sources.
|
|
4953
5364
|
if (rPrObj['w:w']) {
|
|
4954
5365
|
const val = rPrObj['w:w']['@_w:val'];
|
|
4955
|
-
if (val) run.setScaling(parseInt(val, 10));
|
|
5366
|
+
if (val) run.setScaling(parseInt(String(val), 10));
|
|
4956
5367
|
}
|
|
4957
5368
|
|
|
4958
|
-
// Parse vertical position (w:position) per ECMA-376 Part 1 §17.3.2.31
|
|
5369
|
+
// Parse vertical position (w:position) per ECMA-376 Part 1 §17.3.2.31.
|
|
5370
|
+
// ST_SignedHpsMeasure — 0 = baseline (default / explicit reset).
|
|
4959
5371
|
if (rPrObj['w:position']) {
|
|
4960
5372
|
const val = rPrObj['w:position']['@_w:val'];
|
|
4961
|
-
if (val) run.setPosition(parseInt(val, 10));
|
|
5373
|
+
if (val !== undefined) run.setPosition(parseInt(String(val), 10));
|
|
4962
5374
|
}
|
|
4963
5375
|
|
|
4964
|
-
// Parse kerning (w:kern) per ECMA-376 Part 1 §17.3.2.20
|
|
5376
|
+
// Parse kerning (w:kern) per ECMA-376 Part 1 §17.3.2.20.
|
|
5377
|
+
// ST_HpsMeasure — 0 means "kern at every size" (no minimum threshold).
|
|
4965
5378
|
if (rPrObj['w:kern']) {
|
|
4966
5379
|
const val = rPrObj['w:kern']['@_w:val'];
|
|
4967
|
-
if (val) run.setKerning(parseInt(val, 10));
|
|
5380
|
+
if (val !== undefined) run.setKerning(parseInt(String(val), 10));
|
|
4968
5381
|
}
|
|
4969
5382
|
|
|
4970
5383
|
// Parse language (w:lang) per ECMA-376 Part 1 §17.3.2.20 (CT_Language)
|
|
@@ -4984,14 +5397,27 @@ export class DocumentParser {
|
|
|
4984
5397
|
}
|
|
4985
5398
|
}
|
|
4986
5399
|
|
|
4987
|
-
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5400
|
+
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5401
|
+
// §17.3.2.10 CT_EastAsianLayout. `w:vert` / `w:vertCompress` /
|
|
5402
|
+
// `w:combine` are ST_OnOff attributes — route through
|
|
5403
|
+
// parseOnOffAttribute so every literal ("1"/"0"/"true"/"false"/
|
|
5404
|
+
// "on"/"off") resolves correctly. The previous truthy gate both
|
|
5405
|
+
// dropped explicit false (`w:vert="0"` → coerced 0 → undefined) AND
|
|
5406
|
+
// wrongly marked `w:vert="off"` as true (non-empty string is truthy
|
|
5407
|
+
// without parsing).
|
|
4988
5408
|
if (rPrObj['w:eastAsianLayout']) {
|
|
4989
5409
|
const layoutObj = rPrObj['w:eastAsianLayout'];
|
|
4990
5410
|
const layout: any = {};
|
|
4991
5411
|
if (layoutObj['@_w:id'] !== undefined) layout.id = Number(layoutObj['@_w:id']);
|
|
4992
|
-
if (layoutObj['@_w:vert']
|
|
4993
|
-
|
|
4994
|
-
|
|
5412
|
+
if (layoutObj['@_w:vert'] !== undefined) {
|
|
5413
|
+
layout.vert = parseOnOffAttribute(String(layoutObj['@_w:vert']), true);
|
|
5414
|
+
}
|
|
5415
|
+
if (layoutObj['@_w:vertCompress'] !== undefined) {
|
|
5416
|
+
layout.vertCompress = parseOnOffAttribute(String(layoutObj['@_w:vertCompress']), true);
|
|
5417
|
+
}
|
|
5418
|
+
if (layoutObj['@_w:combine'] !== undefined) {
|
|
5419
|
+
layout.combine = parseOnOffAttribute(String(layoutObj['@_w:combine']), true);
|
|
5420
|
+
}
|
|
4995
5421
|
if (layoutObj['@_w:combineBrackets'])
|
|
4996
5422
|
layout.combineBrackets = layoutObj['@_w:combineBrackets'];
|
|
4997
5423
|
|
|
@@ -5021,17 +5447,23 @@ export class DocumentParser {
|
|
|
5021
5447
|
|
|
5022
5448
|
if (rPrObj['w:rFonts']) {
|
|
5023
5449
|
const rFonts = rPrObj['w:rFonts'];
|
|
5024
|
-
|
|
5450
|
+
// Per ECMA-376 §17.3.2.26 CT_Fonts, all four literal-font
|
|
5451
|
+
// attributes (ascii/hAnsi/eastAsia/cs) are ST_String. XMLParser
|
|
5452
|
+
// coerces purely-numeric font names ("2010", etc.) to JS
|
|
5453
|
+
// numbers; cast through String() so RunFormatting's
|
|
5454
|
+
// declared-string font fields keep their type contract.
|
|
5455
|
+
if (rFonts['@_w:ascii'] !== undefined) run.setFont(String(rFonts['@_w:ascii']));
|
|
5025
5456
|
// Parse additional font variants per ECMA-376 Part 1 §17.3.2.26
|
|
5026
|
-
if (rFonts['@_w:hAnsi']) run.setFontHAnsi(rFonts['@_w:hAnsi']);
|
|
5027
|
-
if (rFonts['@_w:eastAsia']) run.setFontEastAsia(rFonts['@_w:eastAsia']);
|
|
5028
|
-
if (rFonts['@_w:cs']) run.setFontCs(rFonts['@_w:cs']);
|
|
5029
|
-
if (rFonts['@_w:hint']) run.setFontHint(rFonts['@_w:hint']);
|
|
5457
|
+
if (rFonts['@_w:hAnsi'] !== undefined) run.setFontHAnsi(String(rFonts['@_w:hAnsi']));
|
|
5458
|
+
if (rFonts['@_w:eastAsia'] !== undefined) run.setFontEastAsia(String(rFonts['@_w:eastAsia']));
|
|
5459
|
+
if (rFonts['@_w:cs'] !== undefined) run.setFontCs(String(rFonts['@_w:cs']));
|
|
5460
|
+
if (rFonts['@_w:hint']) run.setFontHint(String(rFonts['@_w:hint']));
|
|
5030
5461
|
// Parse theme font references per ECMA-376 Part 1 §17.3.2.26
|
|
5031
|
-
if (rFonts['@_w:asciiTheme']) run.setFontAsciiTheme(rFonts['@_w:asciiTheme']);
|
|
5032
|
-
if (rFonts['@_w:hAnsiTheme']) run.setFontHAnsiTheme(rFonts['@_w:hAnsiTheme']);
|
|
5033
|
-
if (rFonts['@_w:eastAsiaTheme'])
|
|
5034
|
-
|
|
5462
|
+
if (rFonts['@_w:asciiTheme']) run.setFontAsciiTheme(String(rFonts['@_w:asciiTheme']));
|
|
5463
|
+
if (rFonts['@_w:hAnsiTheme']) run.setFontHAnsiTheme(String(rFonts['@_w:hAnsiTheme']));
|
|
5464
|
+
if (rFonts['@_w:eastAsiaTheme'])
|
|
5465
|
+
run.setFontEastAsiaTheme(String(rFonts['@_w:eastAsiaTheme']));
|
|
5466
|
+
if (rFonts['@_w:cstheme']) run.setFontCsTheme(String(rFonts['@_w:cstheme']));
|
|
5035
5467
|
}
|
|
5036
5468
|
|
|
5037
5469
|
if (rPrObj['w:sz']) {
|
|
@@ -5099,7 +5531,7 @@ export class DocumentParser {
|
|
|
5099
5531
|
// This records what the run formatting was BEFORE a change was made
|
|
5100
5532
|
if (rPrObj['w:rPrChange']) {
|
|
5101
5533
|
const changeObj = rPrObj['w:rPrChange'];
|
|
5102
|
-
const propChange: import('../elements/PropertyChangeTypes').RunPropertyChange = {
|
|
5534
|
+
const propChange: import('../elements/PropertyChangeTypes.js').RunPropertyChange = {
|
|
5103
5535
|
id: changeObj['@_w:id'] !== undefined ? parseInt(String(changeObj['@_w:id']), 10) : 0,
|
|
5104
5536
|
author: changeObj['@_w:author'] ? String(changeObj['@_w:author']) : '',
|
|
5105
5537
|
date: changeObj['@_w:date'] ? new Date(String(changeObj['@_w:date'])) : new Date(),
|
|
@@ -5109,7 +5541,7 @@ export class DocumentParser {
|
|
|
5109
5541
|
// Parse previous run properties from child w:rPr element
|
|
5110
5542
|
if (changeObj['w:rPr']) {
|
|
5111
5543
|
const prevRPr = changeObj['w:rPr'];
|
|
5112
|
-
const prevProps: Partial<import('../elements/Run').RunFormatting> = {};
|
|
5544
|
+
const prevProps: Partial<import('../elements/Run.js').RunFormatting> = {};
|
|
5113
5545
|
|
|
5114
5546
|
// Parse previous bold
|
|
5115
5547
|
if (prevRPr['w:b']) {
|
|
@@ -5121,10 +5553,22 @@ export class DocumentParser {
|
|
|
5121
5553
|
prevProps.italic = parseOoxmlBoolean(prevRPr['w:i']);
|
|
5122
5554
|
}
|
|
5123
5555
|
|
|
5124
|
-
// Parse previous underline
|
|
5556
|
+
// Parse previous underline — CT_Underline per §17.3.2.40 has `val`
|
|
5557
|
+
// plus color / themeColor / themeTint / themeShade. Main rPr parser
|
|
5558
|
+
// reads all of them; rPrChange previously only read `val`, so
|
|
5559
|
+
// underline color metadata on tracked "previous" state was dropped.
|
|
5125
5560
|
if (prevRPr['w:u']) {
|
|
5126
|
-
const
|
|
5561
|
+
const uObj = prevRPr['w:u'];
|
|
5562
|
+
const uVal = uObj['@_w:val'];
|
|
5127
5563
|
prevProps.underline = uVal || true;
|
|
5564
|
+
if (uObj['@_w:color']) prevProps.underlineColor = uObj['@_w:color'];
|
|
5565
|
+
if (uObj['@_w:themeColor']) prevProps.underlineThemeColor = uObj['@_w:themeColor'];
|
|
5566
|
+
if (uObj['@_w:themeTint'] !== undefined) {
|
|
5567
|
+
prevProps.underlineThemeTint = parseInt(String(uObj['@_w:themeTint']), 16);
|
|
5568
|
+
}
|
|
5569
|
+
if (uObj['@_w:themeShade'] !== undefined) {
|
|
5570
|
+
prevProps.underlineThemeShade = parseInt(String(uObj['@_w:themeShade']), 16);
|
|
5571
|
+
}
|
|
5128
5572
|
}
|
|
5129
5573
|
|
|
5130
5574
|
// Parse previous strikethrough
|
|
@@ -5133,13 +5577,28 @@ export class DocumentParser {
|
|
|
5133
5577
|
}
|
|
5134
5578
|
|
|
5135
5579
|
// Parse previous font (all w:rFonts attributes per ECMA-376 Part 1 §17.3.2.26)
|
|
5580
|
+
// including theme font references (asciiTheme/hAnsiTheme/eastAsiaTheme/
|
|
5581
|
+
// cstheme). Previously only the literal-font attributes were read, so
|
|
5582
|
+
// rPrChange tracked history of theme-font changes lost the theme linkage
|
|
5583
|
+
// on round-trip — a paragraph whose "previous" font was a theme
|
|
5584
|
+
// reference (e.g. w:asciiTheme="minorHAnsi") silently dropped it.
|
|
5136
5585
|
if (prevRPr['w:rFonts']) {
|
|
5137
5586
|
const rFonts = prevRPr['w:rFonts'];
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
if (rFonts['@_w:
|
|
5587
|
+
// Mirror the main-path String() casts on rPrChange
|
|
5588
|
+
// previous-font reads — ECMA-376 §17.3.2.26 CT_Fonts declares
|
|
5589
|
+
// ascii/hAnsi/eastAsia/cs as ST_String, so purely-numeric
|
|
5590
|
+
// font names must survive round-trip as strings here too.
|
|
5591
|
+
if (rFonts['@_w:ascii'] !== undefined) prevProps.font = String(rFonts['@_w:ascii']);
|
|
5592
|
+
if (rFonts['@_w:hAnsi'] !== undefined) prevProps.fontHAnsi = String(rFonts['@_w:hAnsi']);
|
|
5593
|
+
if (rFonts['@_w:eastAsia'] !== undefined)
|
|
5594
|
+
prevProps.fontEastAsia = String(rFonts['@_w:eastAsia']);
|
|
5595
|
+
if (rFonts['@_w:cs'] !== undefined) prevProps.fontCs = String(rFonts['@_w:cs']);
|
|
5596
|
+
if (rFonts['@_w:hint']) prevProps.fontHint = String(rFonts['@_w:hint']);
|
|
5597
|
+
if (rFonts['@_w:asciiTheme']) prevProps.fontAsciiTheme = String(rFonts['@_w:asciiTheme']);
|
|
5598
|
+
if (rFonts['@_w:hAnsiTheme']) prevProps.fontHAnsiTheme = String(rFonts['@_w:hAnsiTheme']);
|
|
5599
|
+
if (rFonts['@_w:eastAsiaTheme'])
|
|
5600
|
+
prevProps.fontEastAsiaTheme = String(rFonts['@_w:eastAsiaTheme']);
|
|
5601
|
+
if (rFonts['@_w:cstheme']) prevProps.fontCsTheme = String(rFonts['@_w:cstheme']);
|
|
5143
5602
|
}
|
|
5144
5603
|
|
|
5145
5604
|
// Parse previous size (half-points to points)
|
|
@@ -5337,17 +5796,33 @@ export class DocumentParser {
|
|
|
5337
5796
|
}
|
|
5338
5797
|
}
|
|
5339
5798
|
|
|
5340
|
-
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.
|
|
5341
|
-
//
|
|
5799
|
+
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5 — full
|
|
5800
|
+
// CT_Border attribute set for rPrChange previous-properties fidelity.
|
|
5342
5801
|
if (prevRPr['w:bdr']) {
|
|
5343
5802
|
const bdrObj = prevRPr['w:bdr'];
|
|
5344
|
-
|
|
5345
|
-
style: bdrObj['@_w:val'] as import('../elements/Run').TextBorderStyle,
|
|
5803
|
+
const tb: import('../elements/Run.js').TextBorder = {
|
|
5804
|
+
style: bdrObj['@_w:val'] as import('../elements/Run.js').TextBorderStyle,
|
|
5346
5805
|
size: bdrObj['@_w:sz'] !== undefined ? safeParseInt(bdrObj['@_w:sz']) : undefined,
|
|
5347
5806
|
space:
|
|
5348
5807
|
bdrObj['@_w:space'] !== undefined ? safeParseInt(bdrObj['@_w:space']) : undefined,
|
|
5349
5808
|
color: bdrObj['@_w:color'],
|
|
5350
5809
|
};
|
|
5810
|
+
// String(...) cast: XMLParser coerces "80"/"50" hex to numbers
|
|
5811
|
+
// — preserve the declared string contract on the model.
|
|
5812
|
+
if (bdrObj['@_w:themeColor']) {
|
|
5813
|
+
tb.themeColor = String(
|
|
5814
|
+
bdrObj['@_w:themeColor']
|
|
5815
|
+
) as import('../elements/Run.js').ThemeColorValue;
|
|
5816
|
+
}
|
|
5817
|
+
if (bdrObj['@_w:themeTint']) tb.themeTint = String(bdrObj['@_w:themeTint']);
|
|
5818
|
+
if (bdrObj['@_w:themeShade']) tb.themeShade = String(bdrObj['@_w:themeShade']);
|
|
5819
|
+
if (bdrObj['@_w:shadow'] !== undefined) {
|
|
5820
|
+
tb.shadow = parseOnOffAttribute(String(bdrObj['@_w:shadow']), true);
|
|
5821
|
+
}
|
|
5822
|
+
if (bdrObj['@_w:frame'] !== undefined) {
|
|
5823
|
+
tb.frame = parseOnOffAttribute(String(bdrObj['@_w:frame']), true);
|
|
5824
|
+
}
|
|
5825
|
+
prevProps.border = tb;
|
|
5351
5826
|
}
|
|
5352
5827
|
|
|
5353
5828
|
// Parse character shading (w:shd) per ECMA-376 Part 1 §17.3.2.32
|
|
@@ -5358,24 +5833,54 @@ export class DocumentParser {
|
|
|
5358
5833
|
}
|
|
5359
5834
|
}
|
|
5360
5835
|
|
|
5361
|
-
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5836
|
+
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5837
|
+
// §17.3.2.10 CT_EastAsianLayout. Parity-fix with the main rPr
|
|
5838
|
+
// parser: route the three ST_OnOff attributes through
|
|
5839
|
+
// parseOnOffAttribute so every literal — including "0"
|
|
5840
|
+
// (explicit-false override) and "off" — resolves correctly. The
|
|
5841
|
+
// previous truthy gate both dropped explicit-false (XMLParser
|
|
5842
|
+
// coerces "0" to number 0 → falsy → undefined) and wrongly
|
|
5843
|
+
// coerced "off" to true.
|
|
5362
5844
|
if (prevRPr['w:eastAsianLayout']) {
|
|
5363
5845
|
const eaObj = prevRPr['w:eastAsianLayout'];
|
|
5364
5846
|
prevProps.eastAsianLayout = {
|
|
5365
5847
|
id: eaObj['@_w:id'] !== undefined ? safeParseInt(eaObj['@_w:id']) : undefined,
|
|
5366
|
-
combine:
|
|
5367
|
-
|
|
5368
|
-
|
|
5848
|
+
combine:
|
|
5849
|
+
eaObj['@_w:combine'] !== undefined
|
|
5850
|
+
? parseOnOffAttribute(String(eaObj['@_w:combine']), true)
|
|
5851
|
+
: undefined,
|
|
5369
5852
|
combineBrackets: eaObj['@_w:combineBrackets'],
|
|
5370
|
-
vert:
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
: undefined
|
|
5853
|
+
vert:
|
|
5854
|
+
eaObj['@_w:vert'] !== undefined
|
|
5855
|
+
? parseOnOffAttribute(String(eaObj['@_w:vert']), true)
|
|
5856
|
+
: undefined,
|
|
5857
|
+
vertCompress:
|
|
5858
|
+
eaObj['@_w:vertCompress'] !== undefined
|
|
5859
|
+
? parseOnOffAttribute(String(eaObj['@_w:vertCompress']), true)
|
|
5860
|
+
: undefined,
|
|
5376
5861
|
};
|
|
5377
5862
|
}
|
|
5378
5863
|
|
|
5864
|
+
// Collect w14: namespace elements from the previous rPr for
|
|
5865
|
+
// passthrough (Word 2010+ text effects: w14:textOutline,
|
|
5866
|
+
// w14:shadow, w14:reflection, w14:glow, w14:ligatures,
|
|
5867
|
+
// w14:numForm, w14:numSpacing, w14:cntxtAlts, w14:stylisticSets).
|
|
5868
|
+
// The main rPr parser already collects these and the rPrChange
|
|
5869
|
+
// emitter (via generateRunPropertiesXML line 3130) re-emits
|
|
5870
|
+
// prevProps.rawW14Properties, but the rPrChange parser never
|
|
5871
|
+
// captured them — so tracked changes to any w14 text effect
|
|
5872
|
+
// silently lost the previous state on load → save.
|
|
5873
|
+
const prevRawW14: string[] = [];
|
|
5874
|
+
for (const key of Object.keys(prevRPr)) {
|
|
5875
|
+
if (key.startsWith('w14:')) {
|
|
5876
|
+
const rawXml = this.objectToXml({ [key]: prevRPr[key] });
|
|
5877
|
+
if (rawXml) prevRawW14.push(rawXml);
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
if (prevRawW14.length > 0) {
|
|
5881
|
+
(prevProps as { rawW14Properties?: string[] }).rawW14Properties = prevRawW14;
|
|
5882
|
+
}
|
|
5883
|
+
|
|
5379
5884
|
propChange.previousProperties = prevProps;
|
|
5380
5885
|
}
|
|
5381
5886
|
|
|
@@ -5446,8 +5951,15 @@ export class DocumentParser {
|
|
|
5446
5951
|
let docPrId = 1;
|
|
5447
5952
|
let hidden = false;
|
|
5448
5953
|
if (docPrObj) {
|
|
5449
|
-
name
|
|
5450
|
-
|
|
5954
|
+
// wp:docPr @name and @descr are xsd:string per ECMA-376
|
|
5955
|
+
// §20.4.2.5 CT_NonVisualDrawingProps. XMLParser coerces
|
|
5956
|
+
// purely-numeric values ("2010") to JS numbers; cast through
|
|
5957
|
+
// String() so Image.name / Image.description keep the declared
|
|
5958
|
+
// string contract (matches the @_title handling below).
|
|
5959
|
+
const rawName = docPrObj['@_name'];
|
|
5960
|
+
name = rawName !== undefined && rawName !== null ? String(rawName) : 'image';
|
|
5961
|
+
const rawDescr = docPrObj['@_descr'];
|
|
5962
|
+
description = rawDescr !== undefined && rawDescr !== null ? String(rawDescr) : 'Image';
|
|
5451
5963
|
if (docPrObj['@_title']) {
|
|
5452
5964
|
title = String(docPrObj['@_title']);
|
|
5453
5965
|
}
|
|
@@ -5776,7 +6288,7 @@ export class DocumentParser {
|
|
|
5776
6288
|
);
|
|
5777
6289
|
|
|
5778
6290
|
// Create image from buffer with all properties
|
|
5779
|
-
const { Image: ImageClass } = await import('../elements/Image');
|
|
6291
|
+
const { Image: ImageClass } = await import('../elements/Image.js');
|
|
5780
6292
|
const image = await ImageClass.create({
|
|
5781
6293
|
source: imageData,
|
|
5782
6294
|
width,
|
|
@@ -6137,12 +6649,36 @@ export class DocumentParser {
|
|
|
6137
6649
|
*/
|
|
6138
6650
|
private parseBorderElement(borderObj: any): TableBorder | undefined {
|
|
6139
6651
|
if (!borderObj) return undefined;
|
|
6652
|
+
// Extract the full CT_Border attribute set per ECMA-376 §17.18.2:
|
|
6653
|
+
// val (required) / sz / space / color / themeColor / themeTint /
|
|
6654
|
+
// themeShade / shadow / frame. Previously the last five were silently
|
|
6655
|
+
// dropped on load, so themed borders and shadow/frame flags were lost
|
|
6656
|
+
// on every round-trip.
|
|
6140
6657
|
const border: TableBorder = {
|
|
6141
6658
|
style: (borderObj['@_w:val'] || 'single') as TableBorder['style'],
|
|
6142
6659
|
};
|
|
6143
6660
|
if (borderObj['@_w:sz'] !== undefined) border.size = safeParseInt(borderObj['@_w:sz']);
|
|
6144
6661
|
if (borderObj['@_w:space'] !== undefined) border.space = safeParseInt(borderObj['@_w:space']);
|
|
6145
|
-
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
6662
|
+
if (borderObj['@_w:color']) border.color = String(borderObj['@_w:color']);
|
|
6663
|
+
// String(...) cast: themeTint / themeShade are ST_UcharHexNumber
|
|
6664
|
+
// (2-char hex) declared as `string` on the model. XMLParser coerces
|
|
6665
|
+
// purely-digit hex like "80"/"50" to numbers — cast to preserve
|
|
6666
|
+
// the type contract.
|
|
6667
|
+
if (borderObj['@_w:themeColor']) {
|
|
6668
|
+
(border as any).themeColor = String(borderObj['@_w:themeColor']);
|
|
6669
|
+
}
|
|
6670
|
+
if (borderObj['@_w:themeTint']) {
|
|
6671
|
+
(border as any).themeTint = String(borderObj['@_w:themeTint']);
|
|
6672
|
+
}
|
|
6673
|
+
if (borderObj['@_w:themeShade']) {
|
|
6674
|
+
(border as any).themeShade = String(borderObj['@_w:themeShade']);
|
|
6675
|
+
}
|
|
6676
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
6677
|
+
(border as any).shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
6678
|
+
}
|
|
6679
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
6680
|
+
(border as any).frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
6681
|
+
}
|
|
6146
6682
|
return border;
|
|
6147
6683
|
}
|
|
6148
6684
|
|
|
@@ -6188,8 +6724,10 @@ export class DocumentParser {
|
|
|
6188
6724
|
const gridChange = TableGridChange.create(
|
|
6189
6725
|
safeParseInt(changeObj['@_w:id'], 0),
|
|
6190
6726
|
prevWidths,
|
|
6191
|
-
changeObj['@_w:author']
|
|
6192
|
-
changeObj['@_w:date']
|
|
6727
|
+
changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : undefined,
|
|
6728
|
+
changeObj['@_w:date'] !== undefined
|
|
6729
|
+
? new Date(String(changeObj['@_w:date']))
|
|
6730
|
+
: undefined
|
|
6193
6731
|
);
|
|
6194
6732
|
table.setTblGridChange(gridChange);
|
|
6195
6733
|
}
|
|
@@ -6284,53 +6822,90 @@ export class DocumentParser {
|
|
|
6284
6822
|
private parseTablePropertiesFromObject(tblPrObj: any, table: Table): void {
|
|
6285
6823
|
if (!tblPrObj) return;
|
|
6286
6824
|
|
|
6287
|
-
// Parse table style reference (w:tblStyle)
|
|
6825
|
+
// Parse table style reference (w:tblStyle) per ECMA-376 §17.7.4.62.
|
|
6826
|
+
// w:val is ST_String — cast through String() so purely-numeric
|
|
6827
|
+
// custom style IDs ("2025", "1", …) don't leak as JS numbers
|
|
6828
|
+
// through XMLParser's parseAttributeValue coercion into the
|
|
6829
|
+
// string-typed `formatting.style` field.
|
|
6288
6830
|
if (tblPrObj['w:tblStyle']) {
|
|
6289
6831
|
const styleId = tblPrObj['w:tblStyle']['@_w:val'];
|
|
6290
|
-
if (styleId) {
|
|
6291
|
-
table.setStyle(styleId);
|
|
6292
|
-
}
|
|
6293
|
-
}
|
|
6294
|
-
|
|
6295
|
-
// Parse table look flags (w:tblLook) -
|
|
6296
|
-
//
|
|
6832
|
+
if (styleId !== undefined && styleId !== null && styleId !== '') {
|
|
6833
|
+
table.setStyle(String(styleId));
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
|
|
6837
|
+
// Parse table look flags (w:tblLook) per ECMA-376 §17.4.57 — supports both
|
|
6838
|
+
// hex-string format (w:val="04A0") AND individual ST_OnOff attributes
|
|
6839
|
+
// (firstRow/lastRow/firstColumn/lastColumn/noHBand/noVBand).
|
|
6840
|
+
//
|
|
6841
|
+
// XMLParser.parseToObject runs with `parseAttributeValue: true` by default,
|
|
6842
|
+
// so `"1"` coerces to the number `1` and `"true"` to the boolean `true`.
|
|
6843
|
+
// The previous `=== '1'` strict-string comparison missed both coerced
|
|
6844
|
+
// forms, silently flipping every individually-set flag to OFF and
|
|
6845
|
+
// producing `tblLook="0000"` for every Word-authored document whose
|
|
6846
|
+
// tblLook used the expanded attribute syntax. Route each attribute
|
|
6847
|
+
// through `parseOoxmlBoolean` (attribute form) so string/number/boolean
|
|
6848
|
+
// representations all resolve correctly.
|
|
6297
6849
|
if (tblPrObj['w:tblLook']) {
|
|
6298
6850
|
const look = tblPrObj['w:tblLook'];
|
|
6299
6851
|
if (look['@_w:val']) {
|
|
6300
6852
|
// Hex string format
|
|
6301
6853
|
table.setTblLook(look['@_w:val']);
|
|
6302
6854
|
} else {
|
|
6303
|
-
// Individual attribute format
|
|
6304
|
-
//
|
|
6855
|
+
// Individual attribute format — construct hex value.
|
|
6856
|
+
// Bits per §17.4.57: firstRow=0x0020, lastRow=0x0040, firstCol=0x0080,
|
|
6857
|
+
// lastCol=0x0100, noHBand=0x0200, noVBand=0x0400.
|
|
6858
|
+
const attrIsOn = (name: string): boolean => {
|
|
6859
|
+
const v = look[name];
|
|
6860
|
+
if (v === undefined) return false;
|
|
6861
|
+
// parseOoxmlBoolean accepts the value wrapped as `{'@_w:val': v}` —
|
|
6862
|
+
// handles string "1"/"0"/"true"/"false"/"on"/"off", number 1/0,
|
|
6863
|
+
// and boolean true/false uniformly.
|
|
6864
|
+
return parseOoxmlBoolean({ '@_w:val': v });
|
|
6865
|
+
};
|
|
6305
6866
|
let value = 0;
|
|
6306
|
-
if (
|
|
6307
|
-
if (
|
|
6308
|
-
if (
|
|
6309
|
-
if (
|
|
6310
|
-
if (
|
|
6311
|
-
if (
|
|
6867
|
+
if (attrIsOn('@_w:firstRow')) value |= 0x0020;
|
|
6868
|
+
if (attrIsOn('@_w:lastRow')) value |= 0x0040;
|
|
6869
|
+
if (attrIsOn('@_w:firstColumn')) value |= 0x0080;
|
|
6870
|
+
if (attrIsOn('@_w:lastColumn')) value |= 0x0100;
|
|
6871
|
+
if (attrIsOn('@_w:noHBand')) value |= 0x0200;
|
|
6872
|
+
if (attrIsOn('@_w:noVBand')) value |= 0x0400;
|
|
6312
6873
|
table.setTblLook(value.toString(16).toUpperCase().padStart(4, '0'));
|
|
6313
6874
|
}
|
|
6314
6875
|
}
|
|
6315
6876
|
|
|
6316
|
-
// Parse table positioning (tblpPr) - for floating tables
|
|
6877
|
+
// Parse table positioning (tblpPr) - for floating tables.
|
|
6878
|
+
// Per ECMA-376 §17.4.52 CT_TblPPr, the six numeric attributes
|
|
6879
|
+
// (tblpX/tblpY/leftFromText/rightFromText/topFromText/bottomFromText)
|
|
6880
|
+
// are ST_SignedTwipsMeasure / ST_TwipsMeasure where 0 is a valid
|
|
6881
|
+
// value (e.g. float table anchored exactly at the anchor point).
|
|
6882
|
+
// XMLParser coerces "0" to the number 0 (falsy), so the previous
|
|
6883
|
+
// truthy gate silently dropped zero-offset positions. Table's
|
|
6884
|
+
// emitter uses `!== undefined`, so the asymmetry lost zeroes on
|
|
6885
|
+
// round-trip. Route each numeric read through isExplicitlySet +
|
|
6886
|
+
// safeParseInt.
|
|
6317
6887
|
if (tblPrObj['w:tblpPr']) {
|
|
6318
6888
|
const tblpPr = tblPrObj['w:tblpPr'];
|
|
6319
6889
|
const position: any = {};
|
|
6320
6890
|
|
|
6321
|
-
if (tblpPr['@_w:tblpX']) position.x =
|
|
6322
|
-
if (tblpPr['@_w:tblpY']) position.y =
|
|
6891
|
+
if (isExplicitlySet(tblpPr['@_w:tblpX'])) position.x = safeParseInt(tblpPr['@_w:tblpX']);
|
|
6892
|
+
if (isExplicitlySet(tblpPr['@_w:tblpY'])) position.y = safeParseInt(tblpPr['@_w:tblpY']);
|
|
6323
6893
|
if (tblpPr['@_w:horzAnchor']) position.horizontalAnchor = tblpPr['@_w:horzAnchor'];
|
|
6324
6894
|
if (tblpPr['@_w:vertAnchor']) position.verticalAnchor = tblpPr['@_w:vertAnchor'];
|
|
6325
6895
|
if (tblpPr['@_w:tblpXSpec']) position.horizontalAlignment = tblpPr['@_w:tblpXSpec'];
|
|
6326
6896
|
if (tblpPr['@_w:tblpYSpec']) position.verticalAlignment = tblpPr['@_w:tblpYSpec'];
|
|
6327
|
-
if (tblpPr['@_w:leftFromText'])
|
|
6328
|
-
position.leftFromText =
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6897
|
+
if (isExplicitlySet(tblpPr['@_w:leftFromText'])) {
|
|
6898
|
+
position.leftFromText = safeParseInt(tblpPr['@_w:leftFromText']);
|
|
6899
|
+
}
|
|
6900
|
+
if (isExplicitlySet(tblpPr['@_w:rightFromText'])) {
|
|
6901
|
+
position.rightFromText = safeParseInt(tblpPr['@_w:rightFromText']);
|
|
6902
|
+
}
|
|
6903
|
+
if (isExplicitlySet(tblpPr['@_w:topFromText'])) {
|
|
6904
|
+
position.topFromText = safeParseInt(tblpPr['@_w:topFromText']);
|
|
6905
|
+
}
|
|
6906
|
+
if (isExplicitlySet(tblpPr['@_w:bottomFromText'])) {
|
|
6907
|
+
position.bottomFromText = safeParseInt(tblpPr['@_w:bottomFromText']);
|
|
6908
|
+
}
|
|
6334
6909
|
|
|
6335
6910
|
if (Object.keys(position).length > 0) {
|
|
6336
6911
|
table.setPosition(position);
|
|
@@ -6343,9 +6918,9 @@ export class DocumentParser {
|
|
|
6343
6918
|
table.setOverlap(val === 'overlap');
|
|
6344
6919
|
}
|
|
6345
6920
|
|
|
6346
|
-
// Parse bidirectional visual layout
|
|
6921
|
+
// Parse bidirectional visual layout — CT_OnOff, honour w:val per ECMA-376 §17.17.4
|
|
6347
6922
|
if (tblPrObj['w:bidiVisual']) {
|
|
6348
|
-
table.setBidiVisual(
|
|
6923
|
+
table.setBidiVisual(parseOoxmlBoolean(tblPrObj['w:bidiVisual']));
|
|
6349
6924
|
}
|
|
6350
6925
|
|
|
6351
6926
|
// Parse table width — always set when w:tblW is present, including w:w="0" w:type="auto"
|
|
@@ -6358,24 +6933,37 @@ export class DocumentParser {
|
|
|
6358
6933
|
table.setWidthType(widthType);
|
|
6359
6934
|
}
|
|
6360
6935
|
|
|
6361
|
-
// Parse table caption
|
|
6936
|
+
// Parse table caption — ST_String per §17.4.62. Cast through
|
|
6937
|
+
// String() so a purely-numeric caption ("42") is preserved as a
|
|
6938
|
+
// string in `formatting.caption` rather than a JS number.
|
|
6362
6939
|
if (tblPrObj['w:tblCaption']) {
|
|
6363
6940
|
const caption = tblPrObj['w:tblCaption']['@_w:val'];
|
|
6364
|
-
if (caption
|
|
6941
|
+
if (caption !== undefined && caption !== null && caption !== '') {
|
|
6942
|
+
table.setCaption(String(caption));
|
|
6943
|
+
}
|
|
6365
6944
|
}
|
|
6366
6945
|
|
|
6367
|
-
// Parse table description
|
|
6946
|
+
// Parse table description — ST_String per §17.4.63.
|
|
6368
6947
|
if (tblPrObj['w:tblDescription']) {
|
|
6369
6948
|
const description = tblPrObj['w:tblDescription']['@_w:val'];
|
|
6370
|
-
if (description
|
|
6949
|
+
if (description !== undefined && description !== null && description !== '') {
|
|
6950
|
+
table.setDescription(String(description));
|
|
6951
|
+
}
|
|
6371
6952
|
}
|
|
6372
6953
|
|
|
6373
|
-
// Parse cell spacing
|
|
6954
|
+
// Parse table-level cell spacing (w:tblCellSpacing) per ECMA-376
|
|
6955
|
+
// §17.4.44 CT_TblCellSpacing. w:w is ST_MeasurementOrPercent; 0 is
|
|
6956
|
+
// a legal "explicit zero spacing" value (overrides any style-level
|
|
6957
|
+
// inherited tblCellSpacing). The emitter uses `!== undefined`, so
|
|
6958
|
+
// the previous `spacing > 0` gate created a parser/emitter
|
|
6959
|
+
// asymmetry: a tracked table-property change recording a *previous*
|
|
6960
|
+
// state of `<w:tblCellSpacing w:w="0" …/>` lost the override on
|
|
6961
|
+
// every round-trip.
|
|
6374
6962
|
if (tblPrObj['w:tblCellSpacing']) {
|
|
6375
|
-
const
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6963
|
+
const rawW = tblPrObj['w:tblCellSpacing']['@_w:w'];
|
|
6964
|
+
if (isExplicitlySet(rawW)) {
|
|
6965
|
+
table.setCellSpacing(safeParseInt(rawW));
|
|
6966
|
+
const spacingType = tblPrObj['w:tblCellSpacing']['@_w:type'] || 'dxa';
|
|
6379
6967
|
table.setCellSpacingType(spacingType);
|
|
6380
6968
|
}
|
|
6381
6969
|
}
|
|
@@ -6394,7 +6982,7 @@ export class DocumentParser {
|
|
|
6394
6982
|
table.setIndent(indentVal);
|
|
6395
6983
|
const indentType = tblPrObj['w:tblInd']['@_w:type'];
|
|
6396
6984
|
if (indentType) {
|
|
6397
|
-
table.setIndentType(indentType as import('../elements/Table').TableWidthType);
|
|
6985
|
+
table.setIndentType(indentType as import('../elements/Table.js').TableWidthType);
|
|
6398
6986
|
}
|
|
6399
6987
|
}
|
|
6400
6988
|
|
|
@@ -6460,15 +7048,26 @@ export class DocumentParser {
|
|
|
6460
7048
|
}
|
|
6461
7049
|
}
|
|
6462
7050
|
|
|
6463
|
-
// Parse table borders (w:tblBorders) per ECMA-376 Part 1 §17.4.40
|
|
7051
|
+
// Parse table borders (w:tblBorders) per ECMA-376 Part 1 §17.4.40.
|
|
7052
|
+
// left / right have bidi-aware aliases `w:start` / `w:end` (the
|
|
7053
|
+
// preferred spelling in modern Word-authored documents). Prefer
|
|
7054
|
+
// them when present, falling back to the legacy names — the
|
|
7055
|
+
// internal model stores under `left` / `right`, matching the
|
|
7056
|
+
// emitter. Without this fallback, any table whose side borders
|
|
7057
|
+
// were authored with the bidi-aware form silently lost those
|
|
7058
|
+
// borders on every round-trip (the emitter would replace them
|
|
7059
|
+
// with absent w:left/w:right, and the parser would never revive
|
|
7060
|
+
// the w:start/w:end it dropped).
|
|
6464
7061
|
if (tblPrObj['w:tblBorders']) {
|
|
6465
7062
|
const bordersObj = tblPrObj['w:tblBorders'];
|
|
6466
|
-
const borders: import('../elements/Table').TableBorders = {};
|
|
7063
|
+
const borders: import('../elements/Table.js').TableBorders = {};
|
|
6467
7064
|
|
|
6468
7065
|
if (bordersObj['w:top']) borders.top = this.parseBorderElement(bordersObj['w:top']);
|
|
6469
7066
|
if (bordersObj['w:bottom']) borders.bottom = this.parseBorderElement(bordersObj['w:bottom']);
|
|
6470
|
-
|
|
6471
|
-
if (
|
|
7067
|
+
const leftBorder = bordersObj['w:start'] ?? bordersObj['w:left'];
|
|
7068
|
+
if (leftBorder) borders.left = this.parseBorderElement(leftBorder);
|
|
7069
|
+
const rightBorder = bordersObj['w:end'] ?? bordersObj['w:right'];
|
|
7070
|
+
if (rightBorder) borders.right = this.parseBorderElement(rightBorder);
|
|
6472
7071
|
if (bordersObj['w:insideH'])
|
|
6473
7072
|
borders.insideH = this.parseBorderElement(bordersObj['w:insideH']);
|
|
6474
7073
|
if (bordersObj['w:insideV'])
|
|
@@ -6484,8 +7083,8 @@ export class DocumentParser {
|
|
|
6484
7083
|
const changeObj = tblPrObj['w:tblPrChange'];
|
|
6485
7084
|
table.setTblPrChange({
|
|
6486
7085
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6487
|
-
author: changeObj['@_w:author']
|
|
6488
|
-
date: changeObj['@_w:date']
|
|
7086
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7087
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6489
7088
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:tblPr']),
|
|
6490
7089
|
});
|
|
6491
7090
|
}
|
|
@@ -6562,14 +7161,20 @@ export class DocumentParser {
|
|
|
6562
7161
|
private parseTableRowPropertiesFromObject(trPrObj: any, row: TableRow): void {
|
|
6563
7162
|
if (!trPrObj) return;
|
|
6564
7163
|
|
|
6565
|
-
// Parse row height (w:trHeight) per ECMA-376 Part 1 §17.4.81
|
|
6566
|
-
//
|
|
7164
|
+
// Parse row height (w:trHeight) per ECMA-376 Part 1 §17.4.81.
|
|
7165
|
+
// w:val is ST_TwipsMeasure; zero is a valid value and, combined
|
|
7166
|
+
// with w:hRule="exact", represents a hidden / collapsed row.
|
|
7167
|
+
// XMLParser coerces "0" to the number 0 (falsy), and the previous
|
|
7168
|
+
// `heightVal > 0` gate silently dropped explicit zero-height rows
|
|
7169
|
+
// even though the emitter (TableRow.ts §914) preserves them via
|
|
7170
|
+
// `!== undefined`. Route through isExplicitlySet so zero survives.
|
|
7171
|
+
// Per §17.18.33 (ST_HeightRule), when w:hRule is absent the
|
|
7172
|
+
// default is "auto".
|
|
6567
7173
|
if (trPrObj['w:trHeight']) {
|
|
6568
|
-
const
|
|
7174
|
+
const rawVal = trPrObj['w:trHeight']['@_w:val'];
|
|
6569
7175
|
const heightRule = trPrObj['w:trHeight']['@_w:hRule'];
|
|
6570
|
-
if (
|
|
6571
|
-
|
|
6572
|
-
// so we set height first, then override the rule only if explicitly present
|
|
7176
|
+
if (isExplicitlySet(rawVal)) {
|
|
7177
|
+
const heightVal = safeParseInt(rawVal);
|
|
6573
7178
|
row.setHeight(heightVal);
|
|
6574
7179
|
if (heightRule) {
|
|
6575
7180
|
row.setHeightRule(heightRule);
|
|
@@ -6581,14 +7186,14 @@ export class DocumentParser {
|
|
|
6581
7186
|
}
|
|
6582
7187
|
}
|
|
6583
7188
|
|
|
6584
|
-
// Parse table header row (w:tblHeader) per ECMA-376 Part 1 §17.4.49
|
|
7189
|
+
// Parse table header row (w:tblHeader) per ECMA-376 Part 1 §17.4.49 — CT_OnOff
|
|
6585
7190
|
if (trPrObj['w:tblHeader']) {
|
|
6586
|
-
row.setHeader(
|
|
7191
|
+
row.setHeader(parseOoxmlBoolean(trPrObj['w:tblHeader']));
|
|
6587
7192
|
}
|
|
6588
7193
|
|
|
6589
|
-
// Parse can't split (w:cantSplit) per ECMA-376 Part 1 §17.4.5
|
|
7194
|
+
// Parse can't split (w:cantSplit) per ECMA-376 Part 1 §17.4.5 — CT_OnOff
|
|
6590
7195
|
if (trPrObj['w:cantSplit']) {
|
|
6591
|
-
row.setCantSplit(
|
|
7196
|
+
row.setCantSplit(parseOoxmlBoolean(trPrObj['w:cantSplit']));
|
|
6592
7197
|
}
|
|
6593
7198
|
|
|
6594
7199
|
// Parse row justification (w:jc) per ECMA-376 Part 1 §17.4.79
|
|
@@ -6599,9 +7204,9 @@ export class DocumentParser {
|
|
|
6599
7204
|
}
|
|
6600
7205
|
}
|
|
6601
7206
|
|
|
6602
|
-
// Parse hidden (w:hidden) per ECMA-376 Part 1 §17.4.23
|
|
7207
|
+
// Parse hidden (w:hidden) per ECMA-376 Part 1 §17.4.23 — CT_OnOff
|
|
6603
7208
|
if (trPrObj['w:hidden']) {
|
|
6604
|
-
row.setHidden(
|
|
7209
|
+
row.setHidden(parseOoxmlBoolean(trPrObj['w:hidden']));
|
|
6605
7210
|
}
|
|
6606
7211
|
|
|
6607
7212
|
// Parse grid before (w:gridBefore) per ECMA-376 Part 1 §17.4.15
|
|
@@ -6620,30 +7225,36 @@ export class DocumentParser {
|
|
|
6620
7225
|
}
|
|
6621
7226
|
}
|
|
6622
7227
|
|
|
6623
|
-
// Parse width before (w:wBefore) per ECMA-376 Part 1 §17.4.83
|
|
7228
|
+
// Parse width before (w:wBefore) per ECMA-376 Part 1 §17.4.83.
|
|
7229
|
+
// w:w is ST_TblWidth; 0 paired with w:type="auto" is the idiomatic
|
|
7230
|
+
// "no width" form, and explicit 0 in dxa twips can override an
|
|
7231
|
+
// inherited wBefore. Previous `w > 0` gate silently dropped both.
|
|
6624
7232
|
if (trPrObj['w:wBefore']) {
|
|
6625
|
-
const
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
row.setWBefore(
|
|
7233
|
+
const rawW = trPrObj['w:wBefore']['@_w:w'];
|
|
7234
|
+
if (isExplicitlySet(rawW)) {
|
|
7235
|
+
const type = (trPrObj['w:wBefore']['@_w:type'] as string | undefined) || 'dxa';
|
|
7236
|
+
row.setWBefore(safeParseInt(rawW), type);
|
|
6629
7237
|
}
|
|
6630
7238
|
}
|
|
6631
7239
|
|
|
6632
|
-
// Parse width after (w:wAfter) per ECMA-376 Part 1 §17.4.82
|
|
7240
|
+
// Parse width after (w:wAfter) per ECMA-376 Part 1 §17.4.82 — same
|
|
7241
|
+
// ST_TblWidth semantics as wBefore.
|
|
6633
7242
|
if (trPrObj['w:wAfter']) {
|
|
6634
|
-
const
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
row.setWAfter(
|
|
7243
|
+
const rawW = trPrObj['w:wAfter']['@_w:w'];
|
|
7244
|
+
if (isExplicitlySet(rawW)) {
|
|
7245
|
+
const type = (trPrObj['w:wAfter']['@_w:type'] as string | undefined) || 'dxa';
|
|
7246
|
+
row.setWAfter(safeParseInt(rawW), type);
|
|
6638
7247
|
}
|
|
6639
7248
|
}
|
|
6640
7249
|
|
|
6641
|
-
// Parse row-level cell spacing (w:tblCellSpacing)
|
|
7250
|
+
// Parse row-level cell spacing (w:tblCellSpacing). Zero is a valid
|
|
7251
|
+
// override — "explicitly no extra spacing" on a row overriding a
|
|
7252
|
+
// non-zero table-level tblCellSpacing.
|
|
6642
7253
|
if (trPrObj['w:tblCellSpacing']) {
|
|
6643
|
-
const
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
row.setRowCellSpacing(
|
|
7254
|
+
const rawW = trPrObj['w:tblCellSpacing']['@_w:w'];
|
|
7255
|
+
if (isExplicitlySet(rawW)) {
|
|
7256
|
+
const type = (trPrObj['w:tblCellSpacing']['@_w:type'] as string | undefined) || 'dxa';
|
|
7257
|
+
row.setRowCellSpacing(safeParseInt(rawW), type);
|
|
6647
7258
|
}
|
|
6648
7259
|
}
|
|
6649
7260
|
|
|
@@ -6655,11 +7266,39 @@ export class DocumentParser {
|
|
|
6655
7266
|
}
|
|
6656
7267
|
}
|
|
6657
7268
|
|
|
6658
|
-
// Parse divId (w:divId) per ECMA-376 Part 1 §17.4.9
|
|
7269
|
+
// Parse divId (w:divId) per ECMA-376 Part 1 §17.4.9. `w:val` is
|
|
7270
|
+
// ST_DecimalNumber; 0 is a valid reference to the first div in web
|
|
7271
|
+
// settings. The previous `val > 0` gate silently dropped it on load.
|
|
6659
7272
|
if (trPrObj['w:divId']) {
|
|
6660
|
-
const
|
|
6661
|
-
if (
|
|
6662
|
-
|
|
7273
|
+
const rawVal = trPrObj['w:divId']['@_w:val'];
|
|
7274
|
+
if (isExplicitlySet(rawVal)) {
|
|
7275
|
+
const parsed = safeParseInt(rawVal);
|
|
7276
|
+
if (!isNaN(parsed)) row.setDivId(parsed);
|
|
7277
|
+
}
|
|
7278
|
+
}
|
|
7279
|
+
|
|
7280
|
+
// Parse tracked row insertion / deletion (CT_TrackChange inside CT_TrPr)
|
|
7281
|
+
// per ECMA-376 Part 1 §17.13.5.19 (ins) / §17.13.5.14 (del). These mark
|
|
7282
|
+
// the entire row as a tracked revision; a previous version silently
|
|
7283
|
+
// dropped both markers on load → save because the parser skipped them.
|
|
7284
|
+
if (trPrObj['w:ins']) {
|
|
7285
|
+
const insObj = Array.isArray(trPrObj['w:ins']) ? trPrObj['w:ins'][0] : trPrObj['w:ins'];
|
|
7286
|
+
if (insObj && typeof insObj === 'object') {
|
|
7287
|
+
row.setRowInsertion({
|
|
7288
|
+
id: String(insObj['@_w:id'] ?? '0'),
|
|
7289
|
+
author: String(insObj['@_w:author'] ?? ''),
|
|
7290
|
+
date: String(insObj['@_w:date'] ?? ''),
|
|
7291
|
+
});
|
|
7292
|
+
}
|
|
7293
|
+
}
|
|
7294
|
+
if (trPrObj['w:del']) {
|
|
7295
|
+
const delObj = Array.isArray(trPrObj['w:del']) ? trPrObj['w:del'][0] : trPrObj['w:del'];
|
|
7296
|
+
if (delObj && typeof delObj === 'object') {
|
|
7297
|
+
row.setRowDeletion({
|
|
7298
|
+
id: String(delObj['@_w:id'] ?? '0'),
|
|
7299
|
+
author: String(delObj['@_w:author'] ?? ''),
|
|
7300
|
+
date: String(delObj['@_w:date'] ?? ''),
|
|
7301
|
+
});
|
|
6663
7302
|
}
|
|
6664
7303
|
}
|
|
6665
7304
|
|
|
@@ -6668,8 +7307,8 @@ export class DocumentParser {
|
|
|
6668
7307
|
const changeObj = trPrObj['w:trPrChange'];
|
|
6669
7308
|
row.setTrPrChange({
|
|
6670
7309
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6671
|
-
author: changeObj['@_w:author']
|
|
6672
|
-
date: changeObj['@_w:date']
|
|
7310
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7311
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6673
7312
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:trPr']),
|
|
6674
7313
|
});
|
|
6675
7314
|
}
|
|
@@ -6685,11 +7324,15 @@ export class DocumentParser {
|
|
|
6685
7324
|
|
|
6686
7325
|
const exceptions: any = {};
|
|
6687
7326
|
|
|
6688
|
-
// Parse table width exception (w:tblW)
|
|
7327
|
+
// Parse table width exception (w:tblW). The `val > 0` gate previously
|
|
7328
|
+
// dropped both w:w="0" (explicit zero-width override, valid when
|
|
7329
|
+
// paired with w:type="nil"/"auto") and negative overrides. Route
|
|
7330
|
+
// through isExplicitlySet + safeParseInt so zero and negative widths
|
|
7331
|
+
// round-trip.
|
|
6689
7332
|
if (tblPrExObj['w:tblW']) {
|
|
6690
|
-
const
|
|
6691
|
-
if (
|
|
6692
|
-
exceptions.width =
|
|
7333
|
+
const rawW = tblPrExObj['w:tblW']['@_w:w'];
|
|
7334
|
+
if (isExplicitlySet(rawW)) {
|
|
7335
|
+
exceptions.width = safeParseInt(rawW);
|
|
6693
7336
|
}
|
|
6694
7337
|
}
|
|
6695
7338
|
|
|
@@ -6701,19 +7344,26 @@ export class DocumentParser {
|
|
|
6701
7344
|
}
|
|
6702
7345
|
}
|
|
6703
7346
|
|
|
6704
|
-
// Parse cell spacing exception (w:tblCellSpacing)
|
|
7347
|
+
// Parse cell spacing exception (w:tblCellSpacing). Zero-value
|
|
7348
|
+
// override is valid (= "explicit no cell spacing" on a row that
|
|
7349
|
+
// would otherwise inherit non-zero spacing from the table-level
|
|
7350
|
+
// tblCellSpacing).
|
|
6705
7351
|
if (tblPrExObj['w:tblCellSpacing']) {
|
|
6706
|
-
const
|
|
6707
|
-
if (
|
|
6708
|
-
exceptions.cellSpacing =
|
|
7352
|
+
const rawW = tblPrExObj['w:tblCellSpacing']['@_w:w'];
|
|
7353
|
+
if (isExplicitlySet(rawW)) {
|
|
7354
|
+
exceptions.cellSpacing = safeParseInt(rawW);
|
|
6709
7355
|
}
|
|
6710
7356
|
}
|
|
6711
7357
|
|
|
6712
|
-
// Parse table indentation exception (w:tblInd)
|
|
7358
|
+
// Parse table indentation exception (w:tblInd). Per ECMA-376
|
|
7359
|
+
// §17.4.62 CT_TblWidth, w:w is ST_MeasurementOrPercent — 0 is a
|
|
7360
|
+
// legal "reset" value and negative values indicate an outdent (table
|
|
7361
|
+
// hanging into the page margin). The previous `val > 0` check
|
|
7362
|
+
// silently dropped both.
|
|
6713
7363
|
if (tblPrExObj['w:tblInd']) {
|
|
6714
|
-
const
|
|
6715
|
-
if (
|
|
6716
|
-
exceptions.indentation =
|
|
7364
|
+
const rawW = tblPrExObj['w:tblInd']['@_w:w'];
|
|
7365
|
+
if (isExplicitlySet(rawW)) {
|
|
7366
|
+
exceptions.indentation = safeParseInt(rawW);
|
|
6717
7367
|
}
|
|
6718
7368
|
}
|
|
6719
7369
|
|
|
@@ -6744,15 +7394,40 @@ export class DocumentParser {
|
|
|
6744
7394
|
const borderNames = ['top', 'bottom', 'left', 'right', 'insideH', 'insideV'];
|
|
6745
7395
|
|
|
6746
7396
|
for (const name of borderNames) {
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
7397
|
+
// Prefer bidi-aware `w:start`/`w:end` aliases over legacy `w:left`/
|
|
7398
|
+
// `w:right` per ECMA-376 §17.4.40 CT_TblBorders. Modern Word-
|
|
7399
|
+
// authored documents emit the bidi-aware form by default; the
|
|
7400
|
+
// internal model stores under the legacy keys to match the emitter.
|
|
7401
|
+
const aliasKey = name === 'left' ? 'w:start' : name === 'right' ? 'w:end' : undefined;
|
|
7402
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${name}`];
|
|
7403
|
+
if (borderObj) {
|
|
6750
7404
|
borders[name] = {};
|
|
6751
7405
|
|
|
7406
|
+
// Full CT_Border attribute set (§17.18.2) — previously only the four
|
|
7407
|
+
// basic attrs were read, so tblPrEx borders lost themed-color linkage
|
|
7408
|
+
// on every round-trip.
|
|
6752
7409
|
if (borderObj['@_w:val']) borders[name].style = borderObj['@_w:val'];
|
|
6753
7410
|
if (borderObj['@_w:sz']) borders[name].size = parseInt(borderObj['@_w:sz'], 10);
|
|
6754
7411
|
if (borderObj['@_w:space']) borders[name].space = parseInt(borderObj['@_w:space'], 10);
|
|
6755
|
-
if (borderObj['@_w:color']) borders[name].color = borderObj['@_w:color'];
|
|
7412
|
+
if (borderObj['@_w:color']) borders[name].color = String(borderObj['@_w:color']);
|
|
7413
|
+
// String(...) cast: themeTint / themeShade are ST_UcharHexNumber
|
|
7414
|
+
// (2-char hex). XMLParser coerces purely-digit hex to numbers —
|
|
7415
|
+
// cast so the string contract on the model is preserved.
|
|
7416
|
+
if (borderObj['@_w:themeColor']) {
|
|
7417
|
+
borders[name].themeColor = String(borderObj['@_w:themeColor']);
|
|
7418
|
+
}
|
|
7419
|
+
if (borderObj['@_w:themeTint']) {
|
|
7420
|
+
borders[name].themeTint = String(borderObj['@_w:themeTint']);
|
|
7421
|
+
}
|
|
7422
|
+
if (borderObj['@_w:themeShade']) {
|
|
7423
|
+
borders[name].themeShade = String(borderObj['@_w:themeShade']);
|
|
7424
|
+
}
|
|
7425
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
7426
|
+
borders[name].shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
7427
|
+
}
|
|
7428
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
7429
|
+
borders[name].frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
7430
|
+
}
|
|
6756
7431
|
}
|
|
6757
7432
|
}
|
|
6758
7433
|
|
|
@@ -6773,12 +7448,26 @@ export class DocumentParser {
|
|
|
6773
7448
|
// Parse cell properties (w:tcPr) per ECMA-376 Part 1 §17.4.42
|
|
6774
7449
|
const tcPr = cellObj['w:tcPr'];
|
|
6775
7450
|
if (tcPr) {
|
|
6776
|
-
// Parse cell width (w:tcW) with type per ECMA-376 Part 1 §17.4.
|
|
7451
|
+
// Parse cell width (w:tcW) with type per ECMA-376 Part 1 §17.4.72
|
|
7452
|
+
// CT_TblWidth — w:w is ST_MeasurementOrPercent, w:type is
|
|
7453
|
+
// ST_TblWidth. Zero is a legal explicit override:
|
|
7454
|
+
// - `w:w="0" w:type="auto"` is the idiomatic "size to content"
|
|
7455
|
+
// form (also the default when w:tcW is absent).
|
|
7456
|
+
// - `w:w="0" w:type="dxa"` / `"pct"` / `"nil"` explicitly
|
|
7457
|
+
// override an inherited non-zero width back to zero.
|
|
7458
|
+
// The emitter at TableCell.ts:1353 uses `!== undefined`, so the
|
|
7459
|
+
// previous `widthVal > 0 || widthType === 'auto'` gate created a
|
|
7460
|
+
// parser/emitter asymmetry — any cell with an explicit zero
|
|
7461
|
+
// override in a non-auto width type silently reinherited the
|
|
7462
|
+
// style-level width on every round-trip.
|
|
6777
7463
|
if (tcPr['w:tcW']) {
|
|
6778
|
-
const
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
cell.setWidthType(
|
|
7464
|
+
const rawW = tcPr['w:tcW']['@_w:w'];
|
|
7465
|
+
if (isExplicitlySet(rawW)) {
|
|
7466
|
+
const widthType = (tcPr['w:tcW']['@_w:type'] as string | undefined) || 'dxa';
|
|
7467
|
+
cell.setWidthType(
|
|
7468
|
+
safeParseInt(rawW),
|
|
7469
|
+
widthType as import('../elements/TableCell.js').CellWidthType
|
|
7470
|
+
);
|
|
6782
7471
|
}
|
|
6783
7472
|
}
|
|
6784
7473
|
|
|
@@ -6790,7 +7479,11 @@ export class DocumentParser {
|
|
|
6790
7479
|
}
|
|
6791
7480
|
}
|
|
6792
7481
|
|
|
6793
|
-
// Parse cell borders (w:tcBorders)
|
|
7482
|
+
// Parse cell borders (w:tcBorders) per ECMA-376 Part 1 §17.4.66.
|
|
7483
|
+
// Supports both legacy LTR names (w:left / w:right) and bidi-
|
|
7484
|
+
// aware aliases (w:start / w:end). Prefer w:start / w:end when
|
|
7485
|
+
// present. Includes diagonal borders (w:tl2br / w:tr2bl) which
|
|
7486
|
+
// are cell-specific.
|
|
6794
7487
|
if (tcPr['w:tcBorders']) {
|
|
6795
7488
|
const bordersObj = tcPr['w:tcBorders'];
|
|
6796
7489
|
const borders: any = {};
|
|
@@ -6798,8 +7491,10 @@ export class DocumentParser {
|
|
|
6798
7491
|
if (bordersObj['w:top']) borders.top = this.parseBorderElement(bordersObj['w:top']);
|
|
6799
7492
|
if (bordersObj['w:bottom'])
|
|
6800
7493
|
borders.bottom = this.parseBorderElement(bordersObj['w:bottom']);
|
|
6801
|
-
|
|
6802
|
-
if (
|
|
7494
|
+
const leftBorder = bordersObj['w:start'] ?? bordersObj['w:left'];
|
|
7495
|
+
if (leftBorder) borders.left = this.parseBorderElement(leftBorder);
|
|
7496
|
+
const rightBorder = bordersObj['w:end'] ?? bordersObj['w:right'];
|
|
7497
|
+
if (rightBorder) borders.right = this.parseBorderElement(rightBorder);
|
|
6803
7498
|
if (bordersObj['w:tl2br']) borders.tl2br = this.parseBorderElement(bordersObj['w:tl2br']);
|
|
6804
7499
|
if (bordersObj['w:tr2bl']) borders.tr2bl = this.parseBorderElement(bordersObj['w:tr2bl']);
|
|
6805
7500
|
|
|
@@ -6842,10 +7537,15 @@ export class DocumentParser {
|
|
|
6842
7537
|
}
|
|
6843
7538
|
}
|
|
6844
7539
|
|
|
6845
|
-
// Parse vertical alignment (w:vAlign)
|
|
7540
|
+
// Parse vertical alignment (w:vAlign) per ECMA-376 §17.4.83.
|
|
7541
|
+
// ST_VerticalJc has four values (§17.18.101): top, center, both,
|
|
7542
|
+
// bottom. The previous whitelist dropped "both" silently — the
|
|
7543
|
+
// style-level parser accepts it, so the asymmetry truncated cell
|
|
7544
|
+
// vertical alignment on cells using the "both" (justified)
|
|
7545
|
+
// vertical alignment on load.
|
|
6846
7546
|
if (tcPr['w:vAlign']) {
|
|
6847
7547
|
const valign = tcPr['w:vAlign']['@_w:val'];
|
|
6848
|
-
if (valign
|
|
7548
|
+
if (valign === 'top' || valign === 'center' || valign === 'both' || valign === 'bottom') {
|
|
6849
7549
|
cell.setVerticalAlignment(valign);
|
|
6850
7550
|
}
|
|
6851
7551
|
}
|
|
@@ -6866,14 +7566,14 @@ export class DocumentParser {
|
|
|
6866
7566
|
}
|
|
6867
7567
|
}
|
|
6868
7568
|
|
|
6869
|
-
// Parse no wrap (w:noWrap) per ECMA-376 Part 1 §17.4.34
|
|
7569
|
+
// Parse no wrap (w:noWrap) per ECMA-376 Part 1 §17.4.34 — CT_OnOff
|
|
6870
7570
|
if (tcPr['w:noWrap']) {
|
|
6871
|
-
cell.setNoWrap(
|
|
7571
|
+
cell.setNoWrap(parseOoxmlBoolean(tcPr['w:noWrap']));
|
|
6872
7572
|
}
|
|
6873
7573
|
|
|
6874
|
-
// Parse hide mark (w:hideMark) per ECMA-376 Part 1 §17.4.24
|
|
7574
|
+
// Parse hide mark (w:hideMark) per ECMA-376 Part 1 §17.4.24 — CT_OnOff
|
|
6875
7575
|
if (tcPr['w:hideMark']) {
|
|
6876
|
-
cell.setHideMark(
|
|
7576
|
+
cell.setHideMark(parseOoxmlBoolean(tcPr['w:hideMark']));
|
|
6877
7577
|
}
|
|
6878
7578
|
|
|
6879
7579
|
// Parse headers (w:headers) per ECMA-376 Part 1 §17.4.26
|
|
@@ -6884,9 +7584,9 @@ export class DocumentParser {
|
|
|
6884
7584
|
}
|
|
6885
7585
|
}
|
|
6886
7586
|
|
|
6887
|
-
// Parse fit text (w:tcFitText) per ECMA-376 Part 1 §17.4.68
|
|
7587
|
+
// Parse fit text (w:tcFitText) per ECMA-376 Part 1 §17.4.68 — CT_OnOff
|
|
6888
7588
|
if (tcPr['w:tcFitText']) {
|
|
6889
|
-
cell.setFitText(
|
|
7589
|
+
cell.setFitText(parseOoxmlBoolean(tcPr['w:tcFitText']));
|
|
6890
7590
|
}
|
|
6891
7591
|
|
|
6892
7592
|
// Parse vertical merge (w:vMerge) per ECMA-376 Part 1 §17.4.85
|
|
@@ -6913,10 +7613,11 @@ export class DocumentParser {
|
|
|
6913
7613
|
// Parse table cell insertion marker (w:cellIns) per ECMA-376 Part 1 §17.13.5.5
|
|
6914
7614
|
if (tcPr['w:cellIns']) {
|
|
6915
7615
|
const cellIns = tcPr['w:cellIns'];
|
|
6916
|
-
const id = parseInt(cellIns['@_w:id']
|
|
6917
|
-
const author =
|
|
7616
|
+
const id = parseInt(String(cellIns['@_w:id'] ?? '0'), 10);
|
|
7617
|
+
const author =
|
|
7618
|
+
cellIns['@_w:author'] !== undefined ? String(cellIns['@_w:author']) : 'Unknown';
|
|
6918
7619
|
const dateAttr = cellIns['@_w:date'];
|
|
6919
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7620
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6920
7621
|
|
|
6921
7622
|
const revision = new Revision({
|
|
6922
7623
|
id,
|
|
@@ -6931,10 +7632,11 @@ export class DocumentParser {
|
|
|
6931
7632
|
// Parse table cell deletion marker (w:cellDel) per ECMA-376 Part 1 §17.13.5.6
|
|
6932
7633
|
if (tcPr['w:cellDel']) {
|
|
6933
7634
|
const cellDel = tcPr['w:cellDel'];
|
|
6934
|
-
const id = parseInt(cellDel['@_w:id']
|
|
6935
|
-
const author =
|
|
7635
|
+
const id = parseInt(String(cellDel['@_w:id'] ?? '0'), 10);
|
|
7636
|
+
const author =
|
|
7637
|
+
cellDel['@_w:author'] !== undefined ? String(cellDel['@_w:author']) : 'Unknown';
|
|
6936
7638
|
const dateAttr = cellDel['@_w:date'];
|
|
6937
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7639
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6938
7640
|
|
|
6939
7641
|
const revision = new Revision({
|
|
6940
7642
|
id,
|
|
@@ -6949,10 +7651,11 @@ export class DocumentParser {
|
|
|
6949
7651
|
// Parse table cell merge marker (w:cellMerge) per ECMA-376 Part 1 §17.13.5.4
|
|
6950
7652
|
if (tcPr['w:cellMerge']) {
|
|
6951
7653
|
const cellMerge = tcPr['w:cellMerge'];
|
|
6952
|
-
const id = parseInt(cellMerge['@_w:id']
|
|
6953
|
-
const author =
|
|
7654
|
+
const id = parseInt(String(cellMerge['@_w:id'] ?? '0'), 10);
|
|
7655
|
+
const author =
|
|
7656
|
+
cellMerge['@_w:author'] !== undefined ? String(cellMerge['@_w:author']) : 'Unknown';
|
|
6954
7657
|
const dateAttr = cellMerge['@_w:date'];
|
|
6955
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7658
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6956
7659
|
const vMergeAttr = cellMerge['@_w:vMerge'];
|
|
6957
7660
|
const vMergeOrigAttr = cellMerge['@_w:vMergeOrig'];
|
|
6958
7661
|
// ST_AnnotationVMerge uses "rest"/"cont" but API uses "restart"/"continue"
|
|
@@ -6977,8 +7680,8 @@ export class DocumentParser {
|
|
|
6977
7680
|
const changeObj = tcPr['w:tcPrChange'];
|
|
6978
7681
|
cell.setTcPrChange({
|
|
6979
7682
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6980
|
-
author: changeObj['@_w:author']
|
|
6981
|
-
date: changeObj['@_w:date']
|
|
7683
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7684
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6982
7685
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:tcPr']),
|
|
6983
7686
|
});
|
|
6984
7687
|
}
|
|
@@ -7226,28 +7929,40 @@ export class DocumentParser {
|
|
|
7226
7929
|
// Parse SDT properties (sdtPr)
|
|
7227
7930
|
const sdtPr = sdtObj['w:sdtPr'];
|
|
7228
7931
|
if (sdtPr) {
|
|
7229
|
-
// Parse
|
|
7932
|
+
// Parse `<w:id w:val="…"/>` per ECMA-376 §17.5.2.18. `w:val` is
|
|
7933
|
+
// ST_DecimalNumber (xsd:integer) — 0 is legal. XMLParser coerces
|
|
7934
|
+
// `"0"` to the number `0`, so the previous truthy gate silently
|
|
7935
|
+
// dropped w:id=0 on every load → save cycle. The emitter uses
|
|
7936
|
+
// `!== undefined`, creating a parser/emitter asymmetry.
|
|
7230
7937
|
const idElement = sdtPr['w:id'];
|
|
7231
|
-
if (idElement?.['@_w:val']) {
|
|
7232
|
-
|
|
7938
|
+
if (isExplicitlySet(idElement?.['@_w:val'])) {
|
|
7939
|
+
const parsed = safeParseInt(idElement['@_w:val']);
|
|
7940
|
+
if (!isNaN(parsed)) properties.id = parsed;
|
|
7233
7941
|
}
|
|
7234
7942
|
|
|
7235
|
-
// Parse tag
|
|
7943
|
+
// Parse `<w:tag w:val="…"/>` per ECMA-376 §17.5.2.34. `w:val`
|
|
7944
|
+
// is ST_String — any string is legal, including numeric-looking
|
|
7945
|
+
// strings like "123" that XMLParser coerces to the number 123.
|
|
7946
|
+
// Cast via `String(…)` so the tag round-trips as text rather
|
|
7947
|
+
// than leaking a JS number into a `tag?: string` field.
|
|
7236
7948
|
const tagElement = sdtPr['w:tag'];
|
|
7237
|
-
if (tagElement?.['@_w:val']) {
|
|
7238
|
-
properties.tag = tagElement['@_w:val'];
|
|
7949
|
+
if (tagElement?.['@_w:val'] !== undefined) {
|
|
7950
|
+
properties.tag = String(tagElement['@_w:val']);
|
|
7239
7951
|
}
|
|
7240
7952
|
|
|
7241
|
-
// Parse lock
|
|
7953
|
+
// Parse lock — ST_Lock enum: "sdtLocked" / "contentLocked" /
|
|
7954
|
+
// "sdtContentLocked" / "unlocked". Always a non-numeric string,
|
|
7955
|
+
// so no XMLParser coercion concern; truthy check fine.
|
|
7242
7956
|
const lockElement = sdtPr['w:lock'];
|
|
7243
7957
|
if (lockElement?.['@_w:val']) {
|
|
7244
7958
|
properties.lock = lockElement['@_w:val'];
|
|
7245
7959
|
}
|
|
7246
7960
|
|
|
7247
|
-
// Parse alias
|
|
7961
|
+
// Parse alias — ST_String. Same numeric-coercion concern as
|
|
7962
|
+
// `w:tag`; cast via `String(…)`.
|
|
7248
7963
|
const aliasElement = sdtPr['w:alias'];
|
|
7249
|
-
if (aliasElement?.['@_w:val']) {
|
|
7250
|
-
properties.alias = aliasElement['@_w:val'];
|
|
7964
|
+
if (aliasElement?.['@_w:val'] !== undefined) {
|
|
7965
|
+
properties.alias = String(aliasElement['@_w:val']);
|
|
7251
7966
|
}
|
|
7252
7967
|
|
|
7253
7968
|
// Parse control type from various elements
|
|
@@ -7256,9 +7971,18 @@ export class DocumentParser {
|
|
|
7256
7971
|
} else if (sdtPr['w:text']) {
|
|
7257
7972
|
properties.controlType = 'plainText';
|
|
7258
7973
|
const textElement = sdtPr['w:text'];
|
|
7974
|
+
// w:multiLine is an OPTIONAL ST_OnOff attribute per ECMA-376
|
|
7975
|
+
// §17.5.2.33 CT_SdtText. Only record a value when the source
|
|
7976
|
+
// actually set it — otherwise leave the field undefined so
|
|
7977
|
+
// the emitter (which uses `!== undefined`) preserves the
|
|
7978
|
+
// "attribute absent" state on round-trip. Previously the
|
|
7979
|
+
// parser unconditionally stored `false` for any absent
|
|
7980
|
+
// attribute, then the emitter wrote `w:multiLine="0"` —
|
|
7981
|
+
// adding spec-noise that wasn't in the source.
|
|
7982
|
+
const rawMultiLine = textElement?.['@_w:multiLine'];
|
|
7259
7983
|
properties.plainText = {
|
|
7260
7984
|
multiLine:
|
|
7261
|
-
|
|
7985
|
+
rawMultiLine === undefined ? undefined : parseOnOffAttribute(String(rawMultiLine)),
|
|
7262
7986
|
};
|
|
7263
7987
|
} else if (sdtPr['w:comboBox']) {
|
|
7264
7988
|
properties.controlType = 'comboBox';
|
|
@@ -7286,14 +8010,11 @@ export class DocumentParser {
|
|
|
7286
8010
|
} else if (sdtPr['w14:checkbox']) {
|
|
7287
8011
|
properties.controlType = 'checkbox';
|
|
7288
8012
|
const checkboxElement = sdtPr['w14:checkbox'];
|
|
7289
|
-
//
|
|
7290
|
-
|
|
8013
|
+
// <w14:checked> is CT_OnOff in the Word 2010+ extension namespace.
|
|
8014
|
+
// Honour every ST_OnOff literal ("1"/"0"/"true"/"false"/"on"/"off")
|
|
8015
|
+
// and treat a bare self-closing `<w14:checked/>` as true.
|
|
7291
8016
|
properties.checkbox = {
|
|
7292
|
-
checked:
|
|
7293
|
-
checkedVal === 1 ||
|
|
7294
|
-
checkedVal === '1' ||
|
|
7295
|
-
checkedVal === true ||
|
|
7296
|
-
checkedVal === 'true',
|
|
8017
|
+
checked: parseOoxmlBoolean(checkboxElement?.['w14:checked'], '@_w14:val'),
|
|
7297
8018
|
checkedState: String(checkboxElement?.['w14:checkedState']?.['@_w14:val'] ?? ''),
|
|
7298
8019
|
uncheckedState: String(checkboxElement?.['w14:uncheckedState']?.['@_w14:val'] ?? ''),
|
|
7299
8020
|
};
|
|
@@ -7343,11 +8064,10 @@ export class DocumentParser {
|
|
|
7343
8064
|
};
|
|
7344
8065
|
}
|
|
7345
8066
|
|
|
7346
|
-
// Parse showing placeholder flag (w:showingPlcHdr)
|
|
8067
|
+
// Parse showing placeholder flag (w:showingPlcHdr) — CT_OnOff per ECMA-376 §17.5.2.40
|
|
7347
8068
|
const showingPlcHdr = sdtPr['w:showingPlcHdr'];
|
|
7348
8069
|
if (showingPlcHdr) {
|
|
7349
|
-
|
|
7350
|
-
properties.showingPlcHdr = val === '1' || val === 'true' || val === true;
|
|
8070
|
+
properties.showingPlcHdr = parseOoxmlBoolean(showingPlcHdr);
|
|
7351
8071
|
}
|
|
7352
8072
|
}
|
|
7353
8073
|
|
|
@@ -7887,7 +8607,25 @@ export class DocumentParser {
|
|
|
7887
8607
|
}
|
|
7888
8608
|
|
|
7889
8609
|
/**
|
|
7890
|
-
* Helper to parse list items for combo box / dropdown
|
|
8610
|
+
* Helper to parse list items for combo box / dropdown per ECMA-376
|
|
8611
|
+
* Part 1 §17.5.2.13 CT_SdtListItem. `w:value` is required; both
|
|
8612
|
+
* `w:displayText` and `w:value` are ST_String so any string
|
|
8613
|
+
* (including the empty string) is legal.
|
|
8614
|
+
*
|
|
8615
|
+
* The previous truthy gate dropped legitimate list items whenever:
|
|
8616
|
+
* - `w:value="0"` / `w:value="123"` — XMLParser coerces numeric
|
|
8617
|
+
* strings to numbers; `0` fails the truthy check entirely, and
|
|
8618
|
+
* storing a raw number instead of a string breaks the `ListItem`
|
|
8619
|
+
* `value: string` contract downstream.
|
|
8620
|
+
* - `w:displayText=""` — empty displayText is legal (e.g. a
|
|
8621
|
+
* separator / blank choice); the gate dropped it.
|
|
8622
|
+
* The fix:
|
|
8623
|
+
* - Gate on presence (`!== undefined`), not truthiness.
|
|
8624
|
+
* - Coerce both attributes to `String(…)` so numeric-coerced
|
|
8625
|
+
* attribute values serialise back to their original textual form.
|
|
8626
|
+
* - Default missing `w:displayText` to the stringified `w:value`
|
|
8627
|
+
* (the idiomatic Word fallback when authors author list items
|
|
8628
|
+
* with only a value attribute).
|
|
7891
8629
|
*/
|
|
7892
8630
|
private parseListItems(element: any): any {
|
|
7893
8631
|
const items: any[] = [];
|
|
@@ -7895,17 +8633,18 @@ export class DocumentParser {
|
|
|
7895
8633
|
const itemArray = Array.isArray(listItems) ? listItems : listItems ? [listItems] : [];
|
|
7896
8634
|
|
|
7897
8635
|
for (const item of itemArray) {
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
}
|
|
8636
|
+
const rawValue = item['@_w:value'];
|
|
8637
|
+
if (rawValue === undefined) continue; // w:value is required by the schema
|
|
8638
|
+
const value = String(rawValue);
|
|
8639
|
+
const rawDisplay = item['@_w:displayText'];
|
|
8640
|
+
const displayText = rawDisplay === undefined ? value : String(rawDisplay);
|
|
8641
|
+
items.push({ displayText, value });
|
|
7904
8642
|
}
|
|
7905
8643
|
|
|
8644
|
+
const rawLast = element?.['@_w:lastValue'];
|
|
7906
8645
|
return {
|
|
7907
8646
|
items,
|
|
7908
|
-
lastValue:
|
|
8647
|
+
lastValue: rawLast === undefined ? undefined : String(rawLast),
|
|
7909
8648
|
};
|
|
7910
8649
|
}
|
|
7911
8650
|
|
|
@@ -8381,12 +9120,21 @@ export class DocumentParser {
|
|
|
8381
9120
|
if (color) border.color = color;
|
|
8382
9121
|
const space = XMLParser.extractAttribute(sideXml, 'w:space');
|
|
8383
9122
|
if (space) border.space = parseInt(space.toString(), 10);
|
|
9123
|
+
// w:shadow and w:frame are ST_OnOff per ECMA-376 §17.17.4.
|
|
9124
|
+
// Use `!== undefined` gating so explicit-false survives round-trip
|
|
9125
|
+
// (previous code only stored `true`, silently dropping `w:shadow="0"`).
|
|
8384
9126
|
const shadow = XMLParser.extractAttribute(sideXml, 'w:shadow');
|
|
8385
|
-
if (shadow
|
|
9127
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
8386
9128
|
const frame = XMLParser.extractAttribute(sideXml, 'w:frame');
|
|
8387
|
-
if (frame
|
|
9129
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
8388
9130
|
const themeColor = XMLParser.extractAttribute(sideXml, 'w:themeColor');
|
|
8389
9131
|
if (themeColor) border.themeColor = themeColor;
|
|
9132
|
+
// Theme tint / shade per §17.18.82 — CT_TopBorder/CT_BottomBorder extend
|
|
9133
|
+
// CT_Border so inherit the full themed-color attribute set.
|
|
9134
|
+
const themeTint = XMLParser.extractAttribute(sideXml, 'w:themeTint');
|
|
9135
|
+
if (themeTint) border.themeTint = themeTint;
|
|
9136
|
+
const themeShade = XMLParser.extractAttribute(sideXml, 'w:themeShade');
|
|
9137
|
+
if (themeShade) border.themeShade = themeShade;
|
|
8390
9138
|
const artId = XMLParser.extractAttribute(sideXml, 'w:id');
|
|
8391
9139
|
if (artId) border.artId = parseInt(artId.toString(), 10);
|
|
8392
9140
|
return Object.keys(border).length > 0 ? border : undefined;
|
|
@@ -8407,7 +9155,14 @@ export class DocumentParser {
|
|
|
8407
9155
|
}
|
|
8408
9156
|
}
|
|
8409
9157
|
|
|
8410
|
-
// Parse columns
|
|
9158
|
+
// Parse columns per ECMA-376 §17.6.4 CT_Columns. Every attribute
|
|
9159
|
+
// (num / sep / space / equalWidth) is optional with spec-defined
|
|
9160
|
+
// defaults — num defaults to 1, equalWidth to true, sep to false,
|
|
9161
|
+
// space to 720 twips. The previous `if (num)` gate silently dropped
|
|
9162
|
+
// every `<w:cols>` that relied on the default num=1 (e.g. a bare
|
|
9163
|
+
// `<w:cols w:sep="1" w:space="720"/>` specifying a single column
|
|
9164
|
+
// with a separator), which is the exact form Word emits when the
|
|
9165
|
+
// user toggles the column separator without changing column count.
|
|
8411
9166
|
const colsElements = XMLParser.extractElements(sectPr, 'w:cols');
|
|
8412
9167
|
if (colsElements.length > 0) {
|
|
8413
9168
|
const cols = colsElements[0];
|
|
@@ -8436,19 +9191,23 @@ export class DocumentParser {
|
|
|
8436
9191
|
}
|
|
8437
9192
|
}
|
|
8438
9193
|
|
|
8439
|
-
//
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
9194
|
+
// Spec default for num is 1; fall back to column-count from
|
|
9195
|
+
// child `<w:col>` children when available (the expanded per-column
|
|
9196
|
+
// form), otherwise to the literal default.
|
|
9197
|
+
const count = num
|
|
9198
|
+
? parseInt(num.toString(), 10)
|
|
9199
|
+
: columnWidths.length > 0
|
|
9200
|
+
? columnWidths.length
|
|
9201
|
+
: 1;
|
|
9202
|
+
|
|
9203
|
+
sectionProps.columns = {
|
|
9204
|
+
count,
|
|
9205
|
+
space: space ? parseInt(space.toString(), 10) : undefined,
|
|
9206
|
+
equalWidth: equalWidth ? parseOnOffAttribute(equalWidth) : undefined,
|
|
9207
|
+
separator: sep ? parseOnOffAttribute(sep) : undefined,
|
|
9208
|
+
columnWidths: columnWidths.length > 0 ? columnWidths : undefined,
|
|
9209
|
+
columnSpaces: hasColumnSpaces ? columnSpaces : undefined,
|
|
9210
|
+
};
|
|
8452
9211
|
}
|
|
8453
9212
|
}
|
|
8454
9213
|
|
|
@@ -8489,9 +9248,13 @@ export class DocumentParser {
|
|
|
8489
9248
|
}
|
|
8490
9249
|
}
|
|
8491
9250
|
|
|
8492
|
-
// Parse title page flag
|
|
8493
|
-
|
|
8494
|
-
|
|
9251
|
+
// Parse title page flag (w:titlePg) — CT_OnOff per ECMA-376 §17.6.23;
|
|
9252
|
+
// honour w:val so an explicit `w:val="0"` override of an inherited
|
|
9253
|
+
// true is not silently flipped to true.
|
|
9254
|
+
const titlePgEls = XMLParser.extractElements(sectPr, 'w:titlePg');
|
|
9255
|
+
if (titlePgEls.length > 0 && titlePgEls[0]) {
|
|
9256
|
+
const v = XMLParser.extractAttribute(titlePgEls[0], 'w:val');
|
|
9257
|
+
sectionProps.titlePage = parseOnOffAttribute(v, true);
|
|
8495
9258
|
}
|
|
8496
9259
|
|
|
8497
9260
|
// Parse header references
|
|
@@ -8574,14 +9337,18 @@ export class DocumentParser {
|
|
|
8574
9337
|
}
|
|
8575
9338
|
}
|
|
8576
9339
|
|
|
8577
|
-
// Parse bidi (
|
|
8578
|
-
|
|
8579
|
-
|
|
9340
|
+
// Parse bidi (w:bidi) — CT_OnOff per ECMA-376 §17.6.1 (RTL section)
|
|
9341
|
+
const bidiEls = XMLParser.extractElements(sectPr, 'w:bidi');
|
|
9342
|
+
if (bidiEls.length > 0 && bidiEls[0]) {
|
|
9343
|
+
const v = XMLParser.extractAttribute(bidiEls[0], 'w:val');
|
|
9344
|
+
sectionProps.bidi = parseOnOffAttribute(v, true);
|
|
8580
9345
|
}
|
|
8581
9346
|
|
|
8582
|
-
// Parse RTL gutter
|
|
8583
|
-
|
|
8584
|
-
|
|
9347
|
+
// Parse RTL gutter (w:rtlGutter) — CT_OnOff per ECMA-376 §17.6.16
|
|
9348
|
+
const rtlGutterEls = XMLParser.extractElements(sectPr, 'w:rtlGutter');
|
|
9349
|
+
if (rtlGutterEls.length > 0 && rtlGutterEls[0]) {
|
|
9350
|
+
const v = XMLParser.extractAttribute(rtlGutterEls[0], 'w:val');
|
|
9351
|
+
sectionProps.rtlGutter = parseOnOffAttribute(v, true);
|
|
8585
9352
|
}
|
|
8586
9353
|
|
|
8587
9354
|
// Parse document grid (w:docGrid)
|
|
@@ -8659,14 +9426,18 @@ export class DocumentParser {
|
|
|
8659
9426
|
if (Object.keys(props).length > 0) sectionProps.endnotePr = props;
|
|
8660
9427
|
}
|
|
8661
9428
|
|
|
8662
|
-
// Parse noEndnote
|
|
8663
|
-
|
|
8664
|
-
|
|
9429
|
+
// Parse noEndnote (w:noEndnote) — CT_OnOff per ECMA-376 §17.11.14
|
|
9430
|
+
const noEndEls = XMLParser.extractElements(sectPr, 'w:noEndnote');
|
|
9431
|
+
if (noEndEls.length > 0 && noEndEls[0]) {
|
|
9432
|
+
const v = XMLParser.extractAttribute(noEndEls[0], 'w:val');
|
|
9433
|
+
sectionProps.noEndnote = parseOnOffAttribute(v, true);
|
|
8665
9434
|
}
|
|
8666
9435
|
|
|
8667
|
-
// Parse form protection
|
|
8668
|
-
|
|
8669
|
-
|
|
9436
|
+
// Parse form protection (w:formProt) — CT_OnOff per ECMA-376 §17.6.8
|
|
9437
|
+
const formProtEls = XMLParser.extractElements(sectPr, 'w:formProt');
|
|
9438
|
+
if (formProtEls.length > 0 && formProtEls[0]) {
|
|
9439
|
+
const v = XMLParser.extractAttribute(formProtEls[0], 'w:val');
|
|
9440
|
+
sectionProps.formProt = parseOnOffAttribute(v, true);
|
|
8670
9441
|
}
|
|
8671
9442
|
|
|
8672
9443
|
// Parse printer settings (w:printerSettings r:id)
|
|
@@ -8789,34 +9560,45 @@ export class DocumentParser {
|
|
|
8789
9560
|
runFormatting = this.parseRunFormattingFromXml(rPrXml);
|
|
8790
9561
|
}
|
|
8791
9562
|
|
|
8792
|
-
// Parse metadata
|
|
8793
|
-
//
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
//
|
|
8797
|
-
const
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
// locked - Prevent modification
|
|
8804
|
-
const locked = styleXml.includes('<w:locked/>') || styleXml.includes('<w:locked ');
|
|
8805
|
-
|
|
8806
|
-
// personal - User-specific style
|
|
8807
|
-
const personal = styleXml.includes('<w:personal/>') || styleXml.includes('<w:personal ');
|
|
8808
|
-
|
|
8809
|
-
// personalCompose - Style for composing new messages
|
|
8810
|
-
const personalCompose =
|
|
8811
|
-
styleXml.includes('<w:personalCompose/>') || styleXml.includes('<w:personalCompose ');
|
|
8812
|
-
|
|
8813
|
-
// personalReply - Style for replying to messages
|
|
8814
|
-
const personalReply =
|
|
8815
|
-
styleXml.includes('<w:personalReply/>') || styleXml.includes('<w:personalReply ');
|
|
9563
|
+
// Parse metadata CT_OnOff flags per ECMA-376 §17.7.4 (OnOffType bindings).
|
|
9564
|
+
// Each flag honours `w:val` so an explicit `<w:qFormat w:val="0"/>` override
|
|
9565
|
+
// of a based-on style's qFormat=true round-trips as `false`. The old code
|
|
9566
|
+
// detected presence via `styleXml.includes('<w:qFormat/>')` which ignored
|
|
9567
|
+
// w:val entirely and flipped any explicit-false to true.
|
|
9568
|
+
const parseStyleOnOffFlag = (tagName: string): boolean | undefined => {
|
|
9569
|
+
const els = XMLParser.extractElements(styleXml, tagName);
|
|
9570
|
+
if (els.length === 0 || !els[0]) return undefined;
|
|
9571
|
+
const v = XMLParser.extractAttribute(els[0], 'w:val');
|
|
9572
|
+
return parseOnOffAttribute(v, true);
|
|
9573
|
+
};
|
|
8816
9574
|
|
|
8817
|
-
|
|
8818
|
-
const
|
|
8819
|
-
|
|
9575
|
+
const qFormat = parseStyleOnOffFlag('w:qFormat');
|
|
9576
|
+
const semiHidden = parseStyleOnOffFlag('w:semiHidden');
|
|
9577
|
+
const unhideWhenUsed = parseStyleOnOffFlag('w:unhideWhenUsed');
|
|
9578
|
+
const locked = parseStyleOnOffFlag('w:locked');
|
|
9579
|
+
const personal = parseStyleOnOffFlag('w:personal');
|
|
9580
|
+
const personalCompose = parseStyleOnOffFlag('w:personalCompose');
|
|
9581
|
+
const personalReply = parseStyleOnOffFlag('w:personalReply');
|
|
9582
|
+
const autoRedefine = parseStyleOnOffFlag('w:autoRedefine');
|
|
9583
|
+
// `<w:hidden>` (CT_Style §17.7.4, OnOffOnlyType) — completely hide the
|
|
9584
|
+
// style. Previously not modeled; now round-trips as `properties.hidden`.
|
|
9585
|
+
const hidden = parseStyleOnOffFlag('w:hidden');
|
|
9586
|
+
|
|
9587
|
+
// `<w:rsid w:val="HEX"/>` (CT_Style §17.7.4, CT_LongHexNumber §17.18.50) —
|
|
9588
|
+
// revision-save ID stamp identifying the session in which this style
|
|
9589
|
+
// definition was last edited. Schema position: between `personalReply`
|
|
9590
|
+
// and `pPr`. Previously dropped entirely on parse, now preserved on
|
|
9591
|
+
// StyleProperties so round-trips stay faithful.
|
|
9592
|
+
let styleRsid: string | undefined;
|
|
9593
|
+
if (styleXml.includes('<w:rsid')) {
|
|
9594
|
+
const rsidTag = XMLParser.extractSelfClosingTag(styleXml, 'w:rsid');
|
|
9595
|
+
if (rsidTag) {
|
|
9596
|
+
const v = XMLParser.extractAttribute(`<w:rsid${rsidTag}`, 'w:val');
|
|
9597
|
+
if (v && v.length > 0) {
|
|
9598
|
+
styleRsid = v;
|
|
9599
|
+
}
|
|
9600
|
+
}
|
|
9601
|
+
}
|
|
8820
9602
|
|
|
8821
9603
|
// uiPriority - Sort order
|
|
8822
9604
|
let uiPriority: number | undefined;
|
|
@@ -8855,7 +9637,7 @@ export class DocumentParser {
|
|
|
8855
9637
|
}
|
|
8856
9638
|
|
|
8857
9639
|
// Parse table style properties (Phase 5.1)
|
|
8858
|
-
let tableStyle: import('../formatting/Style').TableStyleProperties | undefined;
|
|
9640
|
+
let tableStyle: import('../formatting/Style.js').TableStyleProperties | undefined;
|
|
8859
9641
|
if (typeAttr === 'table') {
|
|
8860
9642
|
tableStyle = this.parseTableStyleProperties(styleXml);
|
|
8861
9643
|
}
|
|
@@ -8867,21 +9649,27 @@ export class DocumentParser {
|
|
|
8867
9649
|
type: typeAttr,
|
|
8868
9650
|
basedOn,
|
|
8869
9651
|
next,
|
|
8870
|
-
|
|
8871
|
-
|
|
9652
|
+
// w:default and w:customStyle are ST_OnOff per ECMA-376 §17.17.4
|
|
9653
|
+
isDefault: parseOnOffAttribute(defaultAttr),
|
|
9654
|
+
customStyle: parseOnOffAttribute(customStyleAttr),
|
|
8872
9655
|
paragraphFormatting,
|
|
8873
9656
|
numPr: styleNumPr,
|
|
8874
9657
|
runFormatting,
|
|
8875
9658
|
tableStyle,
|
|
8876
|
-
// Metadata
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
9659
|
+
// Metadata CT_OnOff flags (ECMA-376 §17.7.4). parseStyleOnOffFlag returns
|
|
9660
|
+
// undefined when the element is absent, or the actual boolean (true/false)
|
|
9661
|
+
// when present — preserve both "explicit false" (override) and "absent"
|
|
9662
|
+
// (inherit) faithfully through the Style properties record.
|
|
9663
|
+
qFormat,
|
|
9664
|
+
semiHidden,
|
|
9665
|
+
hidden,
|
|
9666
|
+
unhideWhenUsed,
|
|
9667
|
+
locked,
|
|
9668
|
+
personal,
|
|
9669
|
+
personalCompose,
|
|
9670
|
+
personalReply,
|
|
9671
|
+
rsid: styleRsid,
|
|
9672
|
+
autoRedefine,
|
|
8885
9673
|
uiPriority,
|
|
8886
9674
|
link,
|
|
8887
9675
|
aliases,
|
|
@@ -8898,6 +9686,86 @@ export class DocumentParser {
|
|
|
8898
9686
|
private parseParagraphFormattingFromXml(pPrXml: string): ParagraphFormatting {
|
|
8899
9687
|
const formatting: ParagraphFormatting = {};
|
|
8900
9688
|
|
|
9689
|
+
// Parse framePr (text frame properties) per ECMA-376 Part 1 §17.3.1.11 —
|
|
9690
|
+
// CT_FramePr is a CT_PPrBase child (#5, between pageBreakBefore and
|
|
9691
|
+
// widowControl). Each attribute is independently optional; numeric
|
|
9692
|
+
// attributes (w/h/x/y/hSpace/vSpace/lines) may legitimately be zero
|
|
9693
|
+
// so use explicit string presence rather than truthy checks.
|
|
9694
|
+
const framePrTag = XMLParser.extractSelfClosingTag(pPrXml, 'w:framePr');
|
|
9695
|
+
if (framePrTag) {
|
|
9696
|
+
const fpStr = `<w:framePr${framePrTag}`;
|
|
9697
|
+
const frameProps: NonNullable<ParagraphFormatting['framePr']> = {};
|
|
9698
|
+
const wAttr = XMLParser.extractAttribute(fpStr, 'w:w');
|
|
9699
|
+
if (wAttr !== undefined) frameProps.w = parseInt(wAttr, 10);
|
|
9700
|
+
const hAttr = XMLParser.extractAttribute(fpStr, 'w:h');
|
|
9701
|
+
if (hAttr !== undefined) frameProps.h = parseInt(hAttr, 10);
|
|
9702
|
+
const hRule = XMLParser.extractAttribute(fpStr, 'w:hRule');
|
|
9703
|
+
if (hRule === 'auto' || hRule === 'atLeast' || hRule === 'exact') {
|
|
9704
|
+
frameProps.hRule = hRule;
|
|
9705
|
+
}
|
|
9706
|
+
const xAttr = XMLParser.extractAttribute(fpStr, 'w:x');
|
|
9707
|
+
if (xAttr !== undefined) frameProps.x = parseInt(xAttr, 10);
|
|
9708
|
+
const yAttr = XMLParser.extractAttribute(fpStr, 'w:y');
|
|
9709
|
+
if (yAttr !== undefined) frameProps.y = parseInt(yAttr, 10);
|
|
9710
|
+
const xAlign = XMLParser.extractAttribute(fpStr, 'w:xAlign');
|
|
9711
|
+
if (
|
|
9712
|
+
xAlign === 'left' ||
|
|
9713
|
+
xAlign === 'center' ||
|
|
9714
|
+
xAlign === 'right' ||
|
|
9715
|
+
xAlign === 'inside' ||
|
|
9716
|
+
xAlign === 'outside'
|
|
9717
|
+
) {
|
|
9718
|
+
frameProps.xAlign = xAlign;
|
|
9719
|
+
}
|
|
9720
|
+
const yAlign = XMLParser.extractAttribute(fpStr, 'w:yAlign');
|
|
9721
|
+
if (
|
|
9722
|
+
yAlign === 'top' ||
|
|
9723
|
+
yAlign === 'center' ||
|
|
9724
|
+
yAlign === 'bottom' ||
|
|
9725
|
+
yAlign === 'inline' ||
|
|
9726
|
+
yAlign === 'inside' ||
|
|
9727
|
+
yAlign === 'outside'
|
|
9728
|
+
) {
|
|
9729
|
+
frameProps.yAlign = yAlign;
|
|
9730
|
+
}
|
|
9731
|
+
const hAnchor = XMLParser.extractAttribute(fpStr, 'w:hAnchor');
|
|
9732
|
+
if (hAnchor === 'page' || hAnchor === 'margin' || hAnchor === 'text') {
|
|
9733
|
+
frameProps.hAnchor = hAnchor;
|
|
9734
|
+
}
|
|
9735
|
+
const vAnchor = XMLParser.extractAttribute(fpStr, 'w:vAnchor');
|
|
9736
|
+
if (vAnchor === 'page' || vAnchor === 'margin' || vAnchor === 'text') {
|
|
9737
|
+
frameProps.vAnchor = vAnchor;
|
|
9738
|
+
}
|
|
9739
|
+
const hSpace = XMLParser.extractAttribute(fpStr, 'w:hSpace');
|
|
9740
|
+
if (hSpace !== undefined) frameProps.hSpace = parseInt(hSpace, 10);
|
|
9741
|
+
const vSpace = XMLParser.extractAttribute(fpStr, 'w:vSpace');
|
|
9742
|
+
if (vSpace !== undefined) frameProps.vSpace = parseInt(vSpace, 10);
|
|
9743
|
+
const wrap = XMLParser.extractAttribute(fpStr, 'w:wrap');
|
|
9744
|
+
if (
|
|
9745
|
+
wrap === 'around' ||
|
|
9746
|
+
wrap === 'auto' ||
|
|
9747
|
+
wrap === 'none' ||
|
|
9748
|
+
wrap === 'notBeside' ||
|
|
9749
|
+
wrap === 'through' ||
|
|
9750
|
+
wrap === 'tight'
|
|
9751
|
+
) {
|
|
9752
|
+
frameProps.wrap = wrap;
|
|
9753
|
+
}
|
|
9754
|
+
const dropCap = XMLParser.extractAttribute(fpStr, 'w:dropCap');
|
|
9755
|
+
if (dropCap === 'none' || dropCap === 'drop' || dropCap === 'margin') {
|
|
9756
|
+
frameProps.dropCap = dropCap;
|
|
9757
|
+
}
|
|
9758
|
+
const lines = XMLParser.extractAttribute(fpStr, 'w:lines');
|
|
9759
|
+
if (lines !== undefined) frameProps.lines = parseInt(lines, 10);
|
|
9760
|
+
const anchorLock = XMLParser.extractAttribute(fpStr, 'w:anchorLock');
|
|
9761
|
+
if (anchorLock !== undefined) {
|
|
9762
|
+
frameProps.anchorLock = parseOnOffAttribute(anchorLock, true);
|
|
9763
|
+
}
|
|
9764
|
+
if (Object.keys(frameProps).length > 0) {
|
|
9765
|
+
formatting.framePr = frameProps;
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
|
|
8901
9769
|
// Parse alignment (w:jc)
|
|
8902
9770
|
const jcElement = XMLParser.extractSelfClosingTag(pPrXml, 'w:jc');
|
|
8903
9771
|
if (jcElement) {
|
|
@@ -8937,15 +9805,17 @@ export class DocumentParser {
|
|
|
8937
9805
|
lineRule: validatedLineRule,
|
|
8938
9806
|
beforeLines: beforeLines ? parseInt(beforeLines, 10) : undefined,
|
|
8939
9807
|
afterLines: afterLines ? parseInt(afterLines, 10) : undefined,
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
afterAutospacing: afterAutosp ? afterAutosp === '1' || afterAutosp === 'true' : undefined,
|
|
9808
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
9809
|
+
beforeAutospacing: beforeAutosp ? parseOnOffAttribute(beforeAutosp) : undefined,
|
|
9810
|
+
afterAutospacing: afterAutosp ? parseOnOffAttribute(afterAutosp) : undefined,
|
|
8944
9811
|
};
|
|
8945
9812
|
}
|
|
8946
9813
|
|
|
8947
9814
|
// Parse indentation (w:ind)
|
|
8948
|
-
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to
|
|
9815
|
+
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to
|
|
9816
|
+
// w:left/w:right. §17.3.1.12 also defines six CJK character-unit variants
|
|
9817
|
+
// (ST_DecimalNumber) — parse those alongside so styles authored in CJK
|
|
9818
|
+
// locales preserve their character-unit indent spec through round-trip.
|
|
8949
9819
|
const indElement = XMLParser.extractSelfClosingTag(pPrXml, 'w:ind');
|
|
8950
9820
|
if (indElement) {
|
|
8951
9821
|
const indTag = `<w:ind${indElement}`;
|
|
@@ -8955,33 +9825,137 @@ export class DocumentParser {
|
|
|
8955
9825
|
const right = XMLParser.extractAttribute(indTag, 'w:right');
|
|
8956
9826
|
const firstLine = XMLParser.extractAttribute(indTag, 'w:firstLine');
|
|
8957
9827
|
const hanging = XMLParser.extractAttribute(indTag, 'w:hanging');
|
|
9828
|
+
// CJK character-unit variants. startChars/endChars collapse to
|
|
9829
|
+
// leftChars/rightChars (same bidi-aware rule as the twips pair).
|
|
9830
|
+
const startChars = XMLParser.extractAttribute(indTag, 'w:startChars');
|
|
9831
|
+
const leftChars = XMLParser.extractAttribute(indTag, 'w:leftChars');
|
|
9832
|
+
const endChars = XMLParser.extractAttribute(indTag, 'w:endChars');
|
|
9833
|
+
const rightChars = XMLParser.extractAttribute(indTag, 'w:rightChars');
|
|
9834
|
+
const firstLineChars = XMLParser.extractAttribute(indTag, 'w:firstLineChars');
|
|
9835
|
+
const hangingChars = XMLParser.extractAttribute(indTag, 'w:hangingChars');
|
|
8958
9836
|
|
|
8959
9837
|
const leftVal = start || left;
|
|
8960
9838
|
const rightVal = end || right;
|
|
9839
|
+
const leftCharsVal = startChars || leftChars;
|
|
9840
|
+
const rightCharsVal = endChars || rightChars;
|
|
8961
9841
|
|
|
8962
9842
|
formatting.indentation = {
|
|
8963
9843
|
left: leftVal ? parseInt(leftVal, 10) : undefined,
|
|
8964
9844
|
right: rightVal ? parseInt(rightVal, 10) : undefined,
|
|
8965
9845
|
firstLine: firstLine ? parseInt(firstLine, 10) : undefined,
|
|
8966
9846
|
hanging: hanging ? parseInt(hanging, 10) : undefined,
|
|
9847
|
+
leftChars: leftCharsVal ? parseInt(leftCharsVal, 10) : undefined,
|
|
9848
|
+
rightChars: rightCharsVal ? parseInt(rightCharsVal, 10) : undefined,
|
|
9849
|
+
firstLineChars: firstLineChars ? parseInt(firstLineChars, 10) : undefined,
|
|
9850
|
+
hangingChars: hangingChars ? parseInt(hangingChars, 10) : undefined,
|
|
8967
9851
|
};
|
|
8968
9852
|
}
|
|
8969
9853
|
|
|
8970
|
-
// Parse boolean
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
9854
|
+
// Parse CT_OnOff boolean flags per ECMA-376 §17.17.4 / §17.3.1. The previous
|
|
9855
|
+
// substring-only detection (`pPrXml.includes('<w:keepNext/>') ||
|
|
9856
|
+
// pPrXml.includes('<w:keepNext ')`) hard-coded the flag to true whenever
|
|
9857
|
+
// the element appeared at all — silently flipping `<w:keepNext w:val="0"/>`
|
|
9858
|
+
// (explicit override) into an enabled flag. Read w:val when present and
|
|
9859
|
+
// honour every ST_OnOff literal (1/0/true/false/on/off).
|
|
9860
|
+
const parseStylePPrCtOnOff = (tagName: string): boolean | undefined => {
|
|
9861
|
+
// extractSelfClosingTag returns the ATTRIBUTE STRING (possibly empty)
|
|
9862
|
+
// when found, or `undefined` when absent. Earlier this helper checked
|
|
9863
|
+
// `=== null` by mistake — that let the "absent" case fall through and
|
|
9864
|
+
// construct a garbage tag that produced `true`, silently enabling the
|
|
9865
|
+
// flag on every style that didn't set it.
|
|
9866
|
+
const el = XMLParser.extractSelfClosingTag(pPrXml, tagName);
|
|
9867
|
+
if (el === undefined) return undefined;
|
|
9868
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
9869
|
+
if (val === undefined) return true;
|
|
9870
|
+
return parseOnOffAttribute(val, true);
|
|
9871
|
+
};
|
|
9872
|
+
|
|
9873
|
+
const keepNextVal = parseStylePPrCtOnOff('w:keepNext');
|
|
9874
|
+
if (keepNextVal !== undefined) formatting.keepNext = keepNextVal;
|
|
9875
|
+
|
|
9876
|
+
const keepLinesVal = parseStylePPrCtOnOff('w:keepLines');
|
|
9877
|
+
if (keepLinesVal !== undefined) formatting.keepLines = keepLinesVal;
|
|
9878
|
+
|
|
9879
|
+
const pageBreakBeforeVal = parseStylePPrCtOnOff('w:pageBreakBefore');
|
|
9880
|
+
if (pageBreakBeforeVal !== undefined) formatting.pageBreakBefore = pageBreakBeforeVal;
|
|
9881
|
+
|
|
9882
|
+
// Contextual spacing per ECMA-376 Part 1 §17.3.1.9
|
|
9883
|
+
// "Don't add space between paragraphs of the same style"
|
|
9884
|
+
const contextualSpacingVal = parseStylePPrCtOnOff('w:contextualSpacing');
|
|
9885
|
+
if (contextualSpacingVal !== undefined) formatting.contextualSpacing = contextualSpacingVal;
|
|
9886
|
+
|
|
9887
|
+
// Remaining CT_PPrBase CT_OnOff flags per ECMA-376 Part 1 §17.3.1.
|
|
9888
|
+
// The main paragraph parser handles all of these; the style-level parser
|
|
9889
|
+
// previously dropped them (substring matches existed only for the four
|
|
9890
|
+
// flags above). Any style using the explicit-false form to override a
|
|
9891
|
+
// based-on style's enabled flag was silently losing the override.
|
|
9892
|
+
const widowControlVal = parseStylePPrCtOnOff('w:widowControl');
|
|
9893
|
+
if (widowControlVal !== undefined) formatting.widowControl = widowControlVal;
|
|
9894
|
+
|
|
9895
|
+
const suppressLineNumbersVal = parseStylePPrCtOnOff('w:suppressLineNumbers');
|
|
9896
|
+
if (suppressLineNumbersVal !== undefined)
|
|
9897
|
+
formatting.suppressLineNumbers = suppressLineNumbersVal;
|
|
9898
|
+
|
|
9899
|
+
const bidiVal = parseStylePPrCtOnOff('w:bidi');
|
|
9900
|
+
if (bidiVal !== undefined) formatting.bidi = bidiVal;
|
|
9901
|
+
|
|
9902
|
+
const mirrorIndentsVal = parseStylePPrCtOnOff('w:mirrorIndents');
|
|
9903
|
+
if (mirrorIndentsVal !== undefined) formatting.mirrorIndents = mirrorIndentsVal;
|
|
9904
|
+
|
|
9905
|
+
const adjustRightIndVal = parseStylePPrCtOnOff('w:adjustRightInd');
|
|
9906
|
+
if (adjustRightIndVal !== undefined) formatting.adjustRightInd = adjustRightIndVal;
|
|
9907
|
+
|
|
9908
|
+
const suppressAutoHyphensVal = parseStylePPrCtOnOff('w:suppressAutoHyphens');
|
|
9909
|
+
if (suppressAutoHyphensVal !== undefined)
|
|
9910
|
+
formatting.suppressAutoHyphens = suppressAutoHyphensVal;
|
|
9911
|
+
|
|
9912
|
+
const kinsokuVal = parseStylePPrCtOnOff('w:kinsoku');
|
|
9913
|
+
if (kinsokuVal !== undefined) formatting.kinsoku = kinsokuVal;
|
|
9914
|
+
|
|
9915
|
+
const wordWrapVal = parseStylePPrCtOnOff('w:wordWrap');
|
|
9916
|
+
if (wordWrapVal !== undefined) formatting.wordWrap = wordWrapVal;
|
|
9917
|
+
|
|
9918
|
+
const overflowPunctVal = parseStylePPrCtOnOff('w:overflowPunct');
|
|
9919
|
+
if (overflowPunctVal !== undefined) formatting.overflowPunct = overflowPunctVal;
|
|
9920
|
+
|
|
9921
|
+
const topLinePunctVal = parseStylePPrCtOnOff('w:topLinePunct');
|
|
9922
|
+
if (topLinePunctVal !== undefined) formatting.topLinePunct = topLinePunctVal;
|
|
9923
|
+
|
|
9924
|
+
const autoSpaceDEVal = parseStylePPrCtOnOff('w:autoSpaceDE');
|
|
9925
|
+
if (autoSpaceDEVal !== undefined) formatting.autoSpaceDE = autoSpaceDEVal;
|
|
9926
|
+
|
|
9927
|
+
const autoSpaceDNVal = parseStylePPrCtOnOff('w:autoSpaceDN');
|
|
9928
|
+
if (autoSpaceDNVal !== undefined) formatting.autoSpaceDN = autoSpaceDNVal;
|
|
9929
|
+
|
|
9930
|
+
const suppressOverlapVal = parseStylePPrCtOnOff('w:suppressOverlap');
|
|
9931
|
+
if (suppressOverlapVal !== undefined) formatting.suppressOverlap = suppressOverlapVal;
|
|
9932
|
+
|
|
9933
|
+
// Parse `w:val`-attribute string-enum children per CT_PPrBase.
|
|
9934
|
+
// Position #28 textDirection (ST_TextDirection), #29 textAlignment
|
|
9935
|
+
// (ST_TextAlignment), #30 textboxTightWrap (ST_TextboxTightWrapType).
|
|
9936
|
+
// The main paragraph parser handles these; the style-level parser
|
|
9937
|
+
// previously dropped them because the substring scan was never
|
|
9938
|
+
// extended past the iteration-25 CT_OnOff helper.
|
|
9939
|
+
const parseStylePPrValAttr = (tagName: string): string | undefined => {
|
|
9940
|
+
const el = XMLParser.extractSelfClosingTag(pPrXml, tagName);
|
|
9941
|
+
if (el === undefined) return undefined;
|
|
9942
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
9943
|
+
return val === undefined ? undefined : String(val);
|
|
9944
|
+
};
|
|
9945
|
+
|
|
9946
|
+
const textDirectionVal = parseStylePPrValAttr('w:textDirection');
|
|
9947
|
+
if (textDirectionVal !== undefined) {
|
|
9948
|
+
formatting.textDirection = textDirectionVal as ParagraphFormatting['textDirection'];
|
|
8976
9949
|
}
|
|
8977
|
-
|
|
8978
|
-
|
|
9950
|
+
|
|
9951
|
+
const textAlignmentVal = parseStylePPrValAttr('w:textAlignment');
|
|
9952
|
+
if (textAlignmentVal !== undefined) {
|
|
9953
|
+
formatting.textAlignment = textAlignmentVal as ParagraphFormatting['textAlignment'];
|
|
8979
9954
|
}
|
|
8980
9955
|
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
formatting.contextualSpacing = true;
|
|
9956
|
+
const textboxTightWrapVal = parseStylePPrValAttr('w:textboxTightWrap');
|
|
9957
|
+
if (textboxTightWrapVal !== undefined) {
|
|
9958
|
+
formatting.textboxTightWrap = textboxTightWrapVal as ParagraphFormatting['textboxTightWrap'];
|
|
8985
9959
|
}
|
|
8986
9960
|
|
|
8987
9961
|
// Parse outline level (w:outlineLvl) - used for TOC generation
|
|
@@ -8997,7 +9971,29 @@ export class DocumentParser {
|
|
|
8997
9971
|
}
|
|
8998
9972
|
}
|
|
8999
9973
|
|
|
9000
|
-
// Parse
|
|
9974
|
+
// Parse divId (CT_PPrBase #32, §17.3.1.10) — numeric HTML div
|
|
9975
|
+
// association. Previously dropped on the style parser; the main
|
|
9976
|
+
// paragraph parser reads it at the pPrObj level but the style pPr
|
|
9977
|
+
// parser used string-based extraction and skipped both divId and
|
|
9978
|
+
// cnfStyle below.
|
|
9979
|
+
const divIdVal = parseStylePPrValAttr('w:divId');
|
|
9980
|
+
if (divIdVal !== undefined) {
|
|
9981
|
+
const parsedDivId = parseInt(divIdVal, 10);
|
|
9982
|
+
if (!isNaN(parsedDivId)) formatting.divId = parsedDivId;
|
|
9983
|
+
}
|
|
9984
|
+
|
|
9985
|
+
// Parse cnfStyle (CT_PPrBase #33, §17.3.1.8) — conditional formatting
|
|
9986
|
+
// bitmask string (12-char 0/1 sequence, e.g. "100000000100").
|
|
9987
|
+
const cnfStyleVal = parseStylePPrValAttr('w:cnfStyle');
|
|
9988
|
+
if (cnfStyleVal !== undefined) formatting.cnfStyle = cnfStyleVal;
|
|
9989
|
+
|
|
9990
|
+
// Parse paragraph borders (w:pBdr) per ECMA-376 Part 1 §17.3.1.24.
|
|
9991
|
+
// Covers the full CT_Border attribute set (§17.18.2): val, sz, space,
|
|
9992
|
+
// color, themeColor, themeTint, themeShade, shadow, frame. The style
|
|
9993
|
+
// *emitter* already round-trips all nine, so any style-pBdr authored
|
|
9994
|
+
// by Word with themed or shadow/frame attributes was silently flattened
|
|
9995
|
+
// here before this fix. Shadow/frame route through parseOnOffAttribute
|
|
9996
|
+
// so ST_OnOff literals ("on"/"off"/"1"/"0"/"true"/"false") resolve.
|
|
9001
9997
|
const pBdrXml = XMLParser.extractBetweenTags(pPrXml, '<w:pBdr>', '</w:pBdr>');
|
|
9002
9998
|
if (pBdrXml) {
|
|
9003
9999
|
const borders: any = {};
|
|
@@ -9011,11 +10007,21 @@ export class DocumentParser {
|
|
|
9011
10007
|
const size = XMLParser.extractAttribute(bTag, 'w:sz');
|
|
9012
10008
|
const space = XMLParser.extractAttribute(bTag, 'w:space');
|
|
9013
10009
|
const color = XMLParser.extractAttribute(bTag, 'w:color');
|
|
10010
|
+
const themeColor = XMLParser.extractAttribute(bTag, 'w:themeColor');
|
|
10011
|
+
const themeTint = XMLParser.extractAttribute(bTag, 'w:themeTint');
|
|
10012
|
+
const themeShade = XMLParser.extractAttribute(bTag, 'w:themeShade');
|
|
10013
|
+
const shadow = XMLParser.extractAttribute(bTag, 'w:shadow');
|
|
10014
|
+
const frame = XMLParser.extractAttribute(bTag, 'w:frame');
|
|
9014
10015
|
const border: any = {};
|
|
9015
10016
|
if (style) border.style = style;
|
|
9016
10017
|
if (size) border.size = parseInt(size, 10);
|
|
9017
10018
|
if (space) border.space = parseInt(space, 10);
|
|
9018
10019
|
if (color) border.color = color;
|
|
10020
|
+
if (themeColor) border.themeColor = themeColor;
|
|
10021
|
+
if (themeTint) border.themeTint = themeTint;
|
|
10022
|
+
if (themeShade) border.themeShade = themeShade;
|
|
10023
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
10024
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
9019
10025
|
if (Object.keys(border).length > 0) borders[type] = border;
|
|
9020
10026
|
}
|
|
9021
10027
|
}
|
|
@@ -9062,37 +10068,113 @@ export class DocumentParser {
|
|
|
9062
10068
|
private parseRunFormattingFromXml(rPrXml: string): RunFormatting {
|
|
9063
10069
|
const formatting: RunFormatting = {};
|
|
9064
10070
|
|
|
9065
|
-
//
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
10071
|
+
// CT_OnOff rPr children per ECMA-376 §17.3.2. Previously detected via
|
|
10072
|
+
// substring-include which hard-coded the flag to `true` whenever the
|
|
10073
|
+
// element appeared — silently flipping `<w:b w:val="0"/>` (explicit
|
|
10074
|
+
// override of a based-on style's bold) into an enabled flag, and
|
|
10075
|
+
// never setting the field to `false` for legitimate overrides.
|
|
10076
|
+
// Mirrors the pPr `parseStylePPrCtOnOff` helper introduced in
|
|
10077
|
+
// iteration 25 / 26.
|
|
10078
|
+
const parseStyleRPrCtOnOff = (tagName: string): boolean | undefined => {
|
|
10079
|
+
const el = XMLParser.extractSelfClosingTag(rPrXml, tagName);
|
|
10080
|
+
if (el === undefined) return undefined;
|
|
10081
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
10082
|
+
if (val === undefined) return true;
|
|
10083
|
+
return parseOnOffAttribute(val, true);
|
|
10084
|
+
};
|
|
10085
|
+
|
|
10086
|
+
const boldVal = parseStyleRPrCtOnOff('w:b');
|
|
10087
|
+
if (boldVal !== undefined) formatting.bold = boldVal;
|
|
9081
10088
|
|
|
9082
|
-
|
|
10089
|
+
const italicVal = parseStyleRPrCtOnOff('w:i');
|
|
10090
|
+
if (italicVal !== undefined) formatting.italic = italicVal;
|
|
10091
|
+
|
|
10092
|
+
const strikeVal = parseStyleRPrCtOnOff('w:strike');
|
|
10093
|
+
if (strikeVal !== undefined) formatting.strike = strikeVal;
|
|
10094
|
+
|
|
10095
|
+
const smallCapsVal = parseStyleRPrCtOnOff('w:smallCaps');
|
|
10096
|
+
if (smallCapsVal !== undefined) formatting.smallCaps = smallCapsVal;
|
|
10097
|
+
|
|
10098
|
+
const allCapsVal = parseStyleRPrCtOnOff('w:caps');
|
|
10099
|
+
if (allCapsVal !== undefined) formatting.allCaps = allCapsVal;
|
|
10100
|
+
|
|
10101
|
+
// Extended CT_OnOff run children per ECMA-376 §17.3.2. The style-level
|
|
10102
|
+
// rPr parser previously dropped all of these silently, so character
|
|
10103
|
+
// styles setting dstrike, outline, shadow, emboss, imprint, rtl,
|
|
10104
|
+
// vanish, noProof, snapToGrid, specVanish, webHidden, or complex-script
|
|
10105
|
+
// variants (bCs / iCs / cs) lost their overrides on programmatic save.
|
|
10106
|
+
const boldCsVal = parseStyleRPrCtOnOff('w:bCs');
|
|
10107
|
+
if (boldCsVal !== undefined) formatting.complexScriptBold = boldCsVal;
|
|
10108
|
+
|
|
10109
|
+
const italicCsVal = parseStyleRPrCtOnOff('w:iCs');
|
|
10110
|
+
if (italicCsVal !== undefined) formatting.complexScriptItalic = italicCsVal;
|
|
10111
|
+
|
|
10112
|
+
const csVal = parseStyleRPrCtOnOff('w:cs');
|
|
10113
|
+
if (csVal !== undefined) formatting.complexScript = csVal;
|
|
10114
|
+
|
|
10115
|
+
const dstrikeVal = parseStyleRPrCtOnOff('w:dstrike');
|
|
10116
|
+
if (dstrikeVal !== undefined) formatting.dstrike = dstrikeVal;
|
|
10117
|
+
|
|
10118
|
+
const outlineVal = parseStyleRPrCtOnOff('w:outline');
|
|
10119
|
+
if (outlineVal !== undefined) formatting.outline = outlineVal;
|
|
10120
|
+
|
|
10121
|
+
const shadowVal = parseStyleRPrCtOnOff('w:shadow');
|
|
10122
|
+
if (shadowVal !== undefined) formatting.shadow = shadowVal;
|
|
10123
|
+
|
|
10124
|
+
const embossVal = parseStyleRPrCtOnOff('w:emboss');
|
|
10125
|
+
if (embossVal !== undefined) formatting.emboss = embossVal;
|
|
10126
|
+
|
|
10127
|
+
const imprintVal = parseStyleRPrCtOnOff('w:imprint');
|
|
10128
|
+
if (imprintVal !== undefined) formatting.imprint = imprintVal;
|
|
10129
|
+
|
|
10130
|
+
const rtlVal = parseStyleRPrCtOnOff('w:rtl');
|
|
10131
|
+
if (rtlVal !== undefined) formatting.rtl = rtlVal;
|
|
10132
|
+
|
|
10133
|
+
const vanishVal = parseStyleRPrCtOnOff('w:vanish');
|
|
10134
|
+
if (vanishVal !== undefined) formatting.vanish = vanishVal;
|
|
10135
|
+
|
|
10136
|
+
const noProofVal = parseStyleRPrCtOnOff('w:noProof');
|
|
10137
|
+
if (noProofVal !== undefined) formatting.noProof = noProofVal;
|
|
10138
|
+
|
|
10139
|
+
const snapToGridVal = parseStyleRPrCtOnOff('w:snapToGrid');
|
|
10140
|
+
if (snapToGridVal !== undefined) formatting.snapToGrid = snapToGridVal;
|
|
10141
|
+
|
|
10142
|
+
const specVanishVal = parseStyleRPrCtOnOff('w:specVanish');
|
|
10143
|
+
if (specVanishVal !== undefined) formatting.specVanish = specVanishVal;
|
|
10144
|
+
|
|
10145
|
+
const webHiddenVal = parseStyleRPrCtOnOff('w:webHidden');
|
|
10146
|
+
if (webHiddenVal !== undefined) formatting.webHidden = webHiddenVal;
|
|
10147
|
+
|
|
10148
|
+
// Parse underline — all attributes per ECMA-376 §17.3.2.40.
|
|
10149
|
+
// Whitelist covers the full ST_Underline enumeration (18 values);
|
|
10150
|
+
// unknown / out-of-spec values fall through to `underline = true`
|
|
10151
|
+
// (underline enabled with default style) to match the main parser.
|
|
9083
10152
|
const uElement = XMLParser.extractSelfClosingTag(rPrXml, 'w:u');
|
|
9084
10153
|
if (uElement) {
|
|
9085
10154
|
const uTag = `<w:u${uElement}`;
|
|
9086
10155
|
const uVal = XMLParser.extractAttribute(uTag, 'w:val');
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
10156
|
+
const ST_UNDERLINE = new Set<string>([
|
|
10157
|
+
'single',
|
|
10158
|
+
'words',
|
|
10159
|
+
'double',
|
|
10160
|
+
'thick',
|
|
10161
|
+
'dotted',
|
|
10162
|
+
'dottedHeavy',
|
|
10163
|
+
'dash',
|
|
10164
|
+
'dashedHeavy',
|
|
10165
|
+
'dashLong',
|
|
10166
|
+
'dashLongHeavy',
|
|
10167
|
+
'dotDash',
|
|
10168
|
+
'dashDotHeavy',
|
|
10169
|
+
'dotDotDash',
|
|
10170
|
+
'dashDotDotHeavy',
|
|
10171
|
+
'wave',
|
|
10172
|
+
'wavyHeavy',
|
|
10173
|
+
'wavyDouble',
|
|
10174
|
+
'none',
|
|
10175
|
+
]);
|
|
10176
|
+
if (uVal !== undefined && ST_UNDERLINE.has(String(uVal))) {
|
|
10177
|
+
formatting.underline = String(uVal) as RunFormatting['underline'];
|
|
9096
10178
|
} else {
|
|
9097
10179
|
formatting.underline = true;
|
|
9098
10180
|
}
|
|
@@ -9100,7 +10182,8 @@ export class DocumentParser {
|
|
|
9100
10182
|
if (uColor) formatting.underlineColor = uColor;
|
|
9101
10183
|
const uThemeColor = XMLParser.extractAttribute(uTag, 'w:themeColor');
|
|
9102
10184
|
if (uThemeColor) {
|
|
9103
|
-
formatting.underlineThemeColor =
|
|
10185
|
+
formatting.underlineThemeColor =
|
|
10186
|
+
uThemeColor as import('../elements/Run.js').ThemeColorValue;
|
|
9104
10187
|
}
|
|
9105
10188
|
const uThemeTint = XMLParser.extractAttribute(uTag, 'w:themeTint');
|
|
9106
10189
|
if (uThemeTint) formatting.underlineThemeTint = parseInt(uThemeTint, 16);
|
|
@@ -9167,17 +10250,25 @@ export class DocumentParser {
|
|
|
9167
10250
|
}
|
|
9168
10251
|
}
|
|
9169
10252
|
|
|
9170
|
-
// Parse color (w:color) — all attributes per ECMA-376 §17.3.2.6
|
|
10253
|
+
// Parse color (w:color) — all attributes per ECMA-376 §17.3.2.6 / ST_HexColor
|
|
10254
|
+
// per §17.18.38. `w:val="auto"` is a valid ST_HexColorAuto sentinel that
|
|
10255
|
+
// tells Word to use the automatic/window text color; the previous parser
|
|
10256
|
+
// dropped it (only storing non-auto hex values), so a style-level rPr with
|
|
10257
|
+
// `<w:color w:val="auto"/>` silently lost that marker on round-trip and
|
|
10258
|
+
// the emitter defaulted to `"000000"` — changing the rendering of any
|
|
10259
|
+
// style that relied on the auto fallback. Preserve the literal "auto" so
|
|
10260
|
+
// it survives through emission. (Matches the object-format parser path
|
|
10261
|
+
// for direct-run rPr at parseRunFromObject line ~5210.)
|
|
9171
10262
|
const colorElement = XMLParser.extractSelfClosingTag(rPrXml, 'w:color');
|
|
9172
10263
|
if (colorElement) {
|
|
9173
10264
|
const colorTag = `<w:color${colorElement}`;
|
|
9174
10265
|
const val = XMLParser.extractAttribute(colorTag, 'w:val');
|
|
9175
|
-
if (val
|
|
10266
|
+
if (val) {
|
|
9176
10267
|
formatting.color = val;
|
|
9177
10268
|
}
|
|
9178
10269
|
const themeColor = XMLParser.extractAttribute(colorTag, 'w:themeColor');
|
|
9179
10270
|
if (themeColor) {
|
|
9180
|
-
formatting.themeColor = themeColor as import('../elements/Run').ThemeColorValue;
|
|
10271
|
+
formatting.themeColor = themeColor as import('../elements/Run.js').ThemeColorValue;
|
|
9181
10272
|
}
|
|
9182
10273
|
const themeTint = XMLParser.extractAttribute(colorTag, 'w:themeTint');
|
|
9183
10274
|
if (themeTint) {
|
|
@@ -9242,6 +10333,186 @@ export class DocumentParser {
|
|
|
9242
10333
|
formatting.shading = shading;
|
|
9243
10334
|
}
|
|
9244
10335
|
|
|
10336
|
+
// Character spacing (w:spacing §17.3.2.35, ST_SignedTwipsMeasure) —
|
|
10337
|
+
// previously dropped on the style parser; 0 and negative values are
|
|
10338
|
+
// valid per spec.
|
|
10339
|
+
const spacingEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:spacing');
|
|
10340
|
+
if (spacingEl !== undefined) {
|
|
10341
|
+
const val = XMLParser.extractAttribute(`<w:spacing${spacingEl}`, 'w:val');
|
|
10342
|
+
if (val !== undefined) {
|
|
10343
|
+
const n = parseInt(String(val), 10);
|
|
10344
|
+
if (!isNaN(n)) formatting.characterSpacing = n;
|
|
10345
|
+
}
|
|
10346
|
+
}
|
|
10347
|
+
|
|
10348
|
+
// Vertical position (w:position §17.3.2.31, ST_SignedHpsMeasure).
|
|
10349
|
+
// 0 = baseline (explicit reset); negative = lowered; positive = raised.
|
|
10350
|
+
const positionEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:position');
|
|
10351
|
+
if (positionEl !== undefined) {
|
|
10352
|
+
const val = XMLParser.extractAttribute(`<w:position${positionEl}`, 'w:val');
|
|
10353
|
+
if (val !== undefined) {
|
|
10354
|
+
const n = parseInt(String(val), 10);
|
|
10355
|
+
if (!isNaN(n)) formatting.position = n;
|
|
10356
|
+
}
|
|
10357
|
+
}
|
|
10358
|
+
|
|
10359
|
+
// Kerning threshold (w:kern §17.3.2.20, ST_HpsMeasure). 0 = kern at
|
|
10360
|
+
// every size (no minimum font-size threshold).
|
|
10361
|
+
const kernEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:kern');
|
|
10362
|
+
if (kernEl !== undefined) {
|
|
10363
|
+
const val = XMLParser.extractAttribute(`<w:kern${kernEl}`, 'w:val');
|
|
10364
|
+
if (val !== undefined) {
|
|
10365
|
+
const n = parseInt(String(val), 10);
|
|
10366
|
+
if (!isNaN(n)) formatting.kerning = n;
|
|
10367
|
+
}
|
|
10368
|
+
}
|
|
10369
|
+
|
|
10370
|
+
// Language (w:lang §17.3.2.20, CT_Language). Single val → plain string;
|
|
10371
|
+
// multi-script (eastAsia and/or bidi present) → LanguageConfig object.
|
|
10372
|
+
const langEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:lang');
|
|
10373
|
+
if (langEl !== undefined) {
|
|
10374
|
+
const langTag = `<w:lang${langEl}`;
|
|
10375
|
+
const val = XMLParser.extractAttribute(langTag, 'w:val');
|
|
10376
|
+
const eastAsia = XMLParser.extractAttribute(langTag, 'w:eastAsia');
|
|
10377
|
+
const bidi = XMLParser.extractAttribute(langTag, 'w:bidi');
|
|
10378
|
+
if (eastAsia || bidi) {
|
|
10379
|
+
formatting.language = {
|
|
10380
|
+
val: val ? String(val) : undefined,
|
|
10381
|
+
eastAsia: eastAsia ? String(eastAsia) : undefined,
|
|
10382
|
+
bidi: bidi ? String(bidi) : undefined,
|
|
10383
|
+
};
|
|
10384
|
+
} else if (val) {
|
|
10385
|
+
formatting.language = String(val);
|
|
10386
|
+
}
|
|
10387
|
+
}
|
|
10388
|
+
|
|
10389
|
+
// Horizontal scaling (w:w §17.3.2.43, ST_TextScale — percentage,
|
|
10390
|
+
// min 1 per spec, so 0 is not valid and we keep a truthy check).
|
|
10391
|
+
const scaleEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:w');
|
|
10392
|
+
if (scaleEl !== undefined) {
|
|
10393
|
+
const val = XMLParser.extractAttribute(`<w:w${scaleEl}`, 'w:val');
|
|
10394
|
+
if (val) {
|
|
10395
|
+
const n = parseInt(String(val), 10);
|
|
10396
|
+
if (!isNaN(n)) formatting.scaling = n;
|
|
10397
|
+
}
|
|
10398
|
+
}
|
|
10399
|
+
|
|
10400
|
+
// Emphasis mark (w:em §17.3.2.13, ST_Em — "dot"/"comma"/"circle"/
|
|
10401
|
+
// "underDot"/"none"). Commonly paired with East Asian typography.
|
|
10402
|
+
const emEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:em');
|
|
10403
|
+
if (emEl !== undefined) {
|
|
10404
|
+
const val = XMLParser.extractAttribute(`<w:em${emEl}`, 'w:val');
|
|
10405
|
+
if (val) {
|
|
10406
|
+
formatting.emphasis = String(val) as RunFormatting['emphasis'];
|
|
10407
|
+
}
|
|
10408
|
+
}
|
|
10409
|
+
|
|
10410
|
+
// Animated text effect (w:effect §17.3.2.12, ST_TextEffect —
|
|
10411
|
+
// "blinkBackground"/"lights"/"antsBlack"/"antsRed"/"shimmer"/"sparkle"/
|
|
10412
|
+
// "none"). Legacy feature but still valid per schema.
|
|
10413
|
+
const effectEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:effect');
|
|
10414
|
+
if (effectEl !== undefined) {
|
|
10415
|
+
const val = XMLParser.extractAttribute(`<w:effect${effectEl}`, 'w:val');
|
|
10416
|
+
if (val) {
|
|
10417
|
+
formatting.effect = String(val) as RunFormatting['effect'];
|
|
10418
|
+
}
|
|
10419
|
+
}
|
|
10420
|
+
|
|
10421
|
+
// Text border (w:bdr §17.3.2.5) — character/run border. Full CT_Border
|
|
10422
|
+
// attribute set (§17.18.2): val / sz / space / color / themeColor /
|
|
10423
|
+
// themeTint / themeShade / shadow / frame.
|
|
10424
|
+
const bdrEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:bdr');
|
|
10425
|
+
if (bdrEl !== undefined) {
|
|
10426
|
+
const bdrTag = `<w:bdr${bdrEl}`;
|
|
10427
|
+
const border: {
|
|
10428
|
+
style?: string;
|
|
10429
|
+
size?: number;
|
|
10430
|
+
color?: string;
|
|
10431
|
+
space?: number;
|
|
10432
|
+
themeColor?: string;
|
|
10433
|
+
themeTint?: string;
|
|
10434
|
+
themeShade?: string;
|
|
10435
|
+
shadow?: boolean;
|
|
10436
|
+
frame?: boolean;
|
|
10437
|
+
} = {};
|
|
10438
|
+
const val = XMLParser.extractAttribute(bdrTag, 'w:val');
|
|
10439
|
+
if (val) border.style = String(val);
|
|
10440
|
+
const sz = XMLParser.extractAttribute(bdrTag, 'w:sz');
|
|
10441
|
+
if (sz !== undefined) {
|
|
10442
|
+
const n = parseInt(String(sz), 10);
|
|
10443
|
+
if (!isNaN(n)) border.size = n;
|
|
10444
|
+
}
|
|
10445
|
+
const color = XMLParser.extractAttribute(bdrTag, 'w:color');
|
|
10446
|
+
if (color) border.color = String(color);
|
|
10447
|
+
const space = XMLParser.extractAttribute(bdrTag, 'w:space');
|
|
10448
|
+
if (space !== undefined) {
|
|
10449
|
+
const n = parseInt(String(space), 10);
|
|
10450
|
+
if (!isNaN(n)) border.space = n;
|
|
10451
|
+
}
|
|
10452
|
+
const themeColor = XMLParser.extractAttribute(bdrTag, 'w:themeColor');
|
|
10453
|
+
if (themeColor) border.themeColor = String(themeColor);
|
|
10454
|
+
const themeTint = XMLParser.extractAttribute(bdrTag, 'w:themeTint');
|
|
10455
|
+
if (themeTint) border.themeTint = String(themeTint);
|
|
10456
|
+
const themeShade = XMLParser.extractAttribute(bdrTag, 'w:themeShade');
|
|
10457
|
+
if (themeShade) border.themeShade = String(themeShade);
|
|
10458
|
+
const shadow = XMLParser.extractAttribute(bdrTag, 'w:shadow');
|
|
10459
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
10460
|
+
const frame = XMLParser.extractAttribute(bdrTag, 'w:frame');
|
|
10461
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
10462
|
+
if (Object.keys(border).length > 0) {
|
|
10463
|
+
formatting.border = border as RunFormatting['border'];
|
|
10464
|
+
}
|
|
10465
|
+
}
|
|
10466
|
+
|
|
10467
|
+
// Manual run width (w:fitText §17.3.2.15). Value is twips; 0 is
|
|
10468
|
+
// technically representable as "explicit zero" — use `!== undefined`.
|
|
10469
|
+
const fitTextEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:fitText');
|
|
10470
|
+
if (fitTextEl !== undefined) {
|
|
10471
|
+
const val = XMLParser.extractAttribute(`<w:fitText${fitTextEl}`, 'w:val');
|
|
10472
|
+
if (val !== undefined) {
|
|
10473
|
+
const n = parseInt(String(val), 10);
|
|
10474
|
+
if (!isNaN(n)) formatting.fitText = n;
|
|
10475
|
+
}
|
|
10476
|
+
}
|
|
10477
|
+
|
|
10478
|
+
// East Asian layout (w:eastAsianLayout §17.3.2.10) — combined
|
|
10479
|
+
// characters / vertical text / compression attributes.
|
|
10480
|
+
const ealEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:eastAsianLayout');
|
|
10481
|
+
if (ealEl !== undefined) {
|
|
10482
|
+
const ealTag = `<w:eastAsianLayout${ealEl}`;
|
|
10483
|
+
const layout: Partial<{
|
|
10484
|
+
id: number;
|
|
10485
|
+
vert: boolean;
|
|
10486
|
+
vertCompress: boolean;
|
|
10487
|
+
combine: boolean;
|
|
10488
|
+
combineBrackets: 'none' | 'round' | 'square' | 'angle' | 'curly';
|
|
10489
|
+
}> = {};
|
|
10490
|
+
const id = XMLParser.extractAttribute(ealTag, 'w:id');
|
|
10491
|
+
if (id !== undefined) {
|
|
10492
|
+
const n = Number(id);
|
|
10493
|
+
if (!isNaN(n)) layout.id = n;
|
|
10494
|
+
}
|
|
10495
|
+
const vert = XMLParser.extractAttribute(ealTag, 'w:vert');
|
|
10496
|
+
if (vert !== undefined && parseOnOffAttribute(vert, true)) layout.vert = true;
|
|
10497
|
+
const vertCompress = XMLParser.extractAttribute(ealTag, 'w:vertCompress');
|
|
10498
|
+
if (vertCompress !== undefined && parseOnOffAttribute(vertCompress, true))
|
|
10499
|
+
layout.vertCompress = true;
|
|
10500
|
+
const combine = XMLParser.extractAttribute(ealTag, 'w:combine');
|
|
10501
|
+
if (combine !== undefined && parseOnOffAttribute(combine, true)) layout.combine = true;
|
|
10502
|
+
const combineBrackets = XMLParser.extractAttribute(ealTag, 'w:combineBrackets');
|
|
10503
|
+
if (combineBrackets) {
|
|
10504
|
+
layout.combineBrackets = String(combineBrackets) as
|
|
10505
|
+
| 'none'
|
|
10506
|
+
| 'round'
|
|
10507
|
+
| 'square'
|
|
10508
|
+
| 'angle'
|
|
10509
|
+
| 'curly';
|
|
10510
|
+
}
|
|
10511
|
+
if (Object.keys(layout).length > 0) {
|
|
10512
|
+
formatting.eastAsianLayout = layout as RunFormatting['eastAsianLayout'];
|
|
10513
|
+
}
|
|
10514
|
+
}
|
|
10515
|
+
|
|
9245
10516
|
return formatting;
|
|
9246
10517
|
}
|
|
9247
10518
|
|
|
@@ -9252,8 +10523,8 @@ export class DocumentParser {
|
|
|
9252
10523
|
*/
|
|
9253
10524
|
private parseTableStyleProperties(
|
|
9254
10525
|
styleXml: string
|
|
9255
|
-
): import('../formatting/Style').TableStyleProperties {
|
|
9256
|
-
const tableStyle: import('../formatting/Style').TableStyleProperties = {};
|
|
10526
|
+
): import('../formatting/Style.js').TableStyleProperties {
|
|
10527
|
+
const tableStyle: import('../formatting/Style.js').TableStyleProperties = {};
|
|
9257
10528
|
|
|
9258
10529
|
// Parse tblPr (table properties)
|
|
9259
10530
|
const tblPrXml = XMLParser.extractBetweenTags(styleXml, '<w:tblPr>', '</w:tblPr>');
|
|
@@ -9306,8 +10577,8 @@ export class DocumentParser {
|
|
|
9306
10577
|
*/
|
|
9307
10578
|
private parseTableFormattingFromXml(
|
|
9308
10579
|
tblPrXml: string
|
|
9309
|
-
): import('../formatting/Style').TableStyleFormatting {
|
|
9310
|
-
const formatting: import('../formatting/Style').TableStyleFormatting = {};
|
|
10580
|
+
): import('../formatting/Style.js').TableStyleFormatting {
|
|
10581
|
+
const formatting: import('../formatting/Style.js').TableStyleFormatting = {};
|
|
9311
10582
|
|
|
9312
10583
|
// Parse indent (w:tblInd) — preserve w:type per ECMA-376 ST_TblWidth
|
|
9313
10584
|
if (tblPrXml.includes('<w:tblInd')) {
|
|
@@ -9320,18 +10591,27 @@ export class DocumentParser {
|
|
|
9320
10591
|
}
|
|
9321
10592
|
const type = XMLParser.extractAttribute(tblIndTag, 'w:type');
|
|
9322
10593
|
if (type) {
|
|
9323
|
-
formatting.indentType = type as import('../elements/Table').TableWidthType;
|
|
10594
|
+
formatting.indentType = type as import('../elements/Table.js').TableWidthType;
|
|
9324
10595
|
}
|
|
9325
10596
|
}
|
|
9326
10597
|
}
|
|
9327
10598
|
|
|
9328
|
-
// Parse alignment
|
|
10599
|
+
// Parse alignment — ST_JcTable has 5 values (start, end, center, left,
|
|
10600
|
+
// right) per ECMA-376 §17.18.45. The whitelist previously only accepted
|
|
10601
|
+
// the three legacy LTR-centric values, silently dropping `start` / `end`
|
|
10602
|
+
// (the bidi-aware defaults a modern authoring tool emits).
|
|
9329
10603
|
if (tblPrXml.includes('<w:jc')) {
|
|
9330
10604
|
const tag = XMLParser.extractSelfClosingTag(tblPrXml, 'w:jc');
|
|
9331
10605
|
if (tag) {
|
|
9332
10606
|
const val = XMLParser.extractAttribute(`<w:jc${tag}`, 'w:val');
|
|
9333
|
-
if (
|
|
9334
|
-
|
|
10607
|
+
if (
|
|
10608
|
+
val === 'left' ||
|
|
10609
|
+
val === 'center' ||
|
|
10610
|
+
val === 'right' ||
|
|
10611
|
+
val === 'start' ||
|
|
10612
|
+
val === 'end'
|
|
10613
|
+
) {
|
|
10614
|
+
formatting.alignment = val as import('../formatting/Style.js').TableAlignment;
|
|
9335
10615
|
}
|
|
9336
10616
|
}
|
|
9337
10617
|
}
|
|
@@ -9372,8 +10652,8 @@ export class DocumentParser {
|
|
|
9372
10652
|
*/
|
|
9373
10653
|
private parseTableCellFormattingFromXml(
|
|
9374
10654
|
tcPrXml: string
|
|
9375
|
-
): import('../formatting/Style').TableCellStyleFormatting {
|
|
9376
|
-
const formatting: import('../formatting/Style').TableCellStyleFormatting = {};
|
|
10655
|
+
): import('../formatting/Style.js').TableCellStyleFormatting {
|
|
10656
|
+
const formatting: import('../formatting/Style.js').TableCellStyleFormatting = {};
|
|
9377
10657
|
|
|
9378
10658
|
// Parse borders
|
|
9379
10659
|
const bordersXml = XMLParser.extractBetweenTags(tcPrXml, '<w:tcBorders>', '</w:tcBorders>');
|
|
@@ -9381,7 +10661,7 @@ export class DocumentParser {
|
|
|
9381
10661
|
formatting.borders = this.parseBordersFromXml(
|
|
9382
10662
|
bordersXml,
|
|
9383
10663
|
true
|
|
9384
|
-
) as import('../formatting/Style').CellBorders;
|
|
10664
|
+
) as import('../formatting/Style.js').CellBorders;
|
|
9385
10665
|
}
|
|
9386
10666
|
|
|
9387
10667
|
// Parse shading
|
|
@@ -9395,12 +10675,15 @@ export class DocumentParser {
|
|
|
9395
10675
|
formatting.margins = this.parseCellMarginsFromXml(marginXml);
|
|
9396
10676
|
}
|
|
9397
10677
|
|
|
9398
|
-
// Parse vertical alignment
|
|
10678
|
+
// Parse vertical alignment — ST_VerticalJc has four values
|
|
10679
|
+
// (top / center / both / bottom) per ECMA-376 §17.18.101. Previously
|
|
10680
|
+
// the whitelist only accepted the first three, silently dropping
|
|
10681
|
+
// `<w:vAlign w:val="both"/>` on cell styles.
|
|
9399
10682
|
if (tcPrXml.includes('<w:vAlign')) {
|
|
9400
10683
|
const tag = XMLParser.extractSelfClosingTag(tcPrXml, 'w:vAlign');
|
|
9401
10684
|
if (tag) {
|
|
9402
10685
|
const val = XMLParser.extractAttribute(`<w:vAlign${tag}`, 'w:val');
|
|
9403
|
-
if (val === 'top' || val === 'center' || val === 'bottom') {
|
|
10686
|
+
if (val === 'top' || val === 'center' || val === 'both' || val === 'bottom') {
|
|
9404
10687
|
formatting.verticalAlignment = val;
|
|
9405
10688
|
}
|
|
9406
10689
|
}
|
|
@@ -9414,8 +10697,8 @@ export class DocumentParser {
|
|
|
9414
10697
|
*/
|
|
9415
10698
|
private parseTableRowFormattingFromXml(
|
|
9416
10699
|
trPrXml: string
|
|
9417
|
-
): import('../formatting/Style').TableRowStyleFormatting {
|
|
9418
|
-
const formatting: import('../formatting/Style').TableRowStyleFormatting = {};
|
|
10700
|
+
): import('../formatting/Style.js').TableRowStyleFormatting {
|
|
10701
|
+
const formatting: import('../formatting/Style.js').TableRowStyleFormatting = {};
|
|
9419
10702
|
|
|
9420
10703
|
// Parse height
|
|
9421
10704
|
if (trPrXml.includes('<w:trHeight')) {
|
|
@@ -9432,15 +10715,25 @@ export class DocumentParser {
|
|
|
9432
10715
|
}
|
|
9433
10716
|
}
|
|
9434
10717
|
|
|
9435
|
-
// Parse cantSplit
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
10718
|
+
// Parse cantSplit / tblHeader — both OnOffOnlyType (§17.4.6, §17.4.50).
|
|
10719
|
+
// Previous substring-include detection hard-coded the flag to `true`
|
|
10720
|
+
// whenever the element appeared, silently flipping an explicit-off
|
|
10721
|
+
// override (e.g., a tblStylePr conditional un-splitting a header row)
|
|
10722
|
+
// into an enabled flag. Reuse parseOnOffAttribute so bare, "on", and
|
|
10723
|
+
// "off" all map correctly, and so absent stays undefined.
|
|
10724
|
+
const parseTrPrOnOffOnly = (tagName: string): boolean | undefined => {
|
|
10725
|
+
const el = XMLParser.extractSelfClosingTag(trPrXml, tagName);
|
|
10726
|
+
if (el === undefined) return undefined;
|
|
10727
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
10728
|
+
if (val === undefined) return true;
|
|
10729
|
+
return parseOnOffAttribute(val, true);
|
|
10730
|
+
};
|
|
9439
10731
|
|
|
9440
|
-
|
|
9441
|
-
if (
|
|
9442
|
-
|
|
9443
|
-
|
|
10732
|
+
const cantSplitVal = parseTrPrOnOffOnly('w:cantSplit');
|
|
10733
|
+
if (cantSplitVal !== undefined) formatting.cantSplit = cantSplitVal;
|
|
10734
|
+
|
|
10735
|
+
const tblHeaderVal = parseTrPrOnOffOnly('w:tblHeader');
|
|
10736
|
+
if (tblHeaderVal !== undefined) formatting.isHeader = tblHeaderVal;
|
|
9444
10737
|
|
|
9445
10738
|
return formatting;
|
|
9446
10739
|
}
|
|
@@ -9450,8 +10743,8 @@ export class DocumentParser {
|
|
|
9450
10743
|
*/
|
|
9451
10744
|
private parseConditionalFormattingFromXml(
|
|
9452
10745
|
styleXml: string
|
|
9453
|
-
): import('../formatting/Style').ConditionalTableFormatting[] | undefined {
|
|
9454
|
-
const conditionalFormatting: import('../formatting/Style').ConditionalTableFormatting[] = [];
|
|
10746
|
+
): import('../formatting/Style.js').ConditionalTableFormatting[] | undefined {
|
|
10747
|
+
const conditionalFormatting: import('../formatting/Style.js').ConditionalTableFormatting[] = [];
|
|
9455
10748
|
|
|
9456
10749
|
// Find all tblStylePr elements
|
|
9457
10750
|
let searchFrom = 0;
|
|
@@ -9467,8 +10760,8 @@ export class DocumentParser {
|
|
|
9467
10760
|
// Extract type attribute
|
|
9468
10761
|
const typeAttr = XMLParser.extractAttribute(tblStylePrXml, 'w:type');
|
|
9469
10762
|
if (typeAttr) {
|
|
9470
|
-
const conditional: import('../formatting/Style').ConditionalTableFormatting = {
|
|
9471
|
-
type: typeAttr as import('../formatting/Style').ConditionalFormattingType,
|
|
10763
|
+
const conditional: import('../formatting/Style.js').ConditionalTableFormatting = {
|
|
10764
|
+
type: typeAttr as import('../formatting/Style.js').ConditionalFormattingType,
|
|
9472
10765
|
};
|
|
9473
10766
|
|
|
9474
10767
|
// Parse pPr
|
|
@@ -9518,29 +10811,58 @@ export class DocumentParser {
|
|
|
9518
10811
|
private parseBordersFromXml(
|
|
9519
10812
|
bordersXml: string,
|
|
9520
10813
|
includeDiagonals: boolean
|
|
9521
|
-
): import('../formatting/Style').TableBorders | import('../formatting/Style').CellBorders {
|
|
10814
|
+
): import('../formatting/Style.js').TableBorders | import('../formatting/Style.js').CellBorders {
|
|
9522
10815
|
const borders: any = {};
|
|
9523
10816
|
|
|
10817
|
+
// Local helper so both the main-side loop and the diagonal loop share
|
|
10818
|
+
// the full CT_Border attribute set (§17.18.2): val / sz / space / color
|
|
10819
|
+
// / themeColor / themeTint / themeShade / shadow / frame. Previously
|
|
10820
|
+
// this parser only extracted the four "basic" attrs, so themed borders
|
|
10821
|
+
// and shadow/frame flags on page/table/cell borders were silently
|
|
10822
|
+
// dropped on every load → save round-trip.
|
|
10823
|
+
const parseBorderAttrs = (
|
|
10824
|
+
type: string
|
|
10825
|
+
): import('../formatting/Style.js').BorderProperties | null => {
|
|
10826
|
+
const tag = XMLParser.extractSelfClosingTag(bordersXml, `w:${type}`);
|
|
10827
|
+
if (!tag) return null;
|
|
10828
|
+
const ref = `<w:${type}${tag}`;
|
|
10829
|
+
const border: import('../formatting/Style.js').BorderProperties = {};
|
|
10830
|
+
const style = XMLParser.extractAttribute(ref, 'w:val');
|
|
10831
|
+
const size = XMLParser.extractAttribute(ref, 'w:sz');
|
|
10832
|
+
const space = XMLParser.extractAttribute(ref, 'w:space');
|
|
10833
|
+
const color = XMLParser.extractAttribute(ref, 'w:color');
|
|
10834
|
+
const themeColor = XMLParser.extractAttribute(ref, 'w:themeColor');
|
|
10835
|
+
const themeTint = XMLParser.extractAttribute(ref, 'w:themeTint');
|
|
10836
|
+
const themeShade = XMLParser.extractAttribute(ref, 'w:themeShade');
|
|
10837
|
+
const shadow = XMLParser.extractAttribute(ref, 'w:shadow');
|
|
10838
|
+
const frame = XMLParser.extractAttribute(ref, 'w:frame');
|
|
10839
|
+
if (style) border.style = style as any;
|
|
10840
|
+
if (size) border.size = parseInt(size, 10);
|
|
10841
|
+
if (space) border.space = parseInt(space, 10);
|
|
10842
|
+
if (color) border.color = color;
|
|
10843
|
+
if (themeColor) (border as any).themeColor = themeColor;
|
|
10844
|
+
if (themeTint) (border as any).themeTint = themeTint;
|
|
10845
|
+
if (themeShade) (border as any).themeShade = themeShade;
|
|
10846
|
+
if (shadow !== undefined) (border as any).shadow = parseOnOffAttribute(shadow, true);
|
|
10847
|
+
if (frame !== undefined) (border as any).frame = parseOnOffAttribute(frame, true);
|
|
10848
|
+
return Object.keys(border).length > 0 ? border : null;
|
|
10849
|
+
};
|
|
10850
|
+
|
|
10851
|
+
// Per ECMA-376 §17.4.40 CT_TblBorders and §17.4.66 CT_TcBorders the
|
|
10852
|
+
// left / right borders have bidi-aware aliases `w:start` / `w:end`.
|
|
10853
|
+
// Modern authoring tools (Word 2013+, Google Docs) emit the bidi-
|
|
10854
|
+
// aware form by default — prefer those over the legacy `w:left` /
|
|
10855
|
+
// `w:right` so bidi-authored tables round-trip their side borders
|
|
10856
|
+
// (the internal model stores under the left/right keys, matching
|
|
10857
|
+
// the emitter).
|
|
9524
10858
|
const borderTypes = ['top', 'bottom', 'left', 'right', 'insideH', 'insideV'];
|
|
9525
10859
|
for (const type of borderTypes) {
|
|
9526
|
-
if
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
const color = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:color');
|
|
9533
|
-
|
|
9534
|
-
const border: import('../formatting/Style').BorderProperties = {};
|
|
9535
|
-
if (style) border.style = style as any;
|
|
9536
|
-
if (size) border.size = parseInt(size, 10);
|
|
9537
|
-
if (space) border.space = parseInt(space, 10);
|
|
9538
|
-
if (color) border.color = color;
|
|
9539
|
-
|
|
9540
|
-
if (Object.keys(border).length > 0) {
|
|
9541
|
-
borders[type] = border;
|
|
9542
|
-
}
|
|
9543
|
-
}
|
|
10860
|
+
// For left/right: prefer bidi-aware start/end alias if present.
|
|
10861
|
+
const alias = type === 'left' ? 'start' : type === 'right' ? 'end' : type;
|
|
10862
|
+
const tagNameToRead = bordersXml.includes(`<w:${alias}`) ? alias : type;
|
|
10863
|
+
if (bordersXml.includes(`<w:${tagNameToRead}`)) {
|
|
10864
|
+
const border = parseBorderAttrs(tagNameToRead);
|
|
10865
|
+
if (border) borders[type] = border;
|
|
9544
10866
|
}
|
|
9545
10867
|
}
|
|
9546
10868
|
|
|
@@ -9549,23 +10871,8 @@ export class DocumentParser {
|
|
|
9549
10871
|
const diagonalTypes = ['tl2br', 'tr2bl'];
|
|
9550
10872
|
for (const type of diagonalTypes) {
|
|
9551
10873
|
if (bordersXml.includes(`<w:${type}`)) {
|
|
9552
|
-
const
|
|
9553
|
-
if (
|
|
9554
|
-
const style = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:val');
|
|
9555
|
-
const size = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:sz');
|
|
9556
|
-
const space = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:space');
|
|
9557
|
-
const color = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:color');
|
|
9558
|
-
|
|
9559
|
-
const border: import('../formatting/Style').BorderProperties = {};
|
|
9560
|
-
if (style) border.style = style as any;
|
|
9561
|
-
if (size) border.size = parseInt(size, 10);
|
|
9562
|
-
if (space) border.space = parseInt(space, 10);
|
|
9563
|
-
if (color) border.color = color;
|
|
9564
|
-
|
|
9565
|
-
if (Object.keys(border).length > 0) {
|
|
9566
|
-
borders[type] = border;
|
|
9567
|
-
}
|
|
9568
|
-
}
|
|
10874
|
+
const border = parseBorderAttrs(type);
|
|
10875
|
+
if (border) borders[type] = border;
|
|
9569
10876
|
}
|
|
9570
10877
|
}
|
|
9571
10878
|
}
|
|
@@ -9578,16 +10885,25 @@ export class DocumentParser {
|
|
|
9578
10885
|
* Extracts all 9 ECMA-376 shading attributes including theme colors.
|
|
9579
10886
|
*/
|
|
9580
10887
|
private parseShadingFromObj(shd: any): ShadingConfig | undefined {
|
|
10888
|
+
// Per ECMA-376 §17.3.1.32 CT_Shd, every string-typed attribute
|
|
10889
|
+
// (ST_UcharHexNumber tint/shade, ST_ThemeColor theme refs,
|
|
10890
|
+
// ST_HexColor fill/color, ST_Shd pattern) can be purely numeric in
|
|
10891
|
+
// hex form — "80", "00", "FF", "80000000", etc. XMLParser's
|
|
10892
|
+
// `parseAttributeValue: true` coerces purely-digit hex strings like
|
|
10893
|
+
// "80" to the JS number 80, violating the `string` type on
|
|
10894
|
+
// ShadingConfig. Previously stored values leaked downstream as
|
|
10895
|
+
// numbers (e.g. `.toUpperCase()` would throw); cast every attribute
|
|
10896
|
+
// through `String(...)` so the declared-type contract holds.
|
|
9581
10897
|
const shading: ShadingConfig = {};
|
|
9582
|
-
if (shd['@_w:val']) shading.pattern = shd['@_w:val'];
|
|
9583
|
-
if (shd['@_w:fill']) shading.fill = shd['@_w:fill'];
|
|
9584
|
-
if (shd['@_w:color']) shading.color = shd['@_w:color'];
|
|
9585
|
-
if (shd['@_w:themeFill']) shading.themeFill = shd['@_w:themeFill'];
|
|
9586
|
-
if (shd['@_w:themeColor']) shading.themeColor = shd['@_w:themeColor'];
|
|
9587
|
-
if (shd['@_w:themeFillTint']) shading.themeFillTint = shd['@_w:themeFillTint'];
|
|
9588
|
-
if (shd['@_w:themeFillShade']) shading.themeFillShade = shd['@_w:themeFillShade'];
|
|
9589
|
-
if (shd['@_w:themeTint']) shading.themeTint = shd['@_w:themeTint'];
|
|
9590
|
-
if (shd['@_w:themeShade']) shading.themeShade = shd['@_w:themeShade'];
|
|
10898
|
+
if (shd['@_w:val']) shading.pattern = String(shd['@_w:val']) as ShadingConfig['pattern'];
|
|
10899
|
+
if (shd['@_w:fill']) shading.fill = String(shd['@_w:fill']);
|
|
10900
|
+
if (shd['@_w:color']) shading.color = String(shd['@_w:color']);
|
|
10901
|
+
if (shd['@_w:themeFill']) shading.themeFill = String(shd['@_w:themeFill']);
|
|
10902
|
+
if (shd['@_w:themeColor']) shading.themeColor = String(shd['@_w:themeColor']);
|
|
10903
|
+
if (shd['@_w:themeFillTint']) shading.themeFillTint = String(shd['@_w:themeFillTint']);
|
|
10904
|
+
if (shd['@_w:themeFillShade']) shading.themeFillShade = String(shd['@_w:themeFillShade']);
|
|
10905
|
+
if (shd['@_w:themeTint']) shading.themeTint = String(shd['@_w:themeTint']);
|
|
10906
|
+
if (shd['@_w:themeShade']) shading.themeShade = String(shd['@_w:themeShade']);
|
|
9591
10907
|
return Object.keys(shading).length > 0 ? shading : undefined;
|
|
9592
10908
|
}
|
|
9593
10909
|
|
|
@@ -9628,8 +10944,8 @@ export class DocumentParser {
|
|
|
9628
10944
|
*/
|
|
9629
10945
|
private parseCellMarginsFromXml(
|
|
9630
10946
|
marginXml: string
|
|
9631
|
-
): import('../formatting/Style').CellMargins | undefined {
|
|
9632
|
-
const margins: import('../formatting/Style').CellMargins = {};
|
|
10947
|
+
): import('../formatting/Style.js').CellMargins | undefined {
|
|
10948
|
+
const margins: import('../formatting/Style.js').CellMargins = {};
|
|
9633
10949
|
|
|
9634
10950
|
// Parse top and bottom directly
|
|
9635
10951
|
for (const type of ['top', 'bottom'] as const) {
|
|
@@ -9894,23 +11210,23 @@ export class DocumentParser {
|
|
|
9894
11210
|
imageManager: ImageManager
|
|
9895
11211
|
): Promise<{
|
|
9896
11212
|
headers: {
|
|
9897
|
-
header: import('../elements/Header').Header;
|
|
11213
|
+
header: import('../elements/Header.js').Header;
|
|
9898
11214
|
relationshipId: string;
|
|
9899
11215
|
filename: string;
|
|
9900
11216
|
}[];
|
|
9901
11217
|
footers: {
|
|
9902
|
-
footer: import('../elements/Footer').Footer;
|
|
11218
|
+
footer: import('../elements/Footer.js').Footer;
|
|
9903
11219
|
relationshipId: string;
|
|
9904
11220
|
filename: string;
|
|
9905
11221
|
}[];
|
|
9906
11222
|
}> {
|
|
9907
11223
|
const headers: {
|
|
9908
|
-
header: import('../elements/Header').Header;
|
|
11224
|
+
header: import('../elements/Header.js').Header;
|
|
9909
11225
|
relationshipId: string;
|
|
9910
11226
|
filename: string;
|
|
9911
11227
|
}[] = [];
|
|
9912
11228
|
const footers: {
|
|
9913
|
-
footer: import('../elements/Footer').Footer;
|
|
11229
|
+
footer: import('../elements/Footer.js').Footer;
|
|
9914
11230
|
relationshipId: string;
|
|
9915
11231
|
filename: string;
|
|
9916
11232
|
}[] = [];
|
|
@@ -9924,7 +11240,7 @@ export class DocumentParser {
|
|
|
9924
11240
|
// Parse headers
|
|
9925
11241
|
// Track already-parsed headers by rId to avoid creating duplicates
|
|
9926
11242
|
// when multiple section property types (default, first, even) reference the same header file
|
|
9927
|
-
const parsedHeadersByRId = new Map<string, import('../elements/Header').Header>();
|
|
11243
|
+
const parsedHeadersByRId = new Map<string, import('../elements/Header.js').Header>();
|
|
9928
11244
|
|
|
9929
11245
|
if (sectionProps.headers) {
|
|
9930
11246
|
for (const [type, rId] of Object.entries(sectionProps.headers)) {
|
|
@@ -9987,7 +11303,7 @@ export class DocumentParser {
|
|
|
9987
11303
|
// Parse footers
|
|
9988
11304
|
// Track already-parsed footers by rId to avoid creating duplicates
|
|
9989
11305
|
// when multiple section property types (default, first, even) reference the same footer file
|
|
9990
|
-
const parsedFootersByRId = new Map<string, import('../elements/Footer').Footer>();
|
|
11306
|
+
const parsedFootersByRId = new Map<string, import('../elements/Footer.js').Footer>();
|
|
9991
11307
|
|
|
9992
11308
|
if (sectionProps.footers) {
|
|
9993
11309
|
for (const [type, rId] of Object.entries(sectionProps.footers)) {
|
|
@@ -10176,29 +11492,42 @@ export class DocumentParser {
|
|
|
10176
11492
|
|
|
10177
11493
|
// Table-level properties (w:tblPr context)
|
|
10178
11494
|
if (propsObj['w:tblStyle']) {
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
11495
|
+
// w:tblStyle w:val is ST_String (§17.7.4.62). XMLParser coerces
|
|
11496
|
+
// purely-numeric style IDs (e.g. "2025") to numbers; cast so the
|
|
11497
|
+
// declared `string` contract holds on tracked-change history.
|
|
11498
|
+
const v = propsObj['w:tblStyle']['@_w:val'];
|
|
11499
|
+
result.style = v !== undefined && v !== null ? String(v) : '';
|
|
11500
|
+
}
|
|
11501
|
+
// tblpPr (floating table position) — mirror main-path zero-value
|
|
11502
|
+
// preservation. The tblPrChange emitter re-emits position via
|
|
11503
|
+
// `!== undefined`, so dropping zero-valued tracked "previous"
|
|
11504
|
+
// positions here lost them silently on round-trip.
|
|
10182
11505
|
if (propsObj['w:tblpPr']) {
|
|
10183
11506
|
const tblpPr = propsObj['w:tblpPr'];
|
|
10184
11507
|
const pos: any = {};
|
|
10185
|
-
if (tblpPr['@_w:tblpX']) pos.x =
|
|
10186
|
-
if (tblpPr['@_w:tblpY']) pos.y =
|
|
11508
|
+
if (isExplicitlySet(tblpPr['@_w:tblpX'])) pos.x = safeParseInt(tblpPr['@_w:tblpX']);
|
|
11509
|
+
if (isExplicitlySet(tblpPr['@_w:tblpY'])) pos.y = safeParseInt(tblpPr['@_w:tblpY']);
|
|
10187
11510
|
if (tblpPr['@_w:horzAnchor']) pos.horizontalAnchor = tblpPr['@_w:horzAnchor'];
|
|
10188
11511
|
if (tblpPr['@_w:vertAnchor']) pos.verticalAnchor = tblpPr['@_w:vertAnchor'];
|
|
10189
|
-
if (tblpPr['@_w:leftFromText'])
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
if (tblpPr['@_w:
|
|
10193
|
-
|
|
10194
|
-
|
|
11512
|
+
if (isExplicitlySet(tblpPr['@_w:leftFromText'])) {
|
|
11513
|
+
pos.leftFromText = safeParseInt(tblpPr['@_w:leftFromText']);
|
|
11514
|
+
}
|
|
11515
|
+
if (isExplicitlySet(tblpPr['@_w:rightFromText'])) {
|
|
11516
|
+
pos.rightFromText = safeParseInt(tblpPr['@_w:rightFromText']);
|
|
11517
|
+
}
|
|
11518
|
+
if (isExplicitlySet(tblpPr['@_w:topFromText'])) {
|
|
11519
|
+
pos.topFromText = safeParseInt(tblpPr['@_w:topFromText']);
|
|
11520
|
+
}
|
|
11521
|
+
if (isExplicitlySet(tblpPr['@_w:bottomFromText'])) {
|
|
11522
|
+
pos.bottomFromText = safeParseInt(tblpPr['@_w:bottomFromText']);
|
|
11523
|
+
}
|
|
10195
11524
|
if (Object.keys(pos).length > 0) result.position = pos;
|
|
10196
11525
|
}
|
|
10197
11526
|
if (propsObj['w:tblOverlap']) {
|
|
10198
11527
|
result.overlap = propsObj['w:tblOverlap']['@_w:val'];
|
|
10199
11528
|
}
|
|
10200
11529
|
if (propsObj['w:bidiVisual']) {
|
|
10201
|
-
result.bidiVisual =
|
|
11530
|
+
result.bidiVisual = parseOoxmlBoolean(propsObj['w:bidiVisual']);
|
|
10202
11531
|
}
|
|
10203
11532
|
if (propsObj['w:tblStyleRowBandSize']) {
|
|
10204
11533
|
result.tblStyleRowBandSize = parseInt(
|
|
@@ -10244,21 +11573,55 @@ export class DocumentParser {
|
|
|
10244
11573
|
const borders: any = {};
|
|
10245
11574
|
const bordersObj = propsObj['w:tblBorders'];
|
|
10246
11575
|
for (const side of ['top', 'bottom', 'left', 'right', 'insideH', 'insideV']) {
|
|
10247
|
-
|
|
10248
|
-
|
|
11576
|
+
// Prefer bidi-aware w:start/w:end aliases over legacy w:left/
|
|
11577
|
+
// w:right (ECMA-376 §17.4.40 CT_TblBorders). Same pattern as
|
|
11578
|
+
// the main table borders parser — the bidi-aware form is the
|
|
11579
|
+
// preferred modern spelling.
|
|
11580
|
+
const aliasKey = side === 'left' ? 'w:start' : side === 'right' ? 'w:end' : undefined;
|
|
11581
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${side}`];
|
|
11582
|
+
if (borderObj) {
|
|
11583
|
+
borders[side] = this.parseBorderElement(borderObj);
|
|
10249
11584
|
}
|
|
10250
11585
|
}
|
|
10251
11586
|
if (Object.keys(borders).length > 0) result.borders = borders;
|
|
10252
11587
|
}
|
|
11588
|
+
// tblLook per ECMA-376 §17.4.57 — supports both hex-string format
|
|
11589
|
+
// (w:val="04A0") AND the expanded individual-attribute form
|
|
11590
|
+
// (firstRow/lastRow/firstColumn/lastColumn/noHBand/noVBand).
|
|
11591
|
+
// Word often emits the expanded form (no w:val) inside *PrChange
|
|
11592
|
+
// previous-properties; the hex-only read silently collapsed every
|
|
11593
|
+
// flag to "0000" on round-trip.
|
|
10253
11594
|
if (propsObj['w:tblLook']) {
|
|
10254
11595
|
const look = propsObj['w:tblLook'];
|
|
10255
|
-
|
|
11596
|
+
if (look['@_w:val']) {
|
|
11597
|
+
result.tblLook = String(look['@_w:val']);
|
|
11598
|
+
} else {
|
|
11599
|
+
const attrIsOn = (name: string): boolean => {
|
|
11600
|
+
const v = look[name];
|
|
11601
|
+
if (v === undefined) return false;
|
|
11602
|
+
return parseOoxmlBoolean({ '@_w:val': v });
|
|
11603
|
+
};
|
|
11604
|
+
let value = 0;
|
|
11605
|
+
if (attrIsOn('@_w:firstRow')) value |= 0x0020;
|
|
11606
|
+
if (attrIsOn('@_w:lastRow')) value |= 0x0040;
|
|
11607
|
+
if (attrIsOn('@_w:firstColumn')) value |= 0x0080;
|
|
11608
|
+
if (attrIsOn('@_w:lastColumn')) value |= 0x0100;
|
|
11609
|
+
if (attrIsOn('@_w:noHBand')) value |= 0x0200;
|
|
11610
|
+
if (attrIsOn('@_w:noVBand')) value |= 0x0400;
|
|
11611
|
+
result.tblLook = value.toString(16).toUpperCase().padStart(4, '0');
|
|
11612
|
+
}
|
|
10256
11613
|
}
|
|
10257
11614
|
if (propsObj['w:tblCaption']) {
|
|
10258
|
-
|
|
11615
|
+
// w:tblCaption w:val is ST_String (§17.4.62). Cast through
|
|
11616
|
+
// String() so purely-numeric caption text round-trips as a
|
|
11617
|
+
// string inside the tracked-change previousProperties.
|
|
11618
|
+
const v = propsObj['w:tblCaption']['@_w:val'];
|
|
11619
|
+
result.caption = v !== undefined && v !== null ? String(v) : undefined;
|
|
10259
11620
|
}
|
|
10260
11621
|
if (propsObj['w:tblDescription']) {
|
|
10261
|
-
|
|
11622
|
+
// w:tblDescription w:val is ST_String (§17.4.63).
|
|
11623
|
+
const v = propsObj['w:tblDescription']['@_w:val'];
|
|
11624
|
+
result.description = v !== undefined && v !== null ? String(v) : undefined;
|
|
10262
11625
|
}
|
|
10263
11626
|
|
|
10264
11627
|
// Row-level properties (w:trPr context) — all CT_TrPr elements
|
|
@@ -10289,14 +11652,15 @@ export class DocumentParser {
|
|
|
10289
11652
|
const rule = propsObj['w:trHeight']['@_w:hRule'];
|
|
10290
11653
|
if (rule) result.heightRule = rule;
|
|
10291
11654
|
}
|
|
11655
|
+
// Row CT_OnOff — honour w:val per ECMA-376 §17.17.4 (ST_OnOff)
|
|
10292
11656
|
if (propsObj['w:tblHeader']) {
|
|
10293
|
-
result.isHeader =
|
|
11657
|
+
result.isHeader = parseOoxmlBoolean(propsObj['w:tblHeader']);
|
|
10294
11658
|
}
|
|
10295
11659
|
if (propsObj['w:cantSplit']) {
|
|
10296
|
-
result.cantSplit =
|
|
11660
|
+
result.cantSplit = parseOoxmlBoolean(propsObj['w:cantSplit']);
|
|
10297
11661
|
}
|
|
10298
11662
|
if (propsObj['w:hidden']) {
|
|
10299
|
-
result.hidden =
|
|
11663
|
+
result.hidden = parseOoxmlBoolean(propsObj['w:hidden']);
|
|
10300
11664
|
}
|
|
10301
11665
|
|
|
10302
11666
|
// Cell-level properties (w:tcPr context) — all CT_TcPr elements
|
|
@@ -10317,14 +11681,19 @@ export class DocumentParser {
|
|
|
10317
11681
|
const borders: any = {};
|
|
10318
11682
|
const bordersObj = propsObj['w:tcBorders'];
|
|
10319
11683
|
for (const side of ['top', 'bottom', 'left', 'right', 'tl2br', 'tr2bl']) {
|
|
10320
|
-
|
|
10321
|
-
|
|
11684
|
+
// Prefer bidi-aware w:start/w:end aliases for left/right
|
|
11685
|
+
// (ECMA-376 §17.4.66 CT_TcBorders). Diagonals (tl2br/tr2bl)
|
|
11686
|
+
// have no bidi aliases.
|
|
11687
|
+
const aliasKey = side === 'left' ? 'w:start' : side === 'right' ? 'w:end' : undefined;
|
|
11688
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${side}`];
|
|
11689
|
+
if (borderObj) {
|
|
11690
|
+
borders[side] = this.parseBorderElement(borderObj);
|
|
10322
11691
|
}
|
|
10323
11692
|
}
|
|
10324
11693
|
if (Object.keys(borders).length > 0) result.borders = borders;
|
|
10325
11694
|
}
|
|
10326
11695
|
if (propsObj['w:noWrap']) {
|
|
10327
|
-
result.noWrap =
|
|
11696
|
+
result.noWrap = parseOoxmlBoolean(propsObj['w:noWrap']);
|
|
10328
11697
|
}
|
|
10329
11698
|
if (propsObj['w:tcMar']) {
|
|
10330
11699
|
const tcMar = propsObj['w:tcMar'];
|
|
@@ -10341,13 +11710,13 @@ export class DocumentParser {
|
|
|
10341
11710
|
result.textDirection = propsObj['w:textDirection']['@_w:val'];
|
|
10342
11711
|
}
|
|
10343
11712
|
if (propsObj['w:tcFitText']) {
|
|
10344
|
-
result.fitText =
|
|
11713
|
+
result.fitText = parseOoxmlBoolean(propsObj['w:tcFitText']);
|
|
10345
11714
|
}
|
|
10346
11715
|
if (propsObj['w:vAlign']) {
|
|
10347
11716
|
result.verticalAlignment = propsObj['w:vAlign']['@_w:val'];
|
|
10348
11717
|
}
|
|
10349
11718
|
if (propsObj['w:hideMark']) {
|
|
10350
|
-
result.hideMark =
|
|
11719
|
+
result.hideMark = parseOoxmlBoolean(propsObj['w:hideMark']);
|
|
10351
11720
|
}
|
|
10352
11721
|
if (propsObj['w:cnfStyle']) {
|
|
10353
11722
|
result.cnfStyle = propsObj['w:cnfStyle']['@_w:val'];
|
|
@@ -10377,6 +11746,79 @@ export class DocumentParser {
|
|
|
10377
11746
|
if (!sectPrXml) return {};
|
|
10378
11747
|
const result: Record<string, any> = {};
|
|
10379
11748
|
|
|
11749
|
+
// Footnote properties (w:footnotePr) per §17.11.9. The main sectPr parser
|
|
11750
|
+
// reads these, and the emitter supports prev.footnotePr, but the
|
|
11751
|
+
// sectPrChange parser previously dropped them entirely — tracked history
|
|
11752
|
+
// of changes to footnote numbering format / position / start / restart
|
|
11753
|
+
// was lost on every round-trip.
|
|
11754
|
+
const footnotePrElements = XMLParser.extractElements(sectPrXml, 'w:footnotePr');
|
|
11755
|
+
if (footnotePrElements.length > 0 && footnotePrElements[0]) {
|
|
11756
|
+
const fnPr = footnotePrElements[0];
|
|
11757
|
+
const fnObj: any = {};
|
|
11758
|
+
const posElements = XMLParser.extractElements(fnPr, 'w:pos');
|
|
11759
|
+
if (posElements[0]) {
|
|
11760
|
+
const pos = XMLParser.extractAttribute(posElements[0], 'w:val');
|
|
11761
|
+
if (pos) fnObj.position = pos;
|
|
11762
|
+
}
|
|
11763
|
+
const numFmtElements = XMLParser.extractElements(fnPr, 'w:numFmt');
|
|
11764
|
+
if (numFmtElements[0]) {
|
|
11765
|
+
const fmt = XMLParser.extractAttribute(numFmtElements[0], 'w:val');
|
|
11766
|
+
if (fmt) fnObj.numberFormat = fmt;
|
|
11767
|
+
}
|
|
11768
|
+
const numStartElements = XMLParser.extractElements(fnPr, 'w:numStart');
|
|
11769
|
+
if (numStartElements[0]) {
|
|
11770
|
+
const start = XMLParser.extractAttribute(numStartElements[0], 'w:val');
|
|
11771
|
+
if (start !== undefined) fnObj.startNumber = parseInt(String(start), 10);
|
|
11772
|
+
}
|
|
11773
|
+
const numRestartElements = XMLParser.extractElements(fnPr, 'w:numRestart');
|
|
11774
|
+
if (numRestartElements[0]) {
|
|
11775
|
+
const restart = XMLParser.extractAttribute(numRestartElements[0], 'w:val');
|
|
11776
|
+
if (restart) fnObj.restart = restart;
|
|
11777
|
+
}
|
|
11778
|
+
if (Object.keys(fnObj).length > 0) result.footnotePr = fnObj;
|
|
11779
|
+
}
|
|
11780
|
+
|
|
11781
|
+
// Endnote properties (w:endnotePr) per §17.11.5 — mirror of footnotePr.
|
|
11782
|
+
const endnotePrElements = XMLParser.extractElements(sectPrXml, 'w:endnotePr');
|
|
11783
|
+
if (endnotePrElements.length > 0 && endnotePrElements[0]) {
|
|
11784
|
+
const enPr = endnotePrElements[0];
|
|
11785
|
+
const enObj: any = {};
|
|
11786
|
+
const posElements = XMLParser.extractElements(enPr, 'w:pos');
|
|
11787
|
+
if (posElements[0]) {
|
|
11788
|
+
const pos = XMLParser.extractAttribute(posElements[0], 'w:val');
|
|
11789
|
+
if (pos) enObj.position = pos;
|
|
11790
|
+
}
|
|
11791
|
+
const numFmtElements = XMLParser.extractElements(enPr, 'w:numFmt');
|
|
11792
|
+
if (numFmtElements[0]) {
|
|
11793
|
+
const fmt = XMLParser.extractAttribute(numFmtElements[0], 'w:val');
|
|
11794
|
+
if (fmt) enObj.numberFormat = fmt;
|
|
11795
|
+
}
|
|
11796
|
+
const numStartElements = XMLParser.extractElements(enPr, 'w:numStart');
|
|
11797
|
+
if (numStartElements[0]) {
|
|
11798
|
+
const start = XMLParser.extractAttribute(numStartElements[0], 'w:val');
|
|
11799
|
+
if (start !== undefined) enObj.startNumber = parseInt(String(start), 10);
|
|
11800
|
+
}
|
|
11801
|
+
const numRestartElements = XMLParser.extractElements(enPr, 'w:numRestart');
|
|
11802
|
+
if (numRestartElements[0]) {
|
|
11803
|
+
const restart = XMLParser.extractAttribute(numRestartElements[0], 'w:val');
|
|
11804
|
+
if (restart) enObj.restart = restart;
|
|
11805
|
+
}
|
|
11806
|
+
if (Object.keys(enObj).length > 0) result.endnotePr = enObj;
|
|
11807
|
+
}
|
|
11808
|
+
|
|
11809
|
+
// Paper source (w:paperSrc) per §17.6.12 CT_PaperSource — first-page / other
|
|
11810
|
+
// paper tray selection. Both attributes optional per schema.
|
|
11811
|
+
const paperSrcElements = XMLParser.extractElements(sectPrXml, 'w:paperSrc');
|
|
11812
|
+
if (paperSrcElements.length > 0 && paperSrcElements[0]) {
|
|
11813
|
+
const ps = paperSrcElements[0];
|
|
11814
|
+
const psObj: any = {};
|
|
11815
|
+
const first = XMLParser.extractAttribute(ps, 'w:first');
|
|
11816
|
+
if (first !== undefined) psObj.first = parseInt(String(first), 10);
|
|
11817
|
+
const other = XMLParser.extractAttribute(ps, 'w:other');
|
|
11818
|
+
if (other !== undefined) psObj.other = parseInt(String(other), 10);
|
|
11819
|
+
if (Object.keys(psObj).length > 0) result.paperSource = psObj;
|
|
11820
|
+
}
|
|
11821
|
+
|
|
10380
11822
|
// Page size
|
|
10381
11823
|
const pgSzElements = XMLParser.extractElements(sectPrXml, 'w:pgSz');
|
|
10382
11824
|
if (pgSzElements.length > 0 && pgSzElements[0]) {
|
|
@@ -10395,7 +11837,10 @@ export class DocumentParser {
|
|
|
10395
11837
|
}
|
|
10396
11838
|
}
|
|
10397
11839
|
|
|
10398
|
-
// Margins
|
|
11840
|
+
// Margins — full CT_PageMar attribute set (§17.6.11) including w:gutter
|
|
11841
|
+
// (the book-binding margin). Previously gutter was dropped on sectPrChange
|
|
11842
|
+
// history, so any tracked change to a binding-gutter value lost the
|
|
11843
|
+
// previous value on round-trip.
|
|
10399
11844
|
const pgMarElements = XMLParser.extractElements(sectPrXml, 'w:pgMar');
|
|
10400
11845
|
if (pgMarElements.length > 0 && pgMarElements[0]) {
|
|
10401
11846
|
const pgMar = pgMarElements[0];
|
|
@@ -10412,6 +11857,8 @@ export class DocumentParser {
|
|
|
10412
11857
|
if (header) margins.header = parseInt(header, 10);
|
|
10413
11858
|
const footer = XMLParser.extractAttribute(pgMar, 'w:footer');
|
|
10414
11859
|
if (footer) margins.footer = parseInt(footer, 10);
|
|
11860
|
+
const gutter = XMLParser.extractAttribute(pgMar, 'w:gutter');
|
|
11861
|
+
if (gutter) margins.gutter = parseInt(gutter, 10);
|
|
10415
11862
|
if (Object.keys(margins).length > 0) result.margins = margins;
|
|
10416
11863
|
}
|
|
10417
11864
|
|
|
@@ -10438,7 +11885,13 @@ export class DocumentParser {
|
|
|
10438
11885
|
if (Object.keys(lnObj).length > 0) result.lineNumbering = lnObj;
|
|
10439
11886
|
}
|
|
10440
11887
|
|
|
10441
|
-
// Page numbering
|
|
11888
|
+
// Page numbering — full CT_PageNumber attribute set (§17.6.12):
|
|
11889
|
+
// fmt / start / chapStyle / chapSep. Previously only fmt+start were read,
|
|
11890
|
+
// so tracked-change history of chapter-numbering edits (e.g. switching
|
|
11891
|
+
// from "Heading 1" to "Heading 2" as the chapter marker, or changing the
|
|
11892
|
+
// chapter separator from hyphen to emDash) lost the previous values.
|
|
11893
|
+
// The Section.ts emitter stores chapStyle / chapSep as top-level
|
|
11894
|
+
// properties rather than on pageNumbering, so expose them the same way.
|
|
10442
11895
|
const pgNumElements = XMLParser.extractElements(sectPrXml, 'w:pgNumType');
|
|
10443
11896
|
if (pgNumElements.length > 0 && pgNumElements[0]) {
|
|
10444
11897
|
const pn = pgNumElements[0];
|
|
@@ -10448,6 +11901,12 @@ export class DocumentParser {
|
|
|
10448
11901
|
const fmt = XMLParser.extractAttribute(pn, 'w:fmt');
|
|
10449
11902
|
if (fmt) pnObj.format = fmt;
|
|
10450
11903
|
if (Object.keys(pnObj).length > 0) result.pageNumbering = pnObj;
|
|
11904
|
+
// Mirror the main-sectPr parser: chapStyle / chapSep live at the root
|
|
11905
|
+
// of the section properties, not inside pageNumbering.
|
|
11906
|
+
const chapStyle = XMLParser.extractAttribute(pn, 'w:chapStyle');
|
|
11907
|
+
if (chapStyle !== undefined) result.chapStyle = parseInt(String(chapStyle), 10);
|
|
11908
|
+
const chapSep = XMLParser.extractAttribute(pn, 'w:chapSep');
|
|
11909
|
+
if (chapSep) result.chapSep = chapSep;
|
|
10451
11910
|
}
|
|
10452
11911
|
|
|
10453
11912
|
// Columns
|
|
@@ -10456,16 +11915,58 @@ export class DocumentParser {
|
|
|
10456
11915
|
const cols = colsElements[0];
|
|
10457
11916
|
const num = XMLParser.extractAttribute(cols, 'w:num');
|
|
10458
11917
|
const space = XMLParser.extractAttribute(cols, 'w:space');
|
|
11918
|
+
// Full CT_Columns attribute set (§17.6.4): num / space / equalWidth / sep
|
|
11919
|
+
// plus the child <w:col w:w="..." w:space="..."/> entries for per-column
|
|
11920
|
+
// widths. Previously only num+space were read, so sectPrChange history of
|
|
11921
|
+
// a columns-layout change dropped equalWidth, the separator line, and
|
|
11922
|
+
// the entire custom column-width / per-column-space configuration.
|
|
11923
|
+
const equalWidth = XMLParser.extractAttribute(cols, 'w:equalWidth');
|
|
11924
|
+
const sep = XMLParser.extractAttribute(cols, 'w:sep');
|
|
11925
|
+
|
|
11926
|
+
// Extract individual <w:col> children for non-equal-width layouts.
|
|
11927
|
+
const colChildElements = XMLParser.extractElements(cols, 'w:col');
|
|
11928
|
+
const columnWidths: number[] = [];
|
|
11929
|
+
const columnSpaces: number[] = [];
|
|
11930
|
+
let hasColumnSpaces = false;
|
|
11931
|
+
for (const col of colChildElements) {
|
|
11932
|
+
const width = XMLParser.extractAttribute(col, 'w:w');
|
|
11933
|
+
if (width) columnWidths.push(parseInt(width, 10));
|
|
11934
|
+
const colSpace = XMLParser.extractAttribute(col, 'w:space');
|
|
11935
|
+
if (colSpace) {
|
|
11936
|
+
columnSpaces.push(parseInt(colSpace, 10));
|
|
11937
|
+
hasColumnSpaces = true;
|
|
11938
|
+
} else {
|
|
11939
|
+
columnSpaces.push(0);
|
|
11940
|
+
}
|
|
11941
|
+
}
|
|
11942
|
+
|
|
10459
11943
|
if (num) {
|
|
10460
11944
|
result.columns = {
|
|
10461
11945
|
count: parseInt(num, 10),
|
|
10462
11946
|
space: space ? parseInt(space, 10) : undefined,
|
|
11947
|
+
equalWidth: equalWidth ? parseOnOffAttribute(equalWidth) : undefined,
|
|
11948
|
+
separator: sep ? parseOnOffAttribute(sep) : undefined,
|
|
11949
|
+
columnWidths: columnWidths.length > 0 ? columnWidths : undefined,
|
|
11950
|
+
columnSpaces: hasColumnSpaces ? columnSpaces : undefined,
|
|
10463
11951
|
};
|
|
10464
11952
|
}
|
|
10465
11953
|
}
|
|
10466
11954
|
|
|
10467
|
-
//
|
|
10468
|
-
|
|
11955
|
+
// CT_OnOff sectPr flags — honour w:val per ECMA-376 §17.17.4 (ST_OnOff).
|
|
11956
|
+
// Previously these used substring `.includes()`, which both ignored w:val
|
|
11957
|
+
// (flipping explicit false to true) and could false-positive on prefix
|
|
11958
|
+
// matches (e.g. "<w:bidi" inside "<w:bidiVisual"). Use extractElements +
|
|
11959
|
+
// extractAttribute + parseOnOffAttribute instead.
|
|
11960
|
+
const parseSectCtOnOff = (tagName: string): boolean | undefined => {
|
|
11961
|
+
const els = XMLParser.extractElements(sectPrXml, tagName);
|
|
11962
|
+
if (els.length === 0 || !els[0]) return undefined;
|
|
11963
|
+
const v = XMLParser.extractAttribute(els[0], 'w:val');
|
|
11964
|
+
return parseOnOffAttribute(v, true);
|
|
11965
|
+
};
|
|
11966
|
+
|
|
11967
|
+
// Form protection (w:formProt) — CT_OnOff
|
|
11968
|
+
const formProtVal = parseSectCtOnOff('w:formProt');
|
|
11969
|
+
if (formProtVal !== undefined) result.formProt = formProtVal;
|
|
10469
11970
|
|
|
10470
11971
|
// Vertical alignment
|
|
10471
11972
|
const vAlignElements = XMLParser.extractElements(sectPrXml, 'w:vAlign');
|
|
@@ -10474,11 +11975,13 @@ export class DocumentParser {
|
|
|
10474
11975
|
if (val) result.verticalAlignment = val;
|
|
10475
11976
|
}
|
|
10476
11977
|
|
|
10477
|
-
// Suppress endnotes
|
|
10478
|
-
|
|
11978
|
+
// Suppress endnotes (w:noEndnote) — CT_OnOff
|
|
11979
|
+
const noEndnoteVal = parseSectCtOnOff('w:noEndnote');
|
|
11980
|
+
if (noEndnoteVal !== undefined) result.noEndnote = noEndnoteVal;
|
|
10479
11981
|
|
|
10480
|
-
// Title page
|
|
10481
|
-
|
|
11982
|
+
// Title page (w:titlePg) — CT_OnOff
|
|
11983
|
+
const titlePgVal = parseSectCtOnOff('w:titlePg');
|
|
11984
|
+
if (titlePgVal !== undefined) result.titlePage = titlePgVal;
|
|
10482
11985
|
|
|
10483
11986
|
// Text direction
|
|
10484
11987
|
const textDirElements = XMLParser.extractElements(sectPrXml, 'w:textDirection');
|
|
@@ -10487,11 +11990,13 @@ export class DocumentParser {
|
|
|
10487
11990
|
if (val) result.textDirection = val;
|
|
10488
11991
|
}
|
|
10489
11992
|
|
|
10490
|
-
// Bidi section
|
|
10491
|
-
|
|
11993
|
+
// Bidi section (w:bidi) — CT_OnOff
|
|
11994
|
+
const bidiVal = parseSectCtOnOff('w:bidi');
|
|
11995
|
+
if (bidiVal !== undefined) result.bidi = bidiVal;
|
|
10492
11996
|
|
|
10493
|
-
// RTL gutter
|
|
10494
|
-
|
|
11997
|
+
// RTL gutter (w:rtlGutter) — CT_OnOff
|
|
11998
|
+
const rtlGutterVal = parseSectCtOnOff('w:rtlGutter');
|
|
11999
|
+
if (rtlGutterVal !== undefined) result.rtlGutter = rtlGutterVal;
|
|
10495
12000
|
|
|
10496
12001
|
// Document grid
|
|
10497
12002
|
const docGridElements = XMLParser.extractElements(sectPrXml, 'w:docGrid');
|
|
@@ -10507,6 +12012,64 @@ export class DocumentParser {
|
|
|
10507
12012
|
if (Object.keys(dgObj).length > 0) result.docGrid = dgObj;
|
|
10508
12013
|
}
|
|
10509
12014
|
|
|
12015
|
+
// Page borders (w:pgBorders) per ECMA-376 §17.6.10. The main sectPr parser
|
|
12016
|
+
// reads these, but the sectPrChange previous-sectPr parser previously
|
|
12017
|
+
// didn't — so a tracked-change history of page-border edits lost the
|
|
12018
|
+
// entire "previous" border configuration (style, color, themeColor,
|
|
12019
|
+
// themeTint, themeShade, shadow, frame) every round-trip. The emitter
|
|
12020
|
+
// supports prev.pageBorders already; this is the missing parser half.
|
|
12021
|
+
const pgBordersElements = XMLParser.extractElements(sectPrXml, 'w:pgBorders');
|
|
12022
|
+
if (pgBordersElements.length > 0 && pgBordersElements[0]) {
|
|
12023
|
+
const pgBordersXml = pgBordersElements[0];
|
|
12024
|
+
const pageBorders: any = {};
|
|
12025
|
+
const offsetFrom = XMLParser.extractAttribute(pgBordersXml, 'w:offsetFrom');
|
|
12026
|
+
if (offsetFrom) pageBorders.offsetFrom = offsetFrom;
|
|
12027
|
+
const display = XMLParser.extractAttribute(pgBordersXml, 'w:display');
|
|
12028
|
+
if (display) pageBorders.display = display;
|
|
12029
|
+
const zOrder = XMLParser.extractAttribute(pgBordersXml, 'w:zOrder');
|
|
12030
|
+
if (zOrder) pageBorders.zOrder = zOrder;
|
|
12031
|
+
|
|
12032
|
+
// Per-side border parser mirrors the main-sectPr logic — full CT_Border
|
|
12033
|
+
// attribute set including themed colors and shadow/frame flags.
|
|
12034
|
+
const parsePrevBorder = (sideXml: string): any | undefined => {
|
|
12035
|
+
if (!sideXml) return undefined;
|
|
12036
|
+
const border: any = {};
|
|
12037
|
+
const val = XMLParser.extractAttribute(sideXml, 'w:val');
|
|
12038
|
+
if (val) border.style = val;
|
|
12039
|
+
const sz = XMLParser.extractAttribute(sideXml, 'w:sz');
|
|
12040
|
+
if (sz) border.size = parseInt(sz, 10);
|
|
12041
|
+
const color = XMLParser.extractAttribute(sideXml, 'w:color');
|
|
12042
|
+
if (color) border.color = color;
|
|
12043
|
+
const space = XMLParser.extractAttribute(sideXml, 'w:space');
|
|
12044
|
+
if (space) border.space = parseInt(space, 10);
|
|
12045
|
+
const shadow = XMLParser.extractAttribute(sideXml, 'w:shadow');
|
|
12046
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
12047
|
+
const frame = XMLParser.extractAttribute(sideXml, 'w:frame');
|
|
12048
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
12049
|
+
const themeColor = XMLParser.extractAttribute(sideXml, 'w:themeColor');
|
|
12050
|
+
if (themeColor) border.themeColor = themeColor;
|
|
12051
|
+
const themeTint = XMLParser.extractAttribute(sideXml, 'w:themeTint');
|
|
12052
|
+
if (themeTint) border.themeTint = themeTint;
|
|
12053
|
+
const themeShade = XMLParser.extractAttribute(sideXml, 'w:themeShade');
|
|
12054
|
+
if (themeShade) border.themeShade = themeShade;
|
|
12055
|
+
const artId = XMLParser.extractAttribute(sideXml, 'w:id');
|
|
12056
|
+
if (artId) border.artId = parseInt(artId, 10);
|
|
12057
|
+
return Object.keys(border).length > 0 ? border : undefined;
|
|
12058
|
+
};
|
|
12059
|
+
|
|
12060
|
+
for (const side of ['top', 'left', 'bottom', 'right']) {
|
|
12061
|
+
const sideElements = XMLParser.extractElements(pgBordersXml, `w:${side}`);
|
|
12062
|
+
if (sideElements.length > 0 && sideElements[0]) {
|
|
12063
|
+
const border = parsePrevBorder(sideElements[0]);
|
|
12064
|
+
if (border) pageBorders[side] = border;
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
12067
|
+
|
|
12068
|
+
if (Object.keys(pageBorders).length > 0) {
|
|
12069
|
+
result.pageBorders = pageBorders;
|
|
12070
|
+
}
|
|
12071
|
+
}
|
|
12072
|
+
|
|
10510
12073
|
return result;
|
|
10511
12074
|
}
|
|
10512
12075
|
}
|