docxmlater 10.4.1 → 11.0.6
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 +411 -638
- 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 +2059 -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 +8 -3
- 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 +8763 -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 +34 -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 +35 -8
- 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 +2187 -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 +13 -4
- 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)
|
|
@@ -1289,6 +1349,11 @@ export class DocumentParser {
|
|
|
1289
1349
|
imageManager
|
|
1290
1350
|
);
|
|
1291
1351
|
if (imageRun) {
|
|
1352
|
+
// Preserve the parent run's w:rPr (rFonts, noProof, b, etc.)
|
|
1353
|
+
// — without this, ImageRun.toXML() emits <w:r><w:drawing/></w:r>
|
|
1354
|
+
// and Word recalculates line height with the default font,
|
|
1355
|
+
// shifting the image and clipping it into adjacent cells.
|
|
1356
|
+
this.parseRunPropertiesFromObject(runObj['w:rPr'], imageRun);
|
|
1292
1357
|
paragraph.addRun(imageRun);
|
|
1293
1358
|
}
|
|
1294
1359
|
}
|
|
@@ -1533,7 +1598,7 @@ export class DocumentParser {
|
|
|
1533
1598
|
} = { revision: null, bookmarkStarts: [], bookmarkEnds: [] };
|
|
1534
1599
|
try {
|
|
1535
1600
|
// Map XML tag to RevisionType
|
|
1536
|
-
let revisionType: import('../elements/Revision').RevisionType;
|
|
1601
|
+
let revisionType: import('../elements/Revision.js').RevisionType;
|
|
1537
1602
|
switch (tagName) {
|
|
1538
1603
|
case 'w:ins':
|
|
1539
1604
|
revisionType = 'insert';
|
|
@@ -1581,7 +1646,7 @@ export class DocumentParser {
|
|
|
1581
1646
|
const runXmls = XMLParser.extractElements(xmlWithoutHyperlinks, 'w:r');
|
|
1582
1647
|
|
|
1583
1648
|
// Use RevisionContent to hold both Run and Hyperlink objects
|
|
1584
|
-
const content: import('../elements/RevisionContent').RevisionContent[] = [];
|
|
1649
|
+
const content: import('../elements/RevisionContent.js').RevisionContent[] = [];
|
|
1585
1650
|
|
|
1586
1651
|
// Parse standalone runs (not inside hyperlinks)
|
|
1587
1652
|
for (const runXml of runXmls) {
|
|
@@ -1727,8 +1792,8 @@ export class DocumentParser {
|
|
|
1727
1792
|
const id = parseInt(idAttr, 10);
|
|
1728
1793
|
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
1729
1794
|
const parentId = parentIdAttr ? parseInt(parentIdAttr, 10) : undefined;
|
|
1730
|
-
// Per ECMA-376, w:done
|
|
1731
|
-
const done = doneAttr
|
|
1795
|
+
// Per ECMA-376 §17.17.4, w:done is ST_OnOff — accept 1/0/true/false/on/off
|
|
1796
|
+
const done = parseOnOffAttribute(doneAttr);
|
|
1732
1797
|
|
|
1733
1798
|
// Parse content (runs from paragraphs within the comment)
|
|
1734
1799
|
const runs: Run[] = [];
|
|
@@ -1921,12 +1986,20 @@ export class DocumentParser {
|
|
|
1921
1986
|
// Parse optional column range for table bookmarks (ECMA-376 §17.16.5)
|
|
1922
1987
|
const colFirstAttr = XMLParser.extractAttribute(bookmarkXml, 'w:colFirst');
|
|
1923
1988
|
const colLastAttr = XMLParser.extractAttribute(bookmarkXml, 'w:colLast');
|
|
1989
|
+
// Parse optional w:displacedByCustomXml per CT_MarkupRange (§17.13.5).
|
|
1990
|
+
// Without this the attribute was dropped on load, so any Word document
|
|
1991
|
+
// with custom-XML-displaced bookmarks lost the disambiguator even
|
|
1992
|
+
// though the model now supports round-tripping it.
|
|
1993
|
+
const displacedAttr = XMLParser.extractAttribute(bookmarkXml, 'w:displacedByCustomXml');
|
|
1994
|
+
const displacedByCustomXml =
|
|
1995
|
+
displacedAttr === 'next' || displacedAttr === 'prev' ? displacedAttr : undefined;
|
|
1924
1996
|
const bookmark = new Bookmark({
|
|
1925
1997
|
name: nameAttr,
|
|
1926
1998
|
id: id,
|
|
1927
1999
|
skipNormalization: true,
|
|
1928
2000
|
colFirst: colFirstAttr ? parseInt(colFirstAttr, 10) : undefined,
|
|
1929
2001
|
colLast: colLastAttr ? parseInt(colLastAttr, 10) : undefined,
|
|
2002
|
+
displacedByCustomXml,
|
|
1930
2003
|
});
|
|
1931
2004
|
|
|
1932
2005
|
// Register with BookmarkManager to enable hasBookmark() checks
|
|
@@ -1969,12 +2042,22 @@ export class DocumentParser {
|
|
|
1969
2042
|
|
|
1970
2043
|
const id = parseInt(idAttr, 10);
|
|
1971
2044
|
|
|
2045
|
+
// CT_MarkupRange (§17.13.5) also permits w:displacedByCustomXml on
|
|
2046
|
+
// the end marker. Previously dropped on load, so a Word document
|
|
2047
|
+
// whose bookmark-end was displaced across a custom-XML node lost
|
|
2048
|
+
// the disambiguator even though the Bookmark model already emits
|
|
2049
|
+
// it from toEndXML().
|
|
2050
|
+
const displacedAttr = XMLParser.extractAttribute(bookmarkXml, 'w:displacedByCustomXml');
|
|
2051
|
+
const displacedByCustomXml =
|
|
2052
|
+
displacedAttr === 'next' || displacedAttr === 'prev' ? displacedAttr : undefined;
|
|
2053
|
+
|
|
1972
2054
|
// Create a placeholder bookmark for the end marker
|
|
1973
2055
|
// The name doesn't matter for bookmarkEnd as it only uses the ID
|
|
1974
2056
|
const bookmark = new Bookmark({
|
|
1975
2057
|
name: `_end_${id}`,
|
|
1976
2058
|
id: id,
|
|
1977
2059
|
skipNormalization: true,
|
|
2060
|
+
displacedByCustomXml,
|
|
1978
2061
|
});
|
|
1979
2062
|
|
|
1980
2063
|
return bookmark;
|
|
@@ -1996,12 +2079,23 @@ export class DocumentParser {
|
|
|
1996
2079
|
try {
|
|
1997
2080
|
const paragraph = new Paragraph();
|
|
1998
2081
|
|
|
1999
|
-
// Parse w14:paraId and w14:textId attributes from paragraph element
|
|
2000
|
-
|
|
2082
|
+
// Parse w14:paraId and w14:textId attributes from paragraph element
|
|
2083
|
+
// (Word 2010+, ST_LongHexNumber 8-char hex). XMLParser keys
|
|
2084
|
+
// attributes under the @_ prefix and may numeric-coerce purely-
|
|
2085
|
+
// digit hex strings like "00000001" to the number 1 — normalise
|
|
2086
|
+
// back to 8-char uppercase hex so the output passes strict
|
|
2087
|
+
// validation. The prior code used the un-prefixed element-shaped
|
|
2088
|
+
// keys and always saw `undefined`.
|
|
2089
|
+
const normaliseHexId = (raw: unknown): string | undefined => {
|
|
2090
|
+
if (raw === undefined || raw === null) return undefined;
|
|
2091
|
+
const asStr = typeof raw === 'number' ? raw.toString(16) : String(raw);
|
|
2092
|
+
return asStr.toUpperCase().padStart(8, '0');
|
|
2093
|
+
};
|
|
2094
|
+
const paraId = normaliseHexId(paraObj['@_w14:paraId']);
|
|
2001
2095
|
if (paraId) {
|
|
2002
2096
|
paragraph.formatting.paraId = paraId;
|
|
2003
2097
|
}
|
|
2004
|
-
const textId = paraObj['
|
|
2098
|
+
const textId = normaliseHexId(paraObj['@_w14:textId']);
|
|
2005
2099
|
if (textId) {
|
|
2006
2100
|
paragraph.formatting.textId = textId;
|
|
2007
2101
|
}
|
|
@@ -2034,6 +2128,7 @@ export class DocumentParser {
|
|
|
2034
2128
|
imageManager
|
|
2035
2129
|
);
|
|
2036
2130
|
if (imageRun) {
|
|
2131
|
+
this.parseRunPropertiesFromObject(child['w:rPr'], imageRun);
|
|
2037
2132
|
paragraph.addRun(imageRun);
|
|
2038
2133
|
}
|
|
2039
2134
|
}
|
|
@@ -2095,6 +2190,7 @@ export class DocumentParser {
|
|
|
2095
2190
|
imageManager
|
|
2096
2191
|
);
|
|
2097
2192
|
if (imageRun) {
|
|
2193
|
+
this.parseRunPropertiesFromObject(child['w:rPr'], imageRun);
|
|
2098
2194
|
paragraph.addRun(imageRun);
|
|
2099
2195
|
}
|
|
2100
2196
|
}
|
|
@@ -2171,6 +2267,22 @@ export class DocumentParser {
|
|
|
2171
2267
|
// Extract the formatting and set it as paragraph mark properties
|
|
2172
2268
|
paragraph.setParagraphMarkFormatting(tempRun.getFormatting());
|
|
2173
2269
|
|
|
2270
|
+
// Transfer w:rPrChange (CT_ParaRPrChange, §17.3.1.30) from the
|
|
2271
|
+
// temp run onto the paragraph's formatting. Without this the
|
|
2272
|
+
// paragraph-mark rPrChange is silently dropped because
|
|
2273
|
+
// `tempRun.getFormatting()` exposes RunFormatting fields only —
|
|
2274
|
+
// `propertyChangeRevision` is a separate field on Run that was
|
|
2275
|
+
// previously discarded along with the temp run.
|
|
2276
|
+
const rPrChangeRev = tempRun.getPropertyChangeRevision();
|
|
2277
|
+
if (rPrChangeRev) {
|
|
2278
|
+
paragraph.formatting.paragraphMarkRunPropertiesChange = {
|
|
2279
|
+
id: rPrChangeRev.id,
|
|
2280
|
+
author: rPrChangeRev.author,
|
|
2281
|
+
date: rPrChangeRev.date,
|
|
2282
|
+
previousProperties: rPrChangeRev.previousProperties,
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2174
2286
|
// Parse paragraph mark deletion tracking (w:del in w:pPr/w:rPr)
|
|
2175
2287
|
// Per ECMA-376 Part 1 §17.13.5.14 - indicates the paragraph mark was deleted
|
|
2176
2288
|
if (rPrObj['w:del']) {
|
|
@@ -2210,9 +2322,14 @@ export class DocumentParser {
|
|
|
2210
2322
|
paragraph.setAlignment(pPrObj['w:jc']['@_w:val']);
|
|
2211
2323
|
}
|
|
2212
2324
|
|
|
2213
|
-
// Style
|
|
2214
|
-
|
|
2215
|
-
|
|
2325
|
+
// Style (w:pStyle per ECMA-376 §17.3.1.27 — `w:val` is ST_String
|
|
2326
|
+
// referencing a style ID). Cast via String(...) so purely-numeric
|
|
2327
|
+
// style IDs that XMLParser's `parseAttributeValue: true` coerces to
|
|
2328
|
+
// JS numbers (e.g., a custom styleId of "1") survive as strings,
|
|
2329
|
+
// matching the `style?: string` field contract on
|
|
2330
|
+
// ParagraphFormatting.
|
|
2331
|
+
if (pPrObj['w:pStyle']?.['@_w:val'] !== undefined) {
|
|
2332
|
+
paragraph.setStyle(String(pPrObj['w:pStyle']['@_w:val']));
|
|
2216
2333
|
}
|
|
2217
2334
|
|
|
2218
2335
|
// Indentation
|
|
@@ -2231,6 +2348,24 @@ export class DocumentParser {
|
|
|
2231
2348
|
// Parse hanging indent per ECMA-376 Part 1 §17.3.1.17
|
|
2232
2349
|
if (isExplicitlySet(ind['@_w:hanging']))
|
|
2233
2350
|
paragraph.setHangingIndent(safeParseInt(ind['@_w:hanging']));
|
|
2351
|
+
|
|
2352
|
+
// CJK character-unit indentation attributes per ECMA-376 §17.3.1.12.
|
|
2353
|
+
// start/endChars are bidi-aware alternatives to left/rightChars; collapse
|
|
2354
|
+
// them onto the leftChars/rightChars fields the same way the twips parser
|
|
2355
|
+
// collapses w:start → left. Values are ST_DecimalNumber (hundredths of a
|
|
2356
|
+
// character unit), and 0 is a legitimate value — use isExplicitlySet so
|
|
2357
|
+
// number-0 from XMLParser.parseAttributeValue is preserved.
|
|
2358
|
+
if (!paragraph.formatting.indentation) paragraph.formatting.indentation = {};
|
|
2359
|
+
const leftCharsVal = ind['@_w:startChars'] ?? ind['@_w:leftChars'];
|
|
2360
|
+
const rightCharsVal = ind['@_w:endChars'] ?? ind['@_w:rightChars'];
|
|
2361
|
+
if (isExplicitlySet(leftCharsVal))
|
|
2362
|
+
paragraph.formatting.indentation.leftChars = safeParseInt(leftCharsVal);
|
|
2363
|
+
if (isExplicitlySet(rightCharsVal))
|
|
2364
|
+
paragraph.formatting.indentation.rightChars = safeParseInt(rightCharsVal);
|
|
2365
|
+
if (isExplicitlySet(ind['@_w:firstLineChars']))
|
|
2366
|
+
paragraph.formatting.indentation.firstLineChars = safeParseInt(ind['@_w:firstLineChars']);
|
|
2367
|
+
if (isExplicitlySet(ind['@_w:hangingChars']))
|
|
2368
|
+
paragraph.formatting.indentation.hangingChars = safeParseInt(ind['@_w:hangingChars']);
|
|
2234
2369
|
}
|
|
2235
2370
|
|
|
2236
2371
|
// Spacing (ECMA-376 §17.3.1.33 — 8 attributes)
|
|
@@ -2251,14 +2386,13 @@ export class DocumentParser {
|
|
|
2251
2386
|
paragraph.formatting.spacing.beforeLines = safeParseInt(spacing['@_w:beforeLines']);
|
|
2252
2387
|
if (isExplicitlySet(spacing['@_w:afterLines']))
|
|
2253
2388
|
paragraph.formatting.spacing.afterLines = safeParseInt(spacing['@_w:afterLines']);
|
|
2389
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
2254
2390
|
const beforeAuto = spacing['@_w:beforeAutospacing'];
|
|
2255
2391
|
if (beforeAuto !== undefined)
|
|
2256
|
-
paragraph.formatting.spacing.beforeAutospacing =
|
|
2257
|
-
String(beforeAuto) === '1' || String(beforeAuto) === 'true';
|
|
2392
|
+
paragraph.formatting.spacing.beforeAutospacing = parseOnOffAttribute(beforeAuto);
|
|
2258
2393
|
const afterAuto = spacing['@_w:afterAutospacing'];
|
|
2259
2394
|
if (afterAuto !== undefined)
|
|
2260
|
-
paragraph.formatting.spacing.afterAutospacing =
|
|
2261
|
-
String(afterAuto) === '1' || String(afterAuto) === 'true';
|
|
2395
|
+
paragraph.formatting.spacing.afterAutospacing = parseOnOffAttribute(afterAuto);
|
|
2262
2396
|
}
|
|
2263
2397
|
|
|
2264
2398
|
// Keep properties — preserve explicit val="0" to override style inheritance
|
|
@@ -2306,7 +2440,12 @@ export class DocumentParser {
|
|
|
2306
2440
|
const pBdr = pPrObj['w:pBdr'];
|
|
2307
2441
|
const borders: any = {};
|
|
2308
2442
|
|
|
2309
|
-
// Helper function to parse border definition
|
|
2443
|
+
// Helper function to parse border definition.
|
|
2444
|
+
// Covers the full CT_Border attribute set per ECMA-376 §17.18.2:
|
|
2445
|
+
// w:val, w:sz, w:color, w:space, w:themeColor, w:themeTint,
|
|
2446
|
+
// w:themeShade, w:shadow, w:frame. The last two are ST_OnOff —
|
|
2447
|
+
// route through parseOnOffAttribute so "off"/"false"/"0"/"on"
|
|
2448
|
+
// all resolve correctly even after XMLParser numeric coercion.
|
|
2310
2449
|
const parseBorder = (borderObj: any): any => {
|
|
2311
2450
|
if (!borderObj) return undefined;
|
|
2312
2451
|
const border: any = {};
|
|
@@ -2315,6 +2454,15 @@ export class DocumentParser {
|
|
|
2315
2454
|
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
2316
2455
|
if (borderObj['@_w:space'] !== undefined)
|
|
2317
2456
|
border.space = safeParseInt(borderObj['@_w:space']);
|
|
2457
|
+
if (borderObj['@_w:themeColor']) border.themeColor = String(borderObj['@_w:themeColor']);
|
|
2458
|
+
if (borderObj['@_w:themeTint']) border.themeTint = String(borderObj['@_w:themeTint']);
|
|
2459
|
+
if (borderObj['@_w:themeShade']) border.themeShade = String(borderObj['@_w:themeShade']);
|
|
2460
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
2461
|
+
border.shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
2462
|
+
}
|
|
2463
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
2464
|
+
border.frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
2465
|
+
}
|
|
2318
2466
|
return Object.keys(border).length > 0 ? border : undefined;
|
|
2319
2467
|
};
|
|
2320
2468
|
|
|
@@ -2353,7 +2501,15 @@ export class DocumentParser {
|
|
|
2353
2501
|
|
|
2354
2502
|
for (const tabObj of tabElements) {
|
|
2355
2503
|
const tab: any = {};
|
|
2356
|
-
|
|
2504
|
+
// w:pos is REQUIRED per §17.3.1.38 and is ST_SignedTwipsMeasure — 0 and
|
|
2505
|
+
// negative values are both valid. Use `!== undefined` so that XMLParser's
|
|
2506
|
+
// parseAttributeValue coercion of "0" to number 0 doesn't silently drop
|
|
2507
|
+
// tabs at the left margin (the previous `if (tabObj['@_w:pos'])` truthy
|
|
2508
|
+
// check turned pos=0 into an invisible tab-loss bug).
|
|
2509
|
+
if (tabObj['@_w:pos'] !== undefined) {
|
|
2510
|
+
const parsed = parseInt(String(tabObj['@_w:pos']), 10);
|
|
2511
|
+
if (!isNaN(parsed)) tab.position = parsed;
|
|
2512
|
+
}
|
|
2357
2513
|
if (tabObj['@_w:val']) tab.val = tabObj['@_w:val'];
|
|
2358
2514
|
if (tabObj['@_w:leader']) tab.leader = tabObj['@_w:leader'];
|
|
2359
2515
|
|
|
@@ -2369,19 +2525,10 @@ export class DocumentParser {
|
|
|
2369
2525
|
|
|
2370
2526
|
// Widow control per ECMA-376 Part 1 §17.3.1.40
|
|
2371
2527
|
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
|
-
}
|
|
2528
|
+
// Delegate to parseOoxmlBoolean so every ST_OnOff literal — including
|
|
2529
|
+
// "off" / "on" — resolves correctly. The previous bespoke check missed
|
|
2530
|
+
// "off", silently flipping explicit-off to explicit-on.
|
|
2531
|
+
paragraph.setWidowControl(parseOoxmlBoolean(pPrObj['w:widowControl']));
|
|
2385
2532
|
}
|
|
2386
2533
|
|
|
2387
2534
|
// Outline level per ECMA-376 Part 1 §17.3.1.19
|
|
@@ -2397,15 +2544,11 @@ export class DocumentParser {
|
|
|
2397
2544
|
paragraph.setSuppressLineNumbers(parseOoxmlBoolean(pPrObj['w:suppressLineNumbers']));
|
|
2398
2545
|
}
|
|
2399
2546
|
|
|
2400
|
-
// Bidirectional layout per ECMA-376 Part 1 §17.3.1.6
|
|
2547
|
+
// Bidirectional layout per ECMA-376 Part 1 §17.3.1.6 — delegate to
|
|
2548
|
+
// parseOoxmlBoolean so "off"/"on" literals resolve correctly (the
|
|
2549
|
+
// previous bespoke check missed them).
|
|
2401
2550
|
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
|
-
}
|
|
2551
|
+
paragraph.setBidi(parseOoxmlBoolean(pPrObj['w:bidi']));
|
|
2409
2552
|
}
|
|
2410
2553
|
|
|
2411
2554
|
// Text direction per ECMA-376 Part 1 §17.3.1.36
|
|
@@ -2423,20 +2566,10 @@ export class DocumentParser {
|
|
|
2423
2566
|
paragraph.setMirrorIndents(parseOoxmlBoolean(pPrObj['w:mirrorIndents']));
|
|
2424
2567
|
}
|
|
2425
2568
|
|
|
2426
|
-
// Auto-adjust right indent per ECMA-376 Part 1 §17.3.1.1
|
|
2569
|
+
// Auto-adjust right indent per ECMA-376 Part 1 §17.3.1.1 — delegate to
|
|
2570
|
+
// parseOoxmlBoolean so "off"/"on" literals resolve correctly.
|
|
2427
2571
|
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
|
-
}
|
|
2572
|
+
paragraph.setAdjustRightInd(parseOoxmlBoolean(pPrObj['w:adjustRightInd']));
|
|
2440
2573
|
}
|
|
2441
2574
|
|
|
2442
2575
|
// Text frame properties per ECMA-376 Part 1 §17.3.1.11
|
|
@@ -2510,11 +2643,16 @@ export class DocumentParser {
|
|
|
2510
2643
|
}
|
|
2511
2644
|
}
|
|
2512
2645
|
|
|
2513
|
-
// HTML div ID per ECMA-376 Part 1 §17.3.1.
|
|
2646
|
+
// HTML div ID per ECMA-376 Part 1 §17.3.1.10 (CT_DivId). `w:val` is
|
|
2647
|
+
// ST_DecimalNumber — 0 is a valid ID referencing the first div in
|
|
2648
|
+
// web settings. XMLParser coerces `"0"` to the number 0, and the
|
|
2649
|
+
// previous `if (divIdVal)` truthy check silently dropped it, breaking
|
|
2650
|
+
// the paragraph's link to div index 0 on every round-trip.
|
|
2514
2651
|
if (pPrObj['w:divId']) {
|
|
2515
2652
|
const divIdVal = pPrObj['w:divId']?.['@_w:val'];
|
|
2516
|
-
if (divIdVal) {
|
|
2517
|
-
|
|
2653
|
+
if (isExplicitlySet(divIdVal)) {
|
|
2654
|
+
const parsed = safeParseInt(divIdVal);
|
|
2655
|
+
if (!isNaN(parsed)) paragraph.setDivId(parsed);
|
|
2518
2656
|
}
|
|
2519
2657
|
}
|
|
2520
2658
|
|
|
@@ -2529,13 +2667,27 @@ export class DocumentParser {
|
|
|
2529
2667
|
}
|
|
2530
2668
|
}
|
|
2531
2669
|
|
|
2532
|
-
// Paragraph property change tracking per ECMA-376 Part 1 §17.3.1.27
|
|
2670
|
+
// Paragraph property change tracking per ECMA-376 Part 1 §17.3.1.27.
|
|
2671
|
+
// CT_TrackChange attributes — `w:id` (ST_DecimalNumber, required),
|
|
2672
|
+
// `w:author` (ST_String, required), `w:date` (ST_DateTime, optional).
|
|
2673
|
+
// XMLParser coerces `w:id="0"` to the number 0; the previous
|
|
2674
|
+
// `if (changeObj['@_w:id'])` truthy gate silently dropped id=0,
|
|
2675
|
+
// producing `<w:pPrChange w:author="…" w:date="…"/>` on emission —
|
|
2676
|
+
// missing the required `w:id` and failing strict validation. The
|
|
2677
|
+
// sibling `trPrChange` / `tblPrChange` / `tcPrChange` / `sectPrChange`
|
|
2678
|
+
// parsers already use `|| '0'` or `!== undefined` for the same reason.
|
|
2533
2679
|
if (pPrObj['w:pPrChange']) {
|
|
2534
2680
|
const changeObj = pPrObj['w:pPrChange'];
|
|
2535
2681
|
const change: any = {};
|
|
2536
|
-
if (changeObj['@_w:author']
|
|
2537
|
-
|
|
2538
|
-
|
|
2682
|
+
if (changeObj['@_w:author'] !== undefined) {
|
|
2683
|
+
change.author = String(changeObj['@_w:author']);
|
|
2684
|
+
}
|
|
2685
|
+
if (changeObj['@_w:date'] !== undefined) {
|
|
2686
|
+
change.date = String(changeObj['@_w:date']);
|
|
2687
|
+
}
|
|
2688
|
+
if (changeObj['@_w:id'] !== undefined) {
|
|
2689
|
+
change.id = String(changeObj['@_w:id']);
|
|
2690
|
+
}
|
|
2539
2691
|
|
|
2540
2692
|
// Parse child w:pPr for previousProperties to preserve tracked change history
|
|
2541
2693
|
if (changeObj['w:pPr']) {
|
|
@@ -2566,7 +2718,11 @@ export class DocumentParser {
|
|
|
2566
2718
|
}
|
|
2567
2719
|
|
|
2568
2720
|
// Parse previous indentation
|
|
2569
|
-
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to w:left/w:right
|
|
2721
|
+
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to w:left/w:right.
|
|
2722
|
+
// Also parse the six CJK character-unit variants (ST_DecimalNumber) per §17.3.1.12;
|
|
2723
|
+
// these round-trip alongside the twips so Word's rendering of the tracked "previous"
|
|
2724
|
+
// state stays locale-accurate for CJK-authored documents. Matches the iteration-21
|
|
2725
|
+
// fix on the main-path parser.
|
|
2570
2726
|
if (prevPPr['w:ind']) {
|
|
2571
2727
|
const ind = prevPPr['w:ind'];
|
|
2572
2728
|
previousProperties.indentation = {};
|
|
@@ -2578,6 +2734,18 @@ export class DocumentParser {
|
|
|
2578
2734
|
previousProperties.indentation.firstLine = parseInt(ind['@_w:firstLine'], 10);
|
|
2579
2735
|
if (ind['@_w:hanging'] !== undefined)
|
|
2580
2736
|
previousProperties.indentation.hanging = parseInt(ind['@_w:hanging'], 10);
|
|
2737
|
+
// CJK character-unit variants. startChars/endChars collapse onto
|
|
2738
|
+
// leftChars/rightChars (same pattern as the twips variants).
|
|
2739
|
+
const leftCharsVal = ind['@_w:startChars'] ?? ind['@_w:leftChars'];
|
|
2740
|
+
const rightCharsVal = ind['@_w:endChars'] ?? ind['@_w:rightChars'];
|
|
2741
|
+
if (leftCharsVal !== undefined)
|
|
2742
|
+
previousProperties.indentation.leftChars = parseInt(leftCharsVal, 10);
|
|
2743
|
+
if (rightCharsVal !== undefined)
|
|
2744
|
+
previousProperties.indentation.rightChars = parseInt(rightCharsVal, 10);
|
|
2745
|
+
if (ind['@_w:firstLineChars'] !== undefined)
|
|
2746
|
+
previousProperties.indentation.firstLineChars = parseInt(ind['@_w:firstLineChars'], 10);
|
|
2747
|
+
if (ind['@_w:hangingChars'] !== undefined)
|
|
2748
|
+
previousProperties.indentation.hangingChars = parseInt(ind['@_w:hangingChars'], 10);
|
|
2581
2749
|
}
|
|
2582
2750
|
|
|
2583
2751
|
// Parse previous alignment
|
|
@@ -2601,48 +2769,51 @@ export class DocumentParser {
|
|
|
2601
2769
|
previousProperties.spacing.beforeLines = parseInt(spacing['@_w:beforeLines'], 10);
|
|
2602
2770
|
if (spacing['@_w:afterLines'] !== undefined)
|
|
2603
2771
|
previousProperties.spacing.afterLines = parseInt(spacing['@_w:afterLines'], 10);
|
|
2772
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
2604
2773
|
const beforeAuto = spacing['@_w:beforeAutospacing'];
|
|
2605
2774
|
if (beforeAuto !== undefined)
|
|
2606
|
-
previousProperties.spacing.beforeAutospacing =
|
|
2607
|
-
String(beforeAuto) === '1' || String(beforeAuto) === 'true';
|
|
2775
|
+
previousProperties.spacing.beforeAutospacing = parseOnOffAttribute(beforeAuto);
|
|
2608
2776
|
const afterAuto = spacing['@_w:afterAutospacing'];
|
|
2609
2777
|
if (afterAuto !== undefined)
|
|
2610
|
-
previousProperties.spacing.afterAutospacing =
|
|
2611
|
-
String(afterAuto) === '1' || String(afterAuto) === 'true';
|
|
2778
|
+
previousProperties.spacing.afterAutospacing = parseOnOffAttribute(afterAuto);
|
|
2612
2779
|
}
|
|
2613
2780
|
|
|
2614
|
-
//
|
|
2781
|
+
// CT_OnOff properties per ECMA-376 §17.17.4 — accept "1"/"0"/"true"/"false"/"on"/"off"
|
|
2782
|
+
// plus the number forms produced by fast-xml-parser's parseAttributeValue. Using
|
|
2783
|
+
// parseOoxmlBoolean() keeps pPrChange round-trips consistent with the main pPr parser;
|
|
2784
|
+
// the previous `!== '0'` pattern silently flipped "false", "off", and the numeric 0.
|
|
2615
2785
|
if (prevPPr['w:keepNext']) {
|
|
2616
|
-
previousProperties.keepNext = prevPPr['w:keepNext']
|
|
2786
|
+
previousProperties.keepNext = parseOoxmlBoolean(prevPPr['w:keepNext']);
|
|
2617
2787
|
}
|
|
2618
2788
|
if (prevPPr['w:keepLines']) {
|
|
2619
|
-
previousProperties.keepLines = prevPPr['w:keepLines']
|
|
2789
|
+
previousProperties.keepLines = parseOoxmlBoolean(prevPPr['w:keepLines']);
|
|
2620
2790
|
}
|
|
2621
2791
|
if (prevPPr['w:pageBreakBefore']) {
|
|
2622
|
-
previousProperties.pageBreakBefore = prevPPr['w:pageBreakBefore']
|
|
2792
|
+
previousProperties.pageBreakBefore = parseOoxmlBoolean(prevPPr['w:pageBreakBefore']);
|
|
2623
2793
|
}
|
|
2624
2794
|
|
|
2625
2795
|
// === Extended paragraph property parsing per ECMA-376 Part 1 §17.3.1 ===
|
|
2626
2796
|
|
|
2627
2797
|
// Parse widowControl (w:widowControl) - orphan/widow control
|
|
2628
2798
|
if (prevPPr['w:widowControl']) {
|
|
2629
|
-
previousProperties.widowControl = prevPPr['w:widowControl']
|
|
2799
|
+
previousProperties.widowControl = parseOoxmlBoolean(prevPPr['w:widowControl']);
|
|
2630
2800
|
}
|
|
2631
2801
|
|
|
2632
2802
|
// Parse suppressAutoHyphens (w:suppressAutoHyphens)
|
|
2633
2803
|
if (prevPPr['w:suppressAutoHyphens']) {
|
|
2634
|
-
previousProperties.suppressAutoHyphens =
|
|
2635
|
-
prevPPr['w:suppressAutoHyphens']
|
|
2804
|
+
previousProperties.suppressAutoHyphens = parseOoxmlBoolean(
|
|
2805
|
+
prevPPr['w:suppressAutoHyphens']
|
|
2806
|
+
);
|
|
2636
2807
|
}
|
|
2637
2808
|
|
|
2638
2809
|
// Parse contextualSpacing (w:contextualSpacing)
|
|
2639
2810
|
if (prevPPr['w:contextualSpacing']) {
|
|
2640
|
-
previousProperties.contextualSpacing = prevPPr['w:contextualSpacing']
|
|
2811
|
+
previousProperties.contextualSpacing = parseOoxmlBoolean(prevPPr['w:contextualSpacing']);
|
|
2641
2812
|
}
|
|
2642
2813
|
|
|
2643
2814
|
// Parse mirrorIndents (w:mirrorIndents)
|
|
2644
2815
|
if (prevPPr['w:mirrorIndents']) {
|
|
2645
|
-
previousProperties.mirrorIndents = prevPPr['w:mirrorIndents']
|
|
2816
|
+
previousProperties.mirrorIndents = parseOoxmlBoolean(prevPPr['w:mirrorIndents']);
|
|
2646
2817
|
}
|
|
2647
2818
|
|
|
2648
2819
|
// Parse outlineLevel (w:outlineLvl @w:val)
|
|
@@ -2650,40 +2821,106 @@ export class DocumentParser {
|
|
|
2650
2821
|
previousProperties.outlineLevel = parseInt(prevPPr['w:outlineLvl']['@_w:val'], 10);
|
|
2651
2822
|
}
|
|
2652
2823
|
|
|
2824
|
+
// Parse previous text frame properties (w:framePr) per ECMA-376
|
|
2825
|
+
// Part 1 §17.3.1.11 CT_FramePr. The pPrChange emitter already
|
|
2826
|
+
// rebuilds every framePr attribute (see Paragraph.ts §3634), but
|
|
2827
|
+
// the parser never read them — so a tracked change to any
|
|
2828
|
+
// frame property (drop-cap, text-box positioning, wrap mode,
|
|
2829
|
+
// anchor lock…) silently lost the previous state on round-trip.
|
|
2830
|
+
if (prevPPr['w:framePr']) {
|
|
2831
|
+
const framePr = prevPPr['w:framePr'];
|
|
2832
|
+
const frameProps: any = {};
|
|
2833
|
+
if (isExplicitlySet(framePr['@_w:w'])) frameProps.w = safeParseInt(framePr['@_w:w']);
|
|
2834
|
+
if (isExplicitlySet(framePr['@_w:h'])) frameProps.h = safeParseInt(framePr['@_w:h']);
|
|
2835
|
+
if (framePr['@_w:hRule']) frameProps.hRule = String(framePr['@_w:hRule']);
|
|
2836
|
+
if (isExplicitlySet(framePr['@_w:x'])) frameProps.x = safeParseInt(framePr['@_w:x']);
|
|
2837
|
+
if (isExplicitlySet(framePr['@_w:y'])) frameProps.y = safeParseInt(framePr['@_w:y']);
|
|
2838
|
+
if (framePr['@_w:xAlign']) frameProps.xAlign = String(framePr['@_w:xAlign']);
|
|
2839
|
+
if (framePr['@_w:yAlign']) frameProps.yAlign = String(framePr['@_w:yAlign']);
|
|
2840
|
+
if (framePr['@_w:hAnchor']) frameProps.hAnchor = String(framePr['@_w:hAnchor']);
|
|
2841
|
+
if (framePr['@_w:vAnchor']) frameProps.vAnchor = String(framePr['@_w:vAnchor']);
|
|
2842
|
+
if (isExplicitlySet(framePr['@_w:hSpace'])) {
|
|
2843
|
+
frameProps.hSpace = safeParseInt(framePr['@_w:hSpace']);
|
|
2844
|
+
}
|
|
2845
|
+
if (isExplicitlySet(framePr['@_w:vSpace'])) {
|
|
2846
|
+
frameProps.vSpace = safeParseInt(framePr['@_w:vSpace']);
|
|
2847
|
+
}
|
|
2848
|
+
if (framePr['@_w:wrap']) frameProps.wrap = String(framePr['@_w:wrap']);
|
|
2849
|
+
if (framePr['@_w:dropCap']) frameProps.dropCap = String(framePr['@_w:dropCap']);
|
|
2850
|
+
if (isExplicitlySet(framePr['@_w:lines'])) {
|
|
2851
|
+
frameProps.lines = safeParseInt(framePr['@_w:lines']);
|
|
2852
|
+
}
|
|
2853
|
+
if (isExplicitlySet(framePr['@_w:anchorLock'])) {
|
|
2854
|
+
frameProps.anchorLock = parseOnOffAttribute(String(framePr['@_w:anchorLock']), true);
|
|
2855
|
+
}
|
|
2856
|
+
if (Object.keys(frameProps).length > 0) {
|
|
2857
|
+
previousProperties.framePr = frameProps;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2653
2861
|
// Parse bidi (w:bidi) - right-to-left paragraph
|
|
2654
2862
|
if (prevPPr['w:bidi']) {
|
|
2655
|
-
previousProperties.bidi = prevPPr['w:bidi']
|
|
2863
|
+
previousProperties.bidi = parseOoxmlBoolean(prevPPr['w:bidi']);
|
|
2656
2864
|
}
|
|
2657
2865
|
|
|
2658
2866
|
// Parse suppressLineNumbers (w:suppressLineNumbers)
|
|
2659
2867
|
if (prevPPr['w:suppressLineNumbers']) {
|
|
2660
|
-
previousProperties.suppressLineNumbers =
|
|
2661
|
-
prevPPr['w:suppressLineNumbers']
|
|
2868
|
+
previousProperties.suppressLineNumbers = parseOoxmlBoolean(
|
|
2869
|
+
prevPPr['w:suppressLineNumbers']
|
|
2870
|
+
);
|
|
2662
2871
|
}
|
|
2663
2872
|
|
|
2664
2873
|
// Parse adjustRightInd (w:adjustRightInd)
|
|
2665
2874
|
if (prevPPr['w:adjustRightInd']) {
|
|
2666
|
-
previousProperties.adjustRightInd = prevPPr['w:adjustRightInd']
|
|
2875
|
+
previousProperties.adjustRightInd = parseOoxmlBoolean(prevPPr['w:adjustRightInd']);
|
|
2667
2876
|
}
|
|
2668
2877
|
|
|
2669
2878
|
// Parse snapToGrid (w:snapToGrid)
|
|
2670
2879
|
if (prevPPr['w:snapToGrid']) {
|
|
2671
|
-
previousProperties.snapToGrid = prevPPr['w:snapToGrid']
|
|
2880
|
+
previousProperties.snapToGrid = parseOoxmlBoolean(prevPPr['w:snapToGrid']);
|
|
2672
2881
|
}
|
|
2673
2882
|
|
|
2674
2883
|
// Parse wordWrap (w:wordWrap)
|
|
2675
2884
|
if (prevPPr['w:wordWrap']) {
|
|
2676
|
-
previousProperties.wordWrap = prevPPr['w:wordWrap']
|
|
2885
|
+
previousProperties.wordWrap = parseOoxmlBoolean(prevPPr['w:wordWrap']);
|
|
2677
2886
|
}
|
|
2678
2887
|
|
|
2679
2888
|
// Parse autoSpaceDE (w:autoSpaceDE) - East Asian/numeric spacing
|
|
2680
2889
|
if (prevPPr['w:autoSpaceDE']) {
|
|
2681
|
-
previousProperties.autoSpaceDE = prevPPr['w:autoSpaceDE']
|
|
2890
|
+
previousProperties.autoSpaceDE = parseOoxmlBoolean(prevPPr['w:autoSpaceDE']);
|
|
2682
2891
|
}
|
|
2683
2892
|
|
|
2684
2893
|
// Parse autoSpaceDN (w:autoSpaceDN) - East Asian/Western spacing
|
|
2685
2894
|
if (prevPPr['w:autoSpaceDN']) {
|
|
2686
|
-
previousProperties.autoSpaceDN = prevPPr['w:autoSpaceDN']
|
|
2895
|
+
previousProperties.autoSpaceDN = parseOoxmlBoolean(prevPPr['w:autoSpaceDN']);
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
// Parse kinsoku / overflowPunct / topLinePunct / suppressOverlap —
|
|
2899
|
+
// CJK typography CT_OnOff flags. The Paragraph pPrChange generator
|
|
2900
|
+
// already emits these in the previous-properties block, but the
|
|
2901
|
+
// parser was missing the read side, so tracked paragraph-property
|
|
2902
|
+
// revisions that recorded any of these four flags were silently
|
|
2903
|
+
// dropped on load → save. Uses `parseOoxmlBoolean` to honour every
|
|
2904
|
+
// ST_OnOff literal (bare, 1/0, true/false, on/off).
|
|
2905
|
+
if (prevPPr['w:kinsoku']) {
|
|
2906
|
+
(previousProperties as { kinsoku?: boolean }).kinsoku = parseOoxmlBoolean(
|
|
2907
|
+
prevPPr['w:kinsoku']
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
if (prevPPr['w:overflowPunct']) {
|
|
2911
|
+
(previousProperties as { overflowPunct?: boolean }).overflowPunct = parseOoxmlBoolean(
|
|
2912
|
+
prevPPr['w:overflowPunct']
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
if (prevPPr['w:topLinePunct']) {
|
|
2916
|
+
(previousProperties as { topLinePunct?: boolean }).topLinePunct = parseOoxmlBoolean(
|
|
2917
|
+
prevPPr['w:topLinePunct']
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
if (prevPPr['w:suppressOverlap']) {
|
|
2921
|
+
(previousProperties as { suppressOverlap?: boolean }).suppressOverlap = parseOoxmlBoolean(
|
|
2922
|
+
prevPPr['w:suppressOverlap']
|
|
2923
|
+
);
|
|
2687
2924
|
}
|
|
2688
2925
|
|
|
2689
2926
|
// Parse textDirection (w:textDirection @w:val)
|
|
@@ -2696,23 +2933,68 @@ export class DocumentParser {
|
|
|
2696
2933
|
previousProperties.textAlignment = String(prevPPr['w:textAlignment']['@_w:val']);
|
|
2697
2934
|
}
|
|
2698
2935
|
|
|
2936
|
+
// Parse previous divId (w:divId) per ECMA-376 §17.3.1.10 —
|
|
2937
|
+
// ST_DecimalNumber referencing a web-settings div. Zero is a
|
|
2938
|
+
// legal ID (first div). XMLParser coerces `"0"` to number 0, so
|
|
2939
|
+
// gate via `isExplicitlySet` to preserve divId=0 on tracked
|
|
2940
|
+
// previous state. The pPrChange emitter (Paragraph.ts §3915)
|
|
2941
|
+
// re-emits prev.divId via `!== undefined`.
|
|
2942
|
+
if (prevPPr['w:divId']?.['@_w:val'] !== undefined) {
|
|
2943
|
+
const rawDivId = prevPPr['w:divId']['@_w:val'];
|
|
2944
|
+
const parsedDivId = safeParseInt(rawDivId);
|
|
2945
|
+
if (!isNaN(parsedDivId)) {
|
|
2946
|
+
previousProperties.divId = parsedDivId;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
// Parse previous cnfStyle (w:cnfStyle) per ECMA-376 §17.3.1.8 —
|
|
2951
|
+
// 12-character bitmask identifying which conditional-formatting
|
|
2952
|
+
// flags from the parent table style apply. XMLParser coerces
|
|
2953
|
+
// purely-numeric hex strings, but the custom parseValue keeps
|
|
2954
|
+
// 7+-digit strings as-is (so 12-char bitmasks survive); use
|
|
2955
|
+
// String + padStart to defensively normalise any shorter form.
|
|
2956
|
+
if (prevPPr['w:cnfStyle']?.['@_w:val'] !== undefined) {
|
|
2957
|
+
previousProperties.cnfStyle = String(prevPPr['w:cnfStyle']['@_w:val']).padStart(12, '0');
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2699
2960
|
// Parse paragraph borders (w:pBdr) per ECMA-376 Part 1 §17.3.1.24
|
|
2961
|
+
// Previous versions stored the attribute values under the wrong
|
|
2962
|
+
// field names (`val`/`sz` instead of `style`/`size`) — the
|
|
2963
|
+
// paragraph emitter reads `style`/`size`, so every tracked
|
|
2964
|
+
// previous border collapsed to `<w:top w:val="nil"/>` on
|
|
2965
|
+
// round-trip. The CT_Border attribute coverage here now matches
|
|
2966
|
+
// the main parser (§17.18.2): all nine attrs, with shadow/frame
|
|
2967
|
+
// routed through parseOnOffAttribute so ST_OnOff literals
|
|
2968
|
+
// ("on"/"off"/"true"/"false") resolve correctly.
|
|
2700
2969
|
if (prevPPr['w:pBdr']) {
|
|
2701
2970
|
const pBdr = prevPPr['w:pBdr'];
|
|
2702
2971
|
previousProperties.borders = {};
|
|
2703
2972
|
|
|
2704
2973
|
const parseBorder = (borderObj: any) => {
|
|
2705
2974
|
if (!borderObj) return undefined;
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
themeColor
|
|
2715
|
-
}
|
|
2975
|
+
const border: any = {};
|
|
2976
|
+
if (borderObj['@_w:val']) border.style = borderObj['@_w:val'];
|
|
2977
|
+
if (borderObj['@_w:sz'] !== undefined) border.size = safeParseInt(borderObj['@_w:sz']);
|
|
2978
|
+
if (borderObj['@_w:space'] !== undefined) {
|
|
2979
|
+
border.space = safeParseInt(borderObj['@_w:space']);
|
|
2980
|
+
}
|
|
2981
|
+
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
2982
|
+
if (borderObj['@_w:themeColor']) {
|
|
2983
|
+
border.themeColor = String(borderObj['@_w:themeColor']);
|
|
2984
|
+
}
|
|
2985
|
+
if (borderObj['@_w:themeTint']) {
|
|
2986
|
+
border.themeTint = String(borderObj['@_w:themeTint']);
|
|
2987
|
+
}
|
|
2988
|
+
if (borderObj['@_w:themeShade']) {
|
|
2989
|
+
border.themeShade = String(borderObj['@_w:themeShade']);
|
|
2990
|
+
}
|
|
2991
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
2992
|
+
border.shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
2993
|
+
}
|
|
2994
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
2995
|
+
border.frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
2996
|
+
}
|
|
2997
|
+
return Object.keys(border).length > 0 ? border : undefined;
|
|
2716
2998
|
};
|
|
2717
2999
|
|
|
2718
3000
|
if (pBdr['w:top']) previousProperties.borders.top = parseBorder(pBdr['w:top']);
|
|
@@ -3873,11 +4155,15 @@ export class DocumentParser {
|
|
|
3873
4155
|
return XMLBuilder.unescapeXml(String(node));
|
|
3874
4156
|
};
|
|
3875
4157
|
|
|
3876
|
-
|
|
4158
|
+
// Field-character attributes (w:dirty, w:fldLock, w:lock on w:fldChar) are
|
|
4159
|
+
// ST_OnOff per ECMA-376 §17.16.18. Delegate to parseOnOffAttribute so every
|
|
4160
|
+
// literal is honoured — the previous inline check missed "on" (silently
|
|
4161
|
+
// coerced to false) and was tighter than the spec requires.
|
|
4162
|
+
const parseBooleanAttr = (value: unknown): boolean | undefined => {
|
|
3877
4163
|
if (value === undefined || value === null) {
|
|
3878
4164
|
return undefined;
|
|
3879
4165
|
}
|
|
3880
|
-
return value
|
|
4166
|
+
return parseOnOffAttribute(value);
|
|
3881
4167
|
};
|
|
3882
4168
|
|
|
3883
4169
|
// Parse w:ffData from a fldChar object (form field data per ECMA-376 §17.16.17)
|
|
@@ -3892,15 +4178,13 @@ export class DocumentParser {
|
|
|
3892
4178
|
if (ffDataObj['w:name']?.['@_w:val'] !== undefined) {
|
|
3893
4179
|
ffd.name = String(ffDataObj['w:name']['@_w:val']);
|
|
3894
4180
|
}
|
|
3895
|
-
// w:enabled
|
|
4181
|
+
// w:enabled — CT_OnOff per ECMA-376 §17.16.11; presence = true, w:val honours ST_OnOff
|
|
3896
4182
|
if (ffDataObj['w:enabled'] !== undefined) {
|
|
3897
|
-
|
|
3898
|
-
ffd.enabled = enabledVal === '0' || enabledVal === 0 ? false : true;
|
|
4183
|
+
ffd.enabled = parseOoxmlBoolean(ffDataObj['w:enabled']);
|
|
3899
4184
|
}
|
|
3900
|
-
// w:calcOnExit
|
|
4185
|
+
// w:calcOnExit — CT_OnOff per ECMA-376 §17.16.4; presence = true, w:val honours ST_OnOff
|
|
3901
4186
|
if (ffDataObj['w:calcOnExit'] !== undefined) {
|
|
3902
|
-
|
|
3903
|
-
ffd.calcOnExit = calcVal === '1' || calcVal === 1 || calcVal === true;
|
|
4187
|
+
ffd.calcOnExit = parseOoxmlBoolean(ffDataObj['w:calcOnExit']);
|
|
3904
4188
|
}
|
|
3905
4189
|
// w:helpText
|
|
3906
4190
|
if (ffDataObj['w:helpText']?.['@_w:val'] !== undefined) {
|
|
@@ -3936,13 +4220,14 @@ export class DocumentParser {
|
|
|
3936
4220
|
if (ffDataObj['w:checkBox'] !== undefined) {
|
|
3937
4221
|
const cb: XmlNode = ffDataObj['w:checkBox'];
|
|
3938
4222
|
const checkBox: FormFieldCheckBox = { type: 'checkBox' };
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
4223
|
+
// w:default / w:checked are CT_OnOff per ECMA-376 §17.16.18 —
|
|
4224
|
+
// honour every ST_OnOff literal ("true"/"false"/"1"/"0"/"on"/"off")
|
|
4225
|
+
// and treat a bare self-closing element as true.
|
|
4226
|
+
if (cb['w:default'] !== undefined) {
|
|
4227
|
+
checkBox.defaultChecked = parseOoxmlBoolean(cb['w:default']);
|
|
3942
4228
|
}
|
|
3943
|
-
if (cb['w:checked']
|
|
3944
|
-
checkBox.checked =
|
|
3945
|
-
cb['w:checked']['@_w:val'] === '1' || cb['w:checked']['@_w:val'] === 1;
|
|
4229
|
+
if (cb['w:checked'] !== undefined) {
|
|
4230
|
+
checkBox.checked = parseOoxmlBoolean(cb['w:checked']);
|
|
3946
4231
|
}
|
|
3947
4232
|
if (cb['w:size']?.['@_w:val'] !== undefined) {
|
|
3948
4233
|
checkBox.size = Number(cb['w:size']['@_w:val']);
|
|
@@ -4146,30 +4431,47 @@ export class DocumentParser {
|
|
|
4146
4431
|
content.push({ type: 'annotationRef' });
|
|
4147
4432
|
break;
|
|
4148
4433
|
|
|
4149
|
-
// Footnote reference (w:footnoteReference) per ECMA-376 Part 1 §17.11.13
|
|
4434
|
+
// Footnote reference (w:footnoteReference) per ECMA-376 Part 1 §17.11.13.
|
|
4435
|
+
// w:customMarkFollows is ST_OnOff — honour every literal via parseOnOffAttribute.
|
|
4150
4436
|
case 'w:footnoteReference': {
|
|
4151
4437
|
const fnRefElements = toArray(runObj['w:footnoteReference']);
|
|
4152
4438
|
const fnRef = fnRefElements[elementIndex] || fnRefElements[0];
|
|
4153
4439
|
const fnId = fnRef?.['@_w:id'];
|
|
4440
|
+
const fnCustomMark = fnRef?.['@_w:customMarkFollows'];
|
|
4154
4441
|
content.push({
|
|
4155
4442
|
type: 'footnoteReference',
|
|
4156
4443
|
footnoteId: fnId !== undefined ? parseInt(fnId, 10) : undefined,
|
|
4444
|
+
customMarkFollows:
|
|
4445
|
+
fnCustomMark !== undefined ? parseOnOffAttribute(fnCustomMark) : undefined,
|
|
4157
4446
|
});
|
|
4158
4447
|
break;
|
|
4159
4448
|
}
|
|
4160
4449
|
|
|
4161
|
-
// Endnote reference (w:endnoteReference) per ECMA-376 Part 1 §17.11.2
|
|
4450
|
+
// Endnote reference (w:endnoteReference) per ECMA-376 Part 1 §17.11.2.
|
|
4451
|
+
// Same ST_OnOff treatment for w:customMarkFollows.
|
|
4162
4452
|
case 'w:endnoteReference': {
|
|
4163
4453
|
const enRefElements = toArray(runObj['w:endnoteReference']);
|
|
4164
4454
|
const enRef = enRefElements[elementIndex] || enRefElements[0];
|
|
4165
4455
|
const enId = enRef?.['@_w:id'];
|
|
4456
|
+
const enCustomMark = enRef?.['@_w:customMarkFollows'];
|
|
4166
4457
|
content.push({
|
|
4167
4458
|
type: 'endnoteReference',
|
|
4168
4459
|
endnoteId: enId !== undefined ? parseInt(enId, 10) : undefined,
|
|
4460
|
+
customMarkFollows:
|
|
4461
|
+
enCustomMark !== undefined ? parseOnOffAttribute(enCustomMark) : undefined,
|
|
4169
4462
|
});
|
|
4170
4463
|
break;
|
|
4171
4464
|
}
|
|
4172
4465
|
|
|
4466
|
+
// Auto-numbered marks INSIDE a footnote/endnote body per
|
|
4467
|
+
// ECMA-376 §17.11.14 / §17.11.3. Empty self-closing elements.
|
|
4468
|
+
case 'w:footnoteRef':
|
|
4469
|
+
content.push({ type: 'footnoteRef' });
|
|
4470
|
+
break;
|
|
4471
|
+
case 'w:endnoteRef':
|
|
4472
|
+
content.push({ type: 'endnoteRef' });
|
|
4473
|
+
break;
|
|
4474
|
+
|
|
4173
4475
|
case 'w:dayShort':
|
|
4174
4476
|
content.push({ type: 'dayShort' });
|
|
4175
4477
|
break;
|
|
@@ -4355,14 +4657,18 @@ export class DocumentParser {
|
|
|
4355
4657
|
if (runObj['w:annotationRef'] !== undefined) {
|
|
4356
4658
|
content.push({ type: 'annotationRef' });
|
|
4357
4659
|
}
|
|
4358
|
-
// Footnote/endnote reference fallback
|
|
4660
|
+
// Footnote/endnote reference fallback. w:customMarkFollows is ST_OnOff
|
|
4661
|
+
// per ECMA-376 §17.11.13 / §17.11.2 — honour every literal.
|
|
4359
4662
|
if (runObj['w:footnoteReference'] !== undefined) {
|
|
4360
4663
|
const fnRefElements = toArray(runObj['w:footnoteReference']);
|
|
4361
4664
|
for (const fnRef of fnRefElements) {
|
|
4362
4665
|
const fnId = fnRef?.['@_w:id'];
|
|
4666
|
+
const fnCustomMark = fnRef?.['@_w:customMarkFollows'];
|
|
4363
4667
|
content.push({
|
|
4364
4668
|
type: 'footnoteReference',
|
|
4365
4669
|
footnoteId: fnId !== undefined ? parseInt(fnId, 10) : undefined,
|
|
4670
|
+
customMarkFollows:
|
|
4671
|
+
fnCustomMark !== undefined ? parseOnOffAttribute(fnCustomMark) : undefined,
|
|
4366
4672
|
});
|
|
4367
4673
|
}
|
|
4368
4674
|
}
|
|
@@ -4370,12 +4676,22 @@ export class DocumentParser {
|
|
|
4370
4676
|
const enRefElements = toArray(runObj['w:endnoteReference']);
|
|
4371
4677
|
for (const enRef of enRefElements) {
|
|
4372
4678
|
const enId = enRef?.['@_w:id'];
|
|
4679
|
+
const enCustomMark = enRef?.['@_w:customMarkFollows'];
|
|
4373
4680
|
content.push({
|
|
4374
4681
|
type: 'endnoteReference',
|
|
4375
4682
|
endnoteId: enId !== undefined ? parseInt(enId, 10) : undefined,
|
|
4683
|
+
customMarkFollows:
|
|
4684
|
+
enCustomMark !== undefined ? parseOnOffAttribute(enCustomMark) : undefined,
|
|
4376
4685
|
});
|
|
4377
4686
|
}
|
|
4378
4687
|
}
|
|
4688
|
+
// Auto-numbered marks INSIDE a footnote/endnote body — empty elements.
|
|
4689
|
+
if (runObj['w:footnoteRef'] !== undefined) {
|
|
4690
|
+
content.push({ type: 'footnoteRef' });
|
|
4691
|
+
}
|
|
4692
|
+
if (runObj['w:endnoteRef'] !== undefined) {
|
|
4693
|
+
content.push({ type: 'endnoteRef' });
|
|
4694
|
+
}
|
|
4379
4695
|
if (runObj['w:dayShort'] !== undefined) {
|
|
4380
4696
|
content.push({ type: 'dayShort' });
|
|
4381
4697
|
}
|
|
@@ -4474,12 +4790,40 @@ export class DocumentParser {
|
|
|
4474
4790
|
: [hyperlinkObj['w:bookmarkStart']];
|
|
4475
4791
|
for (const bs of bookmarkStarts) {
|
|
4476
4792
|
const id = bs['@_w:id'];
|
|
4477
|
-
|
|
4793
|
+
// w:name is ST_String per §17.16.5 CT_Bookmark. XMLParser
|
|
4794
|
+
// coerces purely-numeric bookmark names ("12345") to JS
|
|
4795
|
+
// numbers; cast so Bookmark.name holds the declared string
|
|
4796
|
+
// type contract (parent parsers already do the same —
|
|
4797
|
+
// iter 125 toOptString helper).
|
|
4798
|
+
const rawName = bs['@_w:name'];
|
|
4799
|
+
const name =
|
|
4800
|
+
rawName === undefined || rawName === null || rawName === ''
|
|
4801
|
+
? undefined
|
|
4802
|
+
: String(rawName);
|
|
4478
4803
|
if (id !== undefined && name) {
|
|
4804
|
+
// CT_Bookmark per ECMA-376 §17.16.5: the object-form parser
|
|
4805
|
+
// must carry the same four "markup" attributes that the
|
|
4806
|
+
// XML-string bookmarkStart parser handles — colFirst/colLast
|
|
4807
|
+
// (table-column-scoped bookmarks) and displacedByCustomXml
|
|
4808
|
+
// (custom-XML boundary disambiguator). Previously dropped
|
|
4809
|
+
// whenever a hyperlink wrapped a bookmark, so inline
|
|
4810
|
+
// hyperlinks anchored to table-column bookmarks lost their
|
|
4811
|
+
// column range on round-trip.
|
|
4812
|
+
const rawColFirst = bs['@_w:colFirst'];
|
|
4813
|
+
const rawColLast = bs['@_w:colLast'];
|
|
4814
|
+
const rawDisplaced = bs['@_w:displacedByCustomXml'];
|
|
4815
|
+
const colFirst =
|
|
4816
|
+
rawColFirst === undefined ? undefined : parseInt(String(rawColFirst), 10);
|
|
4817
|
+
const colLast = rawColLast === undefined ? undefined : parseInt(String(rawColLast), 10);
|
|
4818
|
+
const displacedByCustomXml =
|
|
4819
|
+
rawDisplaced === 'next' || rawDisplaced === 'prev' ? rawDisplaced : undefined;
|
|
4479
4820
|
const bookmark = new Bookmark({
|
|
4480
4821
|
name: name,
|
|
4481
4822
|
id: typeof id === 'number' ? id : parseInt(id, 10),
|
|
4482
4823
|
skipNormalization: true,
|
|
4824
|
+
colFirst: Number.isNaN(colFirst as number) ? undefined : colFirst,
|
|
4825
|
+
colLast: Number.isNaN(colLast as number) ? undefined : colLast,
|
|
4826
|
+
displacedByCustomXml,
|
|
4483
4827
|
});
|
|
4484
4828
|
result.bookmarkStarts.push(bookmark);
|
|
4485
4829
|
// Also register with BookmarkManager
|
|
@@ -4501,23 +4845,47 @@ export class DocumentParser {
|
|
|
4501
4845
|
for (const be of bookmarkEnds) {
|
|
4502
4846
|
const id = be['@_w:id'];
|
|
4503
4847
|
if (id !== undefined) {
|
|
4848
|
+
// CT_MarkupRange per ECMA-376 §17.13.5 — preserve
|
|
4849
|
+
// w:displacedByCustomXml on bookmarkEnd when a custom-XML
|
|
4850
|
+
// boundary forced the marker to be displaced.
|
|
4851
|
+
const rawDisplaced = be['@_w:displacedByCustomXml'];
|
|
4852
|
+
const displacedByCustomXml =
|
|
4853
|
+
rawDisplaced === 'next' || rawDisplaced === 'prev' ? rawDisplaced : undefined;
|
|
4504
4854
|
const bookmark = new Bookmark({
|
|
4505
4855
|
name: `_end_${id}`,
|
|
4506
4856
|
id: typeof id === 'number' ? id : parseInt(id, 10),
|
|
4507
4857
|
skipNormalization: true,
|
|
4858
|
+
displacedByCustomXml,
|
|
4508
4859
|
});
|
|
4509
4860
|
result.bookmarkEnds.push(bookmark);
|
|
4510
4861
|
}
|
|
4511
4862
|
}
|
|
4512
4863
|
}
|
|
4513
4864
|
|
|
4514
|
-
// Extract hyperlink attributes
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4865
|
+
// Extract hyperlink attributes. Per ECMA-376 §17.16.22 CT_Hyperlink,
|
|
4866
|
+
// w:anchor / w:tooltip / w:tgtFrame / w:docLocation / r:id are all
|
|
4867
|
+
// ST_String. XMLParser's `parseAttributeValue: true` coerces
|
|
4868
|
+
// purely-numeric strings (e.g., a bookmark name like "12345") to
|
|
4869
|
+
// JS numbers — cast via String(...) so downstream `Hyperlink`
|
|
4870
|
+
// storage and string-method callers see the declared `string`
|
|
4871
|
+
// type contract.
|
|
4872
|
+
const toOptString = (v: unknown): string | undefined =>
|
|
4873
|
+
v === undefined || v === null ? undefined : String(v);
|
|
4874
|
+
const relationshipId = toOptString(hyperlinkObj['@_r:id']);
|
|
4875
|
+
const anchor = toOptString(hyperlinkObj['@_w:anchor']);
|
|
4876
|
+
const tooltip = toOptString(hyperlinkObj['@_w:tooltip']);
|
|
4877
|
+
const tgtFrame = toOptString(hyperlinkObj['@_w:tgtFrame']);
|
|
4878
|
+
// w:history is CT_OnOff per ECMA-376 §17.16.22 — honour every
|
|
4879
|
+
// ST_OnOff literal ("1"/"0"/"true"/"false"/"on"/"off") and every
|
|
4880
|
+
// XMLParser-coerced form (number 0/1, boolean). The Hyperlink
|
|
4881
|
+
// serializer accepts a string, so normalise to the canonical
|
|
4882
|
+
// "1"/"0" form. Without this, `w:history="0"` or `w:history="false"`
|
|
4883
|
+
// coerced to falsy values and the emitter's truthy check dropped
|
|
4884
|
+
// the attribute on round-trip.
|
|
4885
|
+
const rawHistory = hyperlinkObj['@_w:history'];
|
|
4886
|
+
const history =
|
|
4887
|
+
rawHistory === undefined ? undefined : parseOnOffAttribute(rawHistory) ? '1' : '0';
|
|
4888
|
+
const docLocation = toOptString(hyperlinkObj['@_w:docLocation']);
|
|
4521
4889
|
|
|
4522
4890
|
// Parse runs inside the hyperlink
|
|
4523
4891
|
const runs = hyperlinkObj['w:r'];
|
|
@@ -4815,8 +5183,20 @@ export class DocumentParser {
|
|
|
4815
5183
|
}
|
|
4816
5184
|
|
|
4817
5185
|
// Extract field type from instruction (first word)
|
|
4818
|
-
const typeMatch = instruction
|
|
4819
|
-
|
|
5186
|
+
const typeMatch = String(instruction)
|
|
5187
|
+
.trim()
|
|
5188
|
+
.match(/^(\w+)/);
|
|
5189
|
+
const type = (typeMatch?.[1] || 'PAGE') as import('../elements/Field.js').FieldType;
|
|
5190
|
+
|
|
5191
|
+
// CT_SimpleField (§17.16.16) carries two ST_OnOff attributes besides
|
|
5192
|
+
// the required w:instr — w:fldLock (update lock) and w:dirty
|
|
5193
|
+
// (cached-result staleness). Previously neither was parsed, so
|
|
5194
|
+
// Word's "update field" indicator and "lock field" flag were
|
|
5195
|
+
// silently cleared on every load → save round-trip.
|
|
5196
|
+
const fldLockRaw = fieldObj['@_w:fldLock'];
|
|
5197
|
+
const dirtyRaw = fieldObj['@_w:dirty'];
|
|
5198
|
+
const fldLock = fldLockRaw !== undefined ? parseOnOffAttribute(fldLockRaw) : undefined;
|
|
5199
|
+
const dirty = dirtyRaw !== undefined ? parseOnOffAttribute(dirtyRaw) : undefined;
|
|
4820
5200
|
|
|
4821
5201
|
// Parse run formatting from w:rPr if present
|
|
4822
5202
|
let formatting: RunFormatting | undefined;
|
|
@@ -4829,8 +5209,10 @@ export class DocumentParser {
|
|
|
4829
5209
|
// Create field with instruction
|
|
4830
5210
|
const field = Field.create({
|
|
4831
5211
|
type,
|
|
4832
|
-
instruction,
|
|
5212
|
+
instruction: String(instruction),
|
|
4833
5213
|
formatting,
|
|
5214
|
+
fldLock,
|
|
5215
|
+
dirty,
|
|
4834
5216
|
});
|
|
4835
5217
|
|
|
4836
5218
|
return field;
|
|
@@ -4848,15 +5230,25 @@ export class DocumentParser {
|
|
|
4848
5230
|
private parseRunPropertiesFromObject(rPrObj: any, run: Run): void {
|
|
4849
5231
|
if (!rPrObj) return;
|
|
4850
5232
|
|
|
4851
|
-
// Parse character style reference (w:rStyle) per ECMA-376 Part 1
|
|
5233
|
+
// Parse character style reference (w:rStyle) per ECMA-376 Part 1
|
|
5234
|
+
// §17.3.2.36 — `w:val` is ST_String referencing a style ID. Cast
|
|
5235
|
+
// via String(...) so a purely-numeric style ID (e.g., "1") that
|
|
5236
|
+
// XMLParser coerces to the number 1 survives as the string "1",
|
|
5237
|
+
// matching the `characterStyle?: string` field contract on
|
|
5238
|
+
// RunFormatting.
|
|
4852
5239
|
if (rPrObj['w:rStyle']) {
|
|
4853
5240
|
const styleId = rPrObj['w:rStyle']['@_w:val'];
|
|
4854
|
-
if (styleId) {
|
|
4855
|
-
run.setCharacterStyle(styleId);
|
|
5241
|
+
if (styleId !== undefined && styleId !== null && styleId !== '') {
|
|
5242
|
+
run.setCharacterStyle(String(styleId));
|
|
4856
5243
|
}
|
|
4857
5244
|
}
|
|
4858
5245
|
|
|
4859
|
-
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5
|
|
5246
|
+
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5 — CT_Border
|
|
5247
|
+
// §17.18.2 attribute set: val / sz / space / color / themeColor /
|
|
5248
|
+
// themeTint / themeShade / shadow / frame. Previously only the first
|
|
5249
|
+
// four were read, so themed character borders lost their theme linkage
|
|
5250
|
+
// on round-trip. The emitter (Run.generateRunPropertiesXML) handles
|
|
5251
|
+
// all nine since iteration 79.
|
|
4860
5252
|
if (rPrObj['w:bdr']) {
|
|
4861
5253
|
const bdr = rPrObj['w:bdr'];
|
|
4862
5254
|
const border: any = {};
|
|
@@ -4864,6 +5256,20 @@ export class DocumentParser {
|
|
|
4864
5256
|
if (bdr['@_w:sz']) border.size = parseInt(bdr['@_w:sz'], 10);
|
|
4865
5257
|
if (bdr['@_w:color']) border.color = bdr['@_w:color'];
|
|
4866
5258
|
if (bdr['@_w:space']) border.space = parseInt(bdr['@_w:space'], 10);
|
|
5259
|
+
// Per ECMA-376 §17.18.82 CT_Border: themeTint / themeShade are
|
|
5260
|
+
// ST_UcharHexNumber (2-char hex). XMLParser coerces purely-digit
|
|
5261
|
+
// hex strings like "80" / "50" to JS numbers; cast via String(...)
|
|
5262
|
+
// so the declared `string` contract on the model holds for any
|
|
5263
|
+
// downstream code that calls string methods (.toUpperCase(), etc.).
|
|
5264
|
+
if (bdr['@_w:themeColor']) border.themeColor = String(bdr['@_w:themeColor']);
|
|
5265
|
+
if (bdr['@_w:themeTint']) border.themeTint = String(bdr['@_w:themeTint']);
|
|
5266
|
+
if (bdr['@_w:themeShade']) border.themeShade = String(bdr['@_w:themeShade']);
|
|
5267
|
+
if (bdr['@_w:shadow'] !== undefined) {
|
|
5268
|
+
border.shadow = parseOnOffAttribute(String(bdr['@_w:shadow']), true);
|
|
5269
|
+
}
|
|
5270
|
+
if (bdr['@_w:frame'] !== undefined) {
|
|
5271
|
+
border.frame = parseOnOffAttribute(String(bdr['@_w:frame']), true);
|
|
5272
|
+
}
|
|
4867
5273
|
if (Object.keys(border).length > 0) {
|
|
4868
5274
|
run.setBorder(border);
|
|
4869
5275
|
}
|
|
@@ -4883,26 +5289,30 @@ export class DocumentParser {
|
|
|
4883
5289
|
if (val) run.setEmphasis(val);
|
|
4884
5290
|
}
|
|
4885
5291
|
|
|
4886
|
-
//
|
|
4887
|
-
//
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
if (
|
|
4892
|
-
if (
|
|
5292
|
+
// CT_OnOff text effects — presence + w:val both matter. Use `!== undefined`
|
|
5293
|
+
// to detect presence, then parseOoxmlBoolean() for the value, so an explicit
|
|
5294
|
+
// `<w:outline w:val="0"/>` override of a style-inherited true is preserved
|
|
5295
|
+
// (not silently dropped into "inherit"). Applies to all OnOffType rPr flags
|
|
5296
|
+
// per ECMA-376 §17.3.2.
|
|
5297
|
+
if (rPrObj['w:outline'] !== undefined) run.setOutline(parseOoxmlBoolean(rPrObj['w:outline']));
|
|
5298
|
+
if (rPrObj['w:shadow'] !== undefined) run.setShadow(parseOoxmlBoolean(rPrObj['w:shadow']));
|
|
5299
|
+
if (rPrObj['w:emboss'] !== undefined) run.setEmboss(parseOoxmlBoolean(rPrObj['w:emboss']));
|
|
5300
|
+
if (rPrObj['w:imprint'] !== undefined) run.setImprint(parseOoxmlBoolean(rPrObj['w:imprint']));
|
|
5301
|
+
if (rPrObj['w:noProof'] !== undefined) run.setNoProof(parseOoxmlBoolean(rPrObj['w:noProof']));
|
|
4893
5302
|
// snapToGrid: default when absent is true (§17.3.2.34), so explicit val="0" must be preserved
|
|
4894
5303
|
if (rPrObj['w:snapToGrid'] !== undefined) {
|
|
4895
5304
|
run.setSnapToGrid(parseOoxmlBoolean(rPrObj['w:snapToGrid']));
|
|
4896
5305
|
}
|
|
4897
|
-
if (
|
|
4898
|
-
if (
|
|
5306
|
+
if (rPrObj['w:vanish'] !== undefined) run.setVanish(parseOoxmlBoolean(rPrObj['w:vanish']));
|
|
5307
|
+
if (rPrObj['w:specVanish'] !== undefined)
|
|
5308
|
+
run.setSpecVanish(parseOoxmlBoolean(rPrObj['w:specVanish']));
|
|
4899
5309
|
|
|
4900
5310
|
// Boolean properties - use parseOoxmlBoolean helper
|
|
4901
5311
|
// Per ECMA-376: <w:b/> or <w:b w:val="1"/> or <w:b w:val="true"/> means true
|
|
4902
5312
|
// <w:b w:val="0"/> or <w:b w:val="false"/> means false (omit from document)
|
|
4903
5313
|
|
|
4904
5314
|
// Parse RTL text (w:rtl) per ECMA-376 Part 1 §17.3.2.30
|
|
4905
|
-
if (
|
|
5315
|
+
if (rPrObj['w:rtl'] !== undefined) run.setRTL(parseOoxmlBoolean(rPrObj['w:rtl']));
|
|
4906
5316
|
|
|
4907
5317
|
// b, bCs, i, iCs: preserve explicit val="0" to override style-inherited formatting
|
|
4908
5318
|
if (rPrObj['w:b'] !== undefined) run.setBold(parseOoxmlBoolean(rPrObj['w:b']));
|
|
@@ -4919,11 +5329,12 @@ export class DocumentParser {
|
|
|
4919
5329
|
run.setSmallCaps(parseOoxmlBoolean(rPrObj['w:smallCaps']));
|
|
4920
5330
|
if (rPrObj['w:caps'] !== undefined) run.setAllCaps(parseOoxmlBoolean(rPrObj['w:caps']));
|
|
4921
5331
|
|
|
4922
|
-
// Parse complex script flag (w:cs) per ECMA-376 Part 1 §17.3.2.7
|
|
4923
|
-
if (
|
|
5332
|
+
// Parse complex script flag (w:cs) per ECMA-376 Part 1 §17.3.2.7 — CT_OnOff
|
|
5333
|
+
if (rPrObj['w:cs'] !== undefined) run.setComplexScript(parseOoxmlBoolean(rPrObj['w:cs']));
|
|
4924
5334
|
|
|
4925
|
-
// Parse web hidden (w:webHidden) per ECMA-376 Part 1 §17.3.2.44
|
|
4926
|
-
if (
|
|
5335
|
+
// Parse web hidden (w:webHidden) per ECMA-376 Part 1 §17.3.2.44 — CT_OnOff
|
|
5336
|
+
if (rPrObj['w:webHidden'] !== undefined)
|
|
5337
|
+
run.setWebHidden(parseOoxmlBoolean(rPrObj['w:webHidden']));
|
|
4927
5338
|
|
|
4928
5339
|
if (rPrObj['w:u']) {
|
|
4929
5340
|
// XMLParser adds @_ prefix to attributes
|
|
@@ -4943,28 +5354,37 @@ export class DocumentParser {
|
|
|
4943
5354
|
);
|
|
4944
5355
|
}
|
|
4945
5356
|
|
|
4946
|
-
// Parse character spacing (w:spacing) per ECMA-376 Part 1 §17.3.2.
|
|
5357
|
+
// Parse character spacing (w:spacing) per ECMA-376 Part 1 §17.3.2.35.
|
|
5358
|
+
// ST_SignedTwipsMeasure — 0 and negative values are valid (default /
|
|
5359
|
+
// tighter spacing). XMLParser.parseAttributeValue coerces "0" to number 0,
|
|
5360
|
+
// which is falsy — so the previous `if (val)` truthy check silently dropped
|
|
5361
|
+
// explicit zero / baseline-reset formatting on every run that used it.
|
|
5362
|
+
// Matches the rPrChange parser below which already uses `!== undefined`.
|
|
4947
5363
|
if (rPrObj['w:spacing']) {
|
|
4948
5364
|
const val = rPrObj['w:spacing']['@_w:val'];
|
|
4949
|
-
if (val) run.setCharacterSpacing(parseInt(val, 10));
|
|
5365
|
+
if (val !== undefined) run.setCharacterSpacing(parseInt(String(val), 10));
|
|
4950
5366
|
}
|
|
4951
5367
|
|
|
4952
|
-
// Parse horizontal scaling (w:w) per ECMA-376 Part 1 §17.3.2.43
|
|
5368
|
+
// Parse horizontal scaling (w:w) per ECMA-376 Part 1 §17.3.2.43.
|
|
5369
|
+
// ST_TextScale — min 1 per schema, so value 0 is not spec-valid; keep
|
|
5370
|
+
// truthy check as a mild sanity guard against malformed sources.
|
|
4953
5371
|
if (rPrObj['w:w']) {
|
|
4954
5372
|
const val = rPrObj['w:w']['@_w:val'];
|
|
4955
|
-
if (val) run.setScaling(parseInt(val, 10));
|
|
5373
|
+
if (val) run.setScaling(parseInt(String(val), 10));
|
|
4956
5374
|
}
|
|
4957
5375
|
|
|
4958
|
-
// Parse vertical position (w:position) per ECMA-376 Part 1 §17.3.2.31
|
|
5376
|
+
// Parse vertical position (w:position) per ECMA-376 Part 1 §17.3.2.31.
|
|
5377
|
+
// ST_SignedHpsMeasure — 0 = baseline (default / explicit reset).
|
|
4959
5378
|
if (rPrObj['w:position']) {
|
|
4960
5379
|
const val = rPrObj['w:position']['@_w:val'];
|
|
4961
|
-
if (val) run.setPosition(parseInt(val, 10));
|
|
5380
|
+
if (val !== undefined) run.setPosition(parseInt(String(val), 10));
|
|
4962
5381
|
}
|
|
4963
5382
|
|
|
4964
|
-
// Parse kerning (w:kern) per ECMA-376 Part 1 §17.3.2.20
|
|
5383
|
+
// Parse kerning (w:kern) per ECMA-376 Part 1 §17.3.2.20.
|
|
5384
|
+
// ST_HpsMeasure — 0 means "kern at every size" (no minimum threshold).
|
|
4965
5385
|
if (rPrObj['w:kern']) {
|
|
4966
5386
|
const val = rPrObj['w:kern']['@_w:val'];
|
|
4967
|
-
if (val) run.setKerning(parseInt(val, 10));
|
|
5387
|
+
if (val !== undefined) run.setKerning(parseInt(String(val), 10));
|
|
4968
5388
|
}
|
|
4969
5389
|
|
|
4970
5390
|
// Parse language (w:lang) per ECMA-376 Part 1 §17.3.2.20 (CT_Language)
|
|
@@ -4984,14 +5404,27 @@ export class DocumentParser {
|
|
|
4984
5404
|
}
|
|
4985
5405
|
}
|
|
4986
5406
|
|
|
4987
|
-
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5407
|
+
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5408
|
+
// §17.3.2.10 CT_EastAsianLayout. `w:vert` / `w:vertCompress` /
|
|
5409
|
+
// `w:combine` are ST_OnOff attributes — route through
|
|
5410
|
+
// parseOnOffAttribute so every literal ("1"/"0"/"true"/"false"/
|
|
5411
|
+
// "on"/"off") resolves correctly. The previous truthy gate both
|
|
5412
|
+
// dropped explicit false (`w:vert="0"` → coerced 0 → undefined) AND
|
|
5413
|
+
// wrongly marked `w:vert="off"` as true (non-empty string is truthy
|
|
5414
|
+
// without parsing).
|
|
4988
5415
|
if (rPrObj['w:eastAsianLayout']) {
|
|
4989
5416
|
const layoutObj = rPrObj['w:eastAsianLayout'];
|
|
4990
5417
|
const layout: any = {};
|
|
4991
5418
|
if (layoutObj['@_w:id'] !== undefined) layout.id = Number(layoutObj['@_w:id']);
|
|
4992
|
-
if (layoutObj['@_w:vert']
|
|
4993
|
-
|
|
4994
|
-
|
|
5419
|
+
if (layoutObj['@_w:vert'] !== undefined) {
|
|
5420
|
+
layout.vert = parseOnOffAttribute(String(layoutObj['@_w:vert']), true);
|
|
5421
|
+
}
|
|
5422
|
+
if (layoutObj['@_w:vertCompress'] !== undefined) {
|
|
5423
|
+
layout.vertCompress = parseOnOffAttribute(String(layoutObj['@_w:vertCompress']), true);
|
|
5424
|
+
}
|
|
5425
|
+
if (layoutObj['@_w:combine'] !== undefined) {
|
|
5426
|
+
layout.combine = parseOnOffAttribute(String(layoutObj['@_w:combine']), true);
|
|
5427
|
+
}
|
|
4995
5428
|
if (layoutObj['@_w:combineBrackets'])
|
|
4996
5429
|
layout.combineBrackets = layoutObj['@_w:combineBrackets'];
|
|
4997
5430
|
|
|
@@ -5021,17 +5454,23 @@ export class DocumentParser {
|
|
|
5021
5454
|
|
|
5022
5455
|
if (rPrObj['w:rFonts']) {
|
|
5023
5456
|
const rFonts = rPrObj['w:rFonts'];
|
|
5024
|
-
|
|
5457
|
+
// Per ECMA-376 §17.3.2.26 CT_Fonts, all four literal-font
|
|
5458
|
+
// attributes (ascii/hAnsi/eastAsia/cs) are ST_String. XMLParser
|
|
5459
|
+
// coerces purely-numeric font names ("2010", etc.) to JS
|
|
5460
|
+
// numbers; cast through String() so RunFormatting's
|
|
5461
|
+
// declared-string font fields keep their type contract.
|
|
5462
|
+
if (rFonts['@_w:ascii'] !== undefined) run.setFont(String(rFonts['@_w:ascii']));
|
|
5025
5463
|
// 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']);
|
|
5464
|
+
if (rFonts['@_w:hAnsi'] !== undefined) run.setFontHAnsi(String(rFonts['@_w:hAnsi']));
|
|
5465
|
+
if (rFonts['@_w:eastAsia'] !== undefined) run.setFontEastAsia(String(rFonts['@_w:eastAsia']));
|
|
5466
|
+
if (rFonts['@_w:cs'] !== undefined) run.setFontCs(String(rFonts['@_w:cs']));
|
|
5467
|
+
if (rFonts['@_w:hint']) run.setFontHint(String(rFonts['@_w:hint']));
|
|
5030
5468
|
// 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
|
-
|
|
5469
|
+
if (rFonts['@_w:asciiTheme']) run.setFontAsciiTheme(String(rFonts['@_w:asciiTheme']));
|
|
5470
|
+
if (rFonts['@_w:hAnsiTheme']) run.setFontHAnsiTheme(String(rFonts['@_w:hAnsiTheme']));
|
|
5471
|
+
if (rFonts['@_w:eastAsiaTheme'])
|
|
5472
|
+
run.setFontEastAsiaTheme(String(rFonts['@_w:eastAsiaTheme']));
|
|
5473
|
+
if (rFonts['@_w:cstheme']) run.setFontCsTheme(String(rFonts['@_w:cstheme']));
|
|
5035
5474
|
}
|
|
5036
5475
|
|
|
5037
5476
|
if (rPrObj['w:sz']) {
|
|
@@ -5099,7 +5538,7 @@ export class DocumentParser {
|
|
|
5099
5538
|
// This records what the run formatting was BEFORE a change was made
|
|
5100
5539
|
if (rPrObj['w:rPrChange']) {
|
|
5101
5540
|
const changeObj = rPrObj['w:rPrChange'];
|
|
5102
|
-
const propChange: import('../elements/PropertyChangeTypes').RunPropertyChange = {
|
|
5541
|
+
const propChange: import('../elements/PropertyChangeTypes.js').RunPropertyChange = {
|
|
5103
5542
|
id: changeObj['@_w:id'] !== undefined ? parseInt(String(changeObj['@_w:id']), 10) : 0,
|
|
5104
5543
|
author: changeObj['@_w:author'] ? String(changeObj['@_w:author']) : '',
|
|
5105
5544
|
date: changeObj['@_w:date'] ? new Date(String(changeObj['@_w:date'])) : new Date(),
|
|
@@ -5109,7 +5548,7 @@ export class DocumentParser {
|
|
|
5109
5548
|
// Parse previous run properties from child w:rPr element
|
|
5110
5549
|
if (changeObj['w:rPr']) {
|
|
5111
5550
|
const prevRPr = changeObj['w:rPr'];
|
|
5112
|
-
const prevProps: Partial<import('../elements/Run').RunFormatting> = {};
|
|
5551
|
+
const prevProps: Partial<import('../elements/Run.js').RunFormatting> = {};
|
|
5113
5552
|
|
|
5114
5553
|
// Parse previous bold
|
|
5115
5554
|
if (prevRPr['w:b']) {
|
|
@@ -5121,10 +5560,22 @@ export class DocumentParser {
|
|
|
5121
5560
|
prevProps.italic = parseOoxmlBoolean(prevRPr['w:i']);
|
|
5122
5561
|
}
|
|
5123
5562
|
|
|
5124
|
-
// Parse previous underline
|
|
5563
|
+
// Parse previous underline — CT_Underline per §17.3.2.40 has `val`
|
|
5564
|
+
// plus color / themeColor / themeTint / themeShade. Main rPr parser
|
|
5565
|
+
// reads all of them; rPrChange previously only read `val`, so
|
|
5566
|
+
// underline color metadata on tracked "previous" state was dropped.
|
|
5125
5567
|
if (prevRPr['w:u']) {
|
|
5126
|
-
const
|
|
5568
|
+
const uObj = prevRPr['w:u'];
|
|
5569
|
+
const uVal = uObj['@_w:val'];
|
|
5127
5570
|
prevProps.underline = uVal || true;
|
|
5571
|
+
if (uObj['@_w:color']) prevProps.underlineColor = uObj['@_w:color'];
|
|
5572
|
+
if (uObj['@_w:themeColor']) prevProps.underlineThemeColor = uObj['@_w:themeColor'];
|
|
5573
|
+
if (uObj['@_w:themeTint'] !== undefined) {
|
|
5574
|
+
prevProps.underlineThemeTint = parseInt(String(uObj['@_w:themeTint']), 16);
|
|
5575
|
+
}
|
|
5576
|
+
if (uObj['@_w:themeShade'] !== undefined) {
|
|
5577
|
+
prevProps.underlineThemeShade = parseInt(String(uObj['@_w:themeShade']), 16);
|
|
5578
|
+
}
|
|
5128
5579
|
}
|
|
5129
5580
|
|
|
5130
5581
|
// Parse previous strikethrough
|
|
@@ -5133,13 +5584,28 @@ export class DocumentParser {
|
|
|
5133
5584
|
}
|
|
5134
5585
|
|
|
5135
5586
|
// Parse previous font (all w:rFonts attributes per ECMA-376 Part 1 §17.3.2.26)
|
|
5587
|
+
// including theme font references (asciiTheme/hAnsiTheme/eastAsiaTheme/
|
|
5588
|
+
// cstheme). Previously only the literal-font attributes were read, so
|
|
5589
|
+
// rPrChange tracked history of theme-font changes lost the theme linkage
|
|
5590
|
+
// on round-trip — a paragraph whose "previous" font was a theme
|
|
5591
|
+
// reference (e.g. w:asciiTheme="minorHAnsi") silently dropped it.
|
|
5136
5592
|
if (prevRPr['w:rFonts']) {
|
|
5137
5593
|
const rFonts = prevRPr['w:rFonts'];
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
if (rFonts['@_w:
|
|
5594
|
+
// Mirror the main-path String() casts on rPrChange
|
|
5595
|
+
// previous-font reads — ECMA-376 §17.3.2.26 CT_Fonts declares
|
|
5596
|
+
// ascii/hAnsi/eastAsia/cs as ST_String, so purely-numeric
|
|
5597
|
+
// font names must survive round-trip as strings here too.
|
|
5598
|
+
if (rFonts['@_w:ascii'] !== undefined) prevProps.font = String(rFonts['@_w:ascii']);
|
|
5599
|
+
if (rFonts['@_w:hAnsi'] !== undefined) prevProps.fontHAnsi = String(rFonts['@_w:hAnsi']);
|
|
5600
|
+
if (rFonts['@_w:eastAsia'] !== undefined)
|
|
5601
|
+
prevProps.fontEastAsia = String(rFonts['@_w:eastAsia']);
|
|
5602
|
+
if (rFonts['@_w:cs'] !== undefined) prevProps.fontCs = String(rFonts['@_w:cs']);
|
|
5603
|
+
if (rFonts['@_w:hint']) prevProps.fontHint = String(rFonts['@_w:hint']);
|
|
5604
|
+
if (rFonts['@_w:asciiTheme']) prevProps.fontAsciiTheme = String(rFonts['@_w:asciiTheme']);
|
|
5605
|
+
if (rFonts['@_w:hAnsiTheme']) prevProps.fontHAnsiTheme = String(rFonts['@_w:hAnsiTheme']);
|
|
5606
|
+
if (rFonts['@_w:eastAsiaTheme'])
|
|
5607
|
+
prevProps.fontEastAsiaTheme = String(rFonts['@_w:eastAsiaTheme']);
|
|
5608
|
+
if (rFonts['@_w:cstheme']) prevProps.fontCsTheme = String(rFonts['@_w:cstheme']);
|
|
5143
5609
|
}
|
|
5144
5610
|
|
|
5145
5611
|
// Parse previous size (half-points to points)
|
|
@@ -5337,17 +5803,33 @@ export class DocumentParser {
|
|
|
5337
5803
|
}
|
|
5338
5804
|
}
|
|
5339
5805
|
|
|
5340
|
-
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.
|
|
5341
|
-
//
|
|
5806
|
+
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5 — full
|
|
5807
|
+
// CT_Border attribute set for rPrChange previous-properties fidelity.
|
|
5342
5808
|
if (prevRPr['w:bdr']) {
|
|
5343
5809
|
const bdrObj = prevRPr['w:bdr'];
|
|
5344
|
-
|
|
5345
|
-
style: bdrObj['@_w:val'] as import('../elements/Run').TextBorderStyle,
|
|
5810
|
+
const tb: import('../elements/Run.js').TextBorder = {
|
|
5811
|
+
style: bdrObj['@_w:val'] as import('../elements/Run.js').TextBorderStyle,
|
|
5346
5812
|
size: bdrObj['@_w:sz'] !== undefined ? safeParseInt(bdrObj['@_w:sz']) : undefined,
|
|
5347
5813
|
space:
|
|
5348
5814
|
bdrObj['@_w:space'] !== undefined ? safeParseInt(bdrObj['@_w:space']) : undefined,
|
|
5349
5815
|
color: bdrObj['@_w:color'],
|
|
5350
5816
|
};
|
|
5817
|
+
// String(...) cast: XMLParser coerces "80"/"50" hex to numbers
|
|
5818
|
+
// — preserve the declared string contract on the model.
|
|
5819
|
+
if (bdrObj['@_w:themeColor']) {
|
|
5820
|
+
tb.themeColor = String(
|
|
5821
|
+
bdrObj['@_w:themeColor']
|
|
5822
|
+
) as import('../elements/Run.js').ThemeColorValue;
|
|
5823
|
+
}
|
|
5824
|
+
if (bdrObj['@_w:themeTint']) tb.themeTint = String(bdrObj['@_w:themeTint']);
|
|
5825
|
+
if (bdrObj['@_w:themeShade']) tb.themeShade = String(bdrObj['@_w:themeShade']);
|
|
5826
|
+
if (bdrObj['@_w:shadow'] !== undefined) {
|
|
5827
|
+
tb.shadow = parseOnOffAttribute(String(bdrObj['@_w:shadow']), true);
|
|
5828
|
+
}
|
|
5829
|
+
if (bdrObj['@_w:frame'] !== undefined) {
|
|
5830
|
+
tb.frame = parseOnOffAttribute(String(bdrObj['@_w:frame']), true);
|
|
5831
|
+
}
|
|
5832
|
+
prevProps.border = tb;
|
|
5351
5833
|
}
|
|
5352
5834
|
|
|
5353
5835
|
// Parse character shading (w:shd) per ECMA-376 Part 1 §17.3.2.32
|
|
@@ -5358,24 +5840,54 @@ export class DocumentParser {
|
|
|
5358
5840
|
}
|
|
5359
5841
|
}
|
|
5360
5842
|
|
|
5361
|
-
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5843
|
+
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5844
|
+
// §17.3.2.10 CT_EastAsianLayout. Parity-fix with the main rPr
|
|
5845
|
+
// parser: route the three ST_OnOff attributes through
|
|
5846
|
+
// parseOnOffAttribute so every literal — including "0"
|
|
5847
|
+
// (explicit-false override) and "off" — resolves correctly. The
|
|
5848
|
+
// previous truthy gate both dropped explicit-false (XMLParser
|
|
5849
|
+
// coerces "0" to number 0 → falsy → undefined) and wrongly
|
|
5850
|
+
// coerced "off" to true.
|
|
5362
5851
|
if (prevRPr['w:eastAsianLayout']) {
|
|
5363
5852
|
const eaObj = prevRPr['w:eastAsianLayout'];
|
|
5364
5853
|
prevProps.eastAsianLayout = {
|
|
5365
5854
|
id: eaObj['@_w:id'] !== undefined ? safeParseInt(eaObj['@_w:id']) : undefined,
|
|
5366
|
-
combine:
|
|
5367
|
-
|
|
5368
|
-
|
|
5855
|
+
combine:
|
|
5856
|
+
eaObj['@_w:combine'] !== undefined
|
|
5857
|
+
? parseOnOffAttribute(String(eaObj['@_w:combine']), true)
|
|
5858
|
+
: undefined,
|
|
5369
5859
|
combineBrackets: eaObj['@_w:combineBrackets'],
|
|
5370
|
-
vert:
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
: undefined
|
|
5860
|
+
vert:
|
|
5861
|
+
eaObj['@_w:vert'] !== undefined
|
|
5862
|
+
? parseOnOffAttribute(String(eaObj['@_w:vert']), true)
|
|
5863
|
+
: undefined,
|
|
5864
|
+
vertCompress:
|
|
5865
|
+
eaObj['@_w:vertCompress'] !== undefined
|
|
5866
|
+
? parseOnOffAttribute(String(eaObj['@_w:vertCompress']), true)
|
|
5867
|
+
: undefined,
|
|
5376
5868
|
};
|
|
5377
5869
|
}
|
|
5378
5870
|
|
|
5871
|
+
// Collect w14: namespace elements from the previous rPr for
|
|
5872
|
+
// passthrough (Word 2010+ text effects: w14:textOutline,
|
|
5873
|
+
// w14:shadow, w14:reflection, w14:glow, w14:ligatures,
|
|
5874
|
+
// w14:numForm, w14:numSpacing, w14:cntxtAlts, w14:stylisticSets).
|
|
5875
|
+
// The main rPr parser already collects these and the rPrChange
|
|
5876
|
+
// emitter (via generateRunPropertiesXML line 3130) re-emits
|
|
5877
|
+
// prevProps.rawW14Properties, but the rPrChange parser never
|
|
5878
|
+
// captured them — so tracked changes to any w14 text effect
|
|
5879
|
+
// silently lost the previous state on load → save.
|
|
5880
|
+
const prevRawW14: string[] = [];
|
|
5881
|
+
for (const key of Object.keys(prevRPr)) {
|
|
5882
|
+
if (key.startsWith('w14:')) {
|
|
5883
|
+
const rawXml = this.objectToXml({ [key]: prevRPr[key] });
|
|
5884
|
+
if (rawXml) prevRawW14.push(rawXml);
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
if (prevRawW14.length > 0) {
|
|
5888
|
+
(prevProps as { rawW14Properties?: string[] }).rawW14Properties = prevRawW14;
|
|
5889
|
+
}
|
|
5890
|
+
|
|
5379
5891
|
propChange.previousProperties = prevProps;
|
|
5380
5892
|
}
|
|
5381
5893
|
|
|
@@ -5446,8 +5958,15 @@ export class DocumentParser {
|
|
|
5446
5958
|
let docPrId = 1;
|
|
5447
5959
|
let hidden = false;
|
|
5448
5960
|
if (docPrObj) {
|
|
5449
|
-
name
|
|
5450
|
-
|
|
5961
|
+
// wp:docPr @name and @descr are xsd:string per ECMA-376
|
|
5962
|
+
// §20.4.2.5 CT_NonVisualDrawingProps. XMLParser coerces
|
|
5963
|
+
// purely-numeric values ("2010") to JS numbers; cast through
|
|
5964
|
+
// String() so Image.name / Image.description keep the declared
|
|
5965
|
+
// string contract (matches the @_title handling below).
|
|
5966
|
+
const rawName = docPrObj['@_name'];
|
|
5967
|
+
name = rawName !== undefined && rawName !== null ? String(rawName) : 'image';
|
|
5968
|
+
const rawDescr = docPrObj['@_descr'];
|
|
5969
|
+
description = rawDescr !== undefined && rawDescr !== null ? String(rawDescr) : 'Image';
|
|
5451
5970
|
if (docPrObj['@_title']) {
|
|
5452
5971
|
title = String(docPrObj['@_title']);
|
|
5453
5972
|
}
|
|
@@ -5776,7 +6295,7 @@ export class DocumentParser {
|
|
|
5776
6295
|
);
|
|
5777
6296
|
|
|
5778
6297
|
// Create image from buffer with all properties
|
|
5779
|
-
const { Image: ImageClass } = await import('../elements/Image');
|
|
6298
|
+
const { Image: ImageClass } = await import('../elements/Image.js');
|
|
5780
6299
|
const image = await ImageClass.create({
|
|
5781
6300
|
source: imageData,
|
|
5782
6301
|
width,
|
|
@@ -6137,12 +6656,36 @@ export class DocumentParser {
|
|
|
6137
6656
|
*/
|
|
6138
6657
|
private parseBorderElement(borderObj: any): TableBorder | undefined {
|
|
6139
6658
|
if (!borderObj) return undefined;
|
|
6659
|
+
// Extract the full CT_Border attribute set per ECMA-376 §17.18.2:
|
|
6660
|
+
// val (required) / sz / space / color / themeColor / themeTint /
|
|
6661
|
+
// themeShade / shadow / frame. Previously the last five were silently
|
|
6662
|
+
// dropped on load, so themed borders and shadow/frame flags were lost
|
|
6663
|
+
// on every round-trip.
|
|
6140
6664
|
const border: TableBorder = {
|
|
6141
6665
|
style: (borderObj['@_w:val'] || 'single') as TableBorder['style'],
|
|
6142
6666
|
};
|
|
6143
6667
|
if (borderObj['@_w:sz'] !== undefined) border.size = safeParseInt(borderObj['@_w:sz']);
|
|
6144
6668
|
if (borderObj['@_w:space'] !== undefined) border.space = safeParseInt(borderObj['@_w:space']);
|
|
6145
|
-
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
6669
|
+
if (borderObj['@_w:color']) border.color = String(borderObj['@_w:color']);
|
|
6670
|
+
// String(...) cast: themeTint / themeShade are ST_UcharHexNumber
|
|
6671
|
+
// (2-char hex) declared as `string` on the model. XMLParser coerces
|
|
6672
|
+
// purely-digit hex like "80"/"50" to numbers — cast to preserve
|
|
6673
|
+
// the type contract.
|
|
6674
|
+
if (borderObj['@_w:themeColor']) {
|
|
6675
|
+
(border as any).themeColor = String(borderObj['@_w:themeColor']);
|
|
6676
|
+
}
|
|
6677
|
+
if (borderObj['@_w:themeTint']) {
|
|
6678
|
+
(border as any).themeTint = String(borderObj['@_w:themeTint']);
|
|
6679
|
+
}
|
|
6680
|
+
if (borderObj['@_w:themeShade']) {
|
|
6681
|
+
(border as any).themeShade = String(borderObj['@_w:themeShade']);
|
|
6682
|
+
}
|
|
6683
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
6684
|
+
(border as any).shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
6685
|
+
}
|
|
6686
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
6687
|
+
(border as any).frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
6688
|
+
}
|
|
6146
6689
|
return border;
|
|
6147
6690
|
}
|
|
6148
6691
|
|
|
@@ -6188,8 +6731,10 @@ export class DocumentParser {
|
|
|
6188
6731
|
const gridChange = TableGridChange.create(
|
|
6189
6732
|
safeParseInt(changeObj['@_w:id'], 0),
|
|
6190
6733
|
prevWidths,
|
|
6191
|
-
changeObj['@_w:author']
|
|
6192
|
-
changeObj['@_w:date']
|
|
6734
|
+
changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : undefined,
|
|
6735
|
+
changeObj['@_w:date'] !== undefined
|
|
6736
|
+
? new Date(String(changeObj['@_w:date']))
|
|
6737
|
+
: undefined
|
|
6193
6738
|
);
|
|
6194
6739
|
table.setTblGridChange(gridChange);
|
|
6195
6740
|
}
|
|
@@ -6284,53 +6829,90 @@ export class DocumentParser {
|
|
|
6284
6829
|
private parseTablePropertiesFromObject(tblPrObj: any, table: Table): void {
|
|
6285
6830
|
if (!tblPrObj) return;
|
|
6286
6831
|
|
|
6287
|
-
// Parse table style reference (w:tblStyle)
|
|
6832
|
+
// Parse table style reference (w:tblStyle) per ECMA-376 §17.7.4.62.
|
|
6833
|
+
// w:val is ST_String — cast through String() so purely-numeric
|
|
6834
|
+
// custom style IDs ("2025", "1", …) don't leak as JS numbers
|
|
6835
|
+
// through XMLParser's parseAttributeValue coercion into the
|
|
6836
|
+
// string-typed `formatting.style` field.
|
|
6288
6837
|
if (tblPrObj['w:tblStyle']) {
|
|
6289
6838
|
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
|
-
//
|
|
6839
|
+
if (styleId !== undefined && styleId !== null && styleId !== '') {
|
|
6840
|
+
table.setStyle(String(styleId));
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
|
|
6844
|
+
// Parse table look flags (w:tblLook) per ECMA-376 §17.4.57 — supports both
|
|
6845
|
+
// hex-string format (w:val="04A0") AND individual ST_OnOff attributes
|
|
6846
|
+
// (firstRow/lastRow/firstColumn/lastColumn/noHBand/noVBand).
|
|
6847
|
+
//
|
|
6848
|
+
// XMLParser.parseToObject runs with `parseAttributeValue: true` by default,
|
|
6849
|
+
// so `"1"` coerces to the number `1` and `"true"` to the boolean `true`.
|
|
6850
|
+
// The previous `=== '1'` strict-string comparison missed both coerced
|
|
6851
|
+
// forms, silently flipping every individually-set flag to OFF and
|
|
6852
|
+
// producing `tblLook="0000"` for every Word-authored document whose
|
|
6853
|
+
// tblLook used the expanded attribute syntax. Route each attribute
|
|
6854
|
+
// through `parseOoxmlBoolean` (attribute form) so string/number/boolean
|
|
6855
|
+
// representations all resolve correctly.
|
|
6297
6856
|
if (tblPrObj['w:tblLook']) {
|
|
6298
6857
|
const look = tblPrObj['w:tblLook'];
|
|
6299
6858
|
if (look['@_w:val']) {
|
|
6300
6859
|
// Hex string format
|
|
6301
6860
|
table.setTblLook(look['@_w:val']);
|
|
6302
6861
|
} else {
|
|
6303
|
-
// Individual attribute format
|
|
6304
|
-
//
|
|
6862
|
+
// Individual attribute format — construct hex value.
|
|
6863
|
+
// Bits per §17.4.57: firstRow=0x0020, lastRow=0x0040, firstCol=0x0080,
|
|
6864
|
+
// lastCol=0x0100, noHBand=0x0200, noVBand=0x0400.
|
|
6865
|
+
const attrIsOn = (name: string): boolean => {
|
|
6866
|
+
const v = look[name];
|
|
6867
|
+
if (v === undefined) return false;
|
|
6868
|
+
// parseOoxmlBoolean accepts the value wrapped as `{'@_w:val': v}` —
|
|
6869
|
+
// handles string "1"/"0"/"true"/"false"/"on"/"off", number 1/0,
|
|
6870
|
+
// and boolean true/false uniformly.
|
|
6871
|
+
return parseOoxmlBoolean({ '@_w:val': v });
|
|
6872
|
+
};
|
|
6305
6873
|
let value = 0;
|
|
6306
|
-
if (
|
|
6307
|
-
if (
|
|
6308
|
-
if (
|
|
6309
|
-
if (
|
|
6310
|
-
if (
|
|
6311
|
-
if (
|
|
6874
|
+
if (attrIsOn('@_w:firstRow')) value |= 0x0020;
|
|
6875
|
+
if (attrIsOn('@_w:lastRow')) value |= 0x0040;
|
|
6876
|
+
if (attrIsOn('@_w:firstColumn')) value |= 0x0080;
|
|
6877
|
+
if (attrIsOn('@_w:lastColumn')) value |= 0x0100;
|
|
6878
|
+
if (attrIsOn('@_w:noHBand')) value |= 0x0200;
|
|
6879
|
+
if (attrIsOn('@_w:noVBand')) value |= 0x0400;
|
|
6312
6880
|
table.setTblLook(value.toString(16).toUpperCase().padStart(4, '0'));
|
|
6313
6881
|
}
|
|
6314
6882
|
}
|
|
6315
6883
|
|
|
6316
|
-
// Parse table positioning (tblpPr) - for floating tables
|
|
6884
|
+
// Parse table positioning (tblpPr) - for floating tables.
|
|
6885
|
+
// Per ECMA-376 §17.4.52 CT_TblPPr, the six numeric attributes
|
|
6886
|
+
// (tblpX/tblpY/leftFromText/rightFromText/topFromText/bottomFromText)
|
|
6887
|
+
// are ST_SignedTwipsMeasure / ST_TwipsMeasure where 0 is a valid
|
|
6888
|
+
// value (e.g. float table anchored exactly at the anchor point).
|
|
6889
|
+
// XMLParser coerces "0" to the number 0 (falsy), so the previous
|
|
6890
|
+
// truthy gate silently dropped zero-offset positions. Table's
|
|
6891
|
+
// emitter uses `!== undefined`, so the asymmetry lost zeroes on
|
|
6892
|
+
// round-trip. Route each numeric read through isExplicitlySet +
|
|
6893
|
+
// safeParseInt.
|
|
6317
6894
|
if (tblPrObj['w:tblpPr']) {
|
|
6318
6895
|
const tblpPr = tblPrObj['w:tblpPr'];
|
|
6319
6896
|
const position: any = {};
|
|
6320
6897
|
|
|
6321
|
-
if (tblpPr['@_w:tblpX']) position.x =
|
|
6322
|
-
if (tblpPr['@_w:tblpY']) position.y =
|
|
6898
|
+
if (isExplicitlySet(tblpPr['@_w:tblpX'])) position.x = safeParseInt(tblpPr['@_w:tblpX']);
|
|
6899
|
+
if (isExplicitlySet(tblpPr['@_w:tblpY'])) position.y = safeParseInt(tblpPr['@_w:tblpY']);
|
|
6323
6900
|
if (tblpPr['@_w:horzAnchor']) position.horizontalAnchor = tblpPr['@_w:horzAnchor'];
|
|
6324
6901
|
if (tblpPr['@_w:vertAnchor']) position.verticalAnchor = tblpPr['@_w:vertAnchor'];
|
|
6325
6902
|
if (tblpPr['@_w:tblpXSpec']) position.horizontalAlignment = tblpPr['@_w:tblpXSpec'];
|
|
6326
6903
|
if (tblpPr['@_w:tblpYSpec']) position.verticalAlignment = tblpPr['@_w:tblpYSpec'];
|
|
6327
|
-
if (tblpPr['@_w:leftFromText'])
|
|
6328
|
-
position.leftFromText =
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6904
|
+
if (isExplicitlySet(tblpPr['@_w:leftFromText'])) {
|
|
6905
|
+
position.leftFromText = safeParseInt(tblpPr['@_w:leftFromText']);
|
|
6906
|
+
}
|
|
6907
|
+
if (isExplicitlySet(tblpPr['@_w:rightFromText'])) {
|
|
6908
|
+
position.rightFromText = safeParseInt(tblpPr['@_w:rightFromText']);
|
|
6909
|
+
}
|
|
6910
|
+
if (isExplicitlySet(tblpPr['@_w:topFromText'])) {
|
|
6911
|
+
position.topFromText = safeParseInt(tblpPr['@_w:topFromText']);
|
|
6912
|
+
}
|
|
6913
|
+
if (isExplicitlySet(tblpPr['@_w:bottomFromText'])) {
|
|
6914
|
+
position.bottomFromText = safeParseInt(tblpPr['@_w:bottomFromText']);
|
|
6915
|
+
}
|
|
6334
6916
|
|
|
6335
6917
|
if (Object.keys(position).length > 0) {
|
|
6336
6918
|
table.setPosition(position);
|
|
@@ -6343,9 +6925,9 @@ export class DocumentParser {
|
|
|
6343
6925
|
table.setOverlap(val === 'overlap');
|
|
6344
6926
|
}
|
|
6345
6927
|
|
|
6346
|
-
// Parse bidirectional visual layout
|
|
6928
|
+
// Parse bidirectional visual layout — CT_OnOff, honour w:val per ECMA-376 §17.17.4
|
|
6347
6929
|
if (tblPrObj['w:bidiVisual']) {
|
|
6348
|
-
table.setBidiVisual(
|
|
6930
|
+
table.setBidiVisual(parseOoxmlBoolean(tblPrObj['w:bidiVisual']));
|
|
6349
6931
|
}
|
|
6350
6932
|
|
|
6351
6933
|
// Parse table width — always set when w:tblW is present, including w:w="0" w:type="auto"
|
|
@@ -6358,24 +6940,37 @@ export class DocumentParser {
|
|
|
6358
6940
|
table.setWidthType(widthType);
|
|
6359
6941
|
}
|
|
6360
6942
|
|
|
6361
|
-
// Parse table caption
|
|
6943
|
+
// Parse table caption — ST_String per §17.4.62. Cast through
|
|
6944
|
+
// String() so a purely-numeric caption ("42") is preserved as a
|
|
6945
|
+
// string in `formatting.caption` rather than a JS number.
|
|
6362
6946
|
if (tblPrObj['w:tblCaption']) {
|
|
6363
6947
|
const caption = tblPrObj['w:tblCaption']['@_w:val'];
|
|
6364
|
-
if (caption
|
|
6948
|
+
if (caption !== undefined && caption !== null && caption !== '') {
|
|
6949
|
+
table.setCaption(String(caption));
|
|
6950
|
+
}
|
|
6365
6951
|
}
|
|
6366
6952
|
|
|
6367
|
-
// Parse table description
|
|
6953
|
+
// Parse table description — ST_String per §17.4.63.
|
|
6368
6954
|
if (tblPrObj['w:tblDescription']) {
|
|
6369
6955
|
const description = tblPrObj['w:tblDescription']['@_w:val'];
|
|
6370
|
-
if (description
|
|
6956
|
+
if (description !== undefined && description !== null && description !== '') {
|
|
6957
|
+
table.setDescription(String(description));
|
|
6958
|
+
}
|
|
6371
6959
|
}
|
|
6372
6960
|
|
|
6373
|
-
// Parse cell spacing
|
|
6961
|
+
// Parse table-level cell spacing (w:tblCellSpacing) per ECMA-376
|
|
6962
|
+
// §17.4.44 CT_TblCellSpacing. w:w is ST_MeasurementOrPercent; 0 is
|
|
6963
|
+
// a legal "explicit zero spacing" value (overrides any style-level
|
|
6964
|
+
// inherited tblCellSpacing). The emitter uses `!== undefined`, so
|
|
6965
|
+
// the previous `spacing > 0` gate created a parser/emitter
|
|
6966
|
+
// asymmetry: a tracked table-property change recording a *previous*
|
|
6967
|
+
// state of `<w:tblCellSpacing w:w="0" …/>` lost the override on
|
|
6968
|
+
// every round-trip.
|
|
6374
6969
|
if (tblPrObj['w:tblCellSpacing']) {
|
|
6375
|
-
const
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6970
|
+
const rawW = tblPrObj['w:tblCellSpacing']['@_w:w'];
|
|
6971
|
+
if (isExplicitlySet(rawW)) {
|
|
6972
|
+
table.setCellSpacing(safeParseInt(rawW));
|
|
6973
|
+
const spacingType = tblPrObj['w:tblCellSpacing']['@_w:type'] || 'dxa';
|
|
6379
6974
|
table.setCellSpacingType(spacingType);
|
|
6380
6975
|
}
|
|
6381
6976
|
}
|
|
@@ -6394,7 +6989,7 @@ export class DocumentParser {
|
|
|
6394
6989
|
table.setIndent(indentVal);
|
|
6395
6990
|
const indentType = tblPrObj['w:tblInd']['@_w:type'];
|
|
6396
6991
|
if (indentType) {
|
|
6397
|
-
table.setIndentType(indentType as import('../elements/Table').TableWidthType);
|
|
6992
|
+
table.setIndentType(indentType as import('../elements/Table.js').TableWidthType);
|
|
6398
6993
|
}
|
|
6399
6994
|
}
|
|
6400
6995
|
|
|
@@ -6460,15 +7055,26 @@ export class DocumentParser {
|
|
|
6460
7055
|
}
|
|
6461
7056
|
}
|
|
6462
7057
|
|
|
6463
|
-
// Parse table borders (w:tblBorders) per ECMA-376 Part 1 §17.4.40
|
|
7058
|
+
// Parse table borders (w:tblBorders) per ECMA-376 Part 1 §17.4.40.
|
|
7059
|
+
// left / right have bidi-aware aliases `w:start` / `w:end` (the
|
|
7060
|
+
// preferred spelling in modern Word-authored documents). Prefer
|
|
7061
|
+
// them when present, falling back to the legacy names — the
|
|
7062
|
+
// internal model stores under `left` / `right`, matching the
|
|
7063
|
+
// emitter. Without this fallback, any table whose side borders
|
|
7064
|
+
// were authored with the bidi-aware form silently lost those
|
|
7065
|
+
// borders on every round-trip (the emitter would replace them
|
|
7066
|
+
// with absent w:left/w:right, and the parser would never revive
|
|
7067
|
+
// the w:start/w:end it dropped).
|
|
6464
7068
|
if (tblPrObj['w:tblBorders']) {
|
|
6465
7069
|
const bordersObj = tblPrObj['w:tblBorders'];
|
|
6466
|
-
const borders: import('../elements/Table').TableBorders = {};
|
|
7070
|
+
const borders: import('../elements/Table.js').TableBorders = {};
|
|
6467
7071
|
|
|
6468
7072
|
if (bordersObj['w:top']) borders.top = this.parseBorderElement(bordersObj['w:top']);
|
|
6469
7073
|
if (bordersObj['w:bottom']) borders.bottom = this.parseBorderElement(bordersObj['w:bottom']);
|
|
6470
|
-
|
|
6471
|
-
if (
|
|
7074
|
+
const leftBorder = bordersObj['w:start'] ?? bordersObj['w:left'];
|
|
7075
|
+
if (leftBorder) borders.left = this.parseBorderElement(leftBorder);
|
|
7076
|
+
const rightBorder = bordersObj['w:end'] ?? bordersObj['w:right'];
|
|
7077
|
+
if (rightBorder) borders.right = this.parseBorderElement(rightBorder);
|
|
6472
7078
|
if (bordersObj['w:insideH'])
|
|
6473
7079
|
borders.insideH = this.parseBorderElement(bordersObj['w:insideH']);
|
|
6474
7080
|
if (bordersObj['w:insideV'])
|
|
@@ -6484,8 +7090,8 @@ export class DocumentParser {
|
|
|
6484
7090
|
const changeObj = tblPrObj['w:tblPrChange'];
|
|
6485
7091
|
table.setTblPrChange({
|
|
6486
7092
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6487
|
-
author: changeObj['@_w:author']
|
|
6488
|
-
date: changeObj['@_w:date']
|
|
7093
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7094
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6489
7095
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:tblPr']),
|
|
6490
7096
|
});
|
|
6491
7097
|
}
|
|
@@ -6562,14 +7168,20 @@ export class DocumentParser {
|
|
|
6562
7168
|
private parseTableRowPropertiesFromObject(trPrObj: any, row: TableRow): void {
|
|
6563
7169
|
if (!trPrObj) return;
|
|
6564
7170
|
|
|
6565
|
-
// Parse row height (w:trHeight) per ECMA-376 Part 1 §17.4.81
|
|
6566
|
-
//
|
|
7171
|
+
// Parse row height (w:trHeight) per ECMA-376 Part 1 §17.4.81.
|
|
7172
|
+
// w:val is ST_TwipsMeasure; zero is a valid value and, combined
|
|
7173
|
+
// with w:hRule="exact", represents a hidden / collapsed row.
|
|
7174
|
+
// XMLParser coerces "0" to the number 0 (falsy), and the previous
|
|
7175
|
+
// `heightVal > 0` gate silently dropped explicit zero-height rows
|
|
7176
|
+
// even though the emitter (TableRow.ts §914) preserves them via
|
|
7177
|
+
// `!== undefined`. Route through isExplicitlySet so zero survives.
|
|
7178
|
+
// Per §17.18.33 (ST_HeightRule), when w:hRule is absent the
|
|
7179
|
+
// default is "auto".
|
|
6567
7180
|
if (trPrObj['w:trHeight']) {
|
|
6568
|
-
const
|
|
7181
|
+
const rawVal = trPrObj['w:trHeight']['@_w:val'];
|
|
6569
7182
|
const heightRule = trPrObj['w:trHeight']['@_w:hRule'];
|
|
6570
|
-
if (
|
|
6571
|
-
|
|
6572
|
-
// so we set height first, then override the rule only if explicitly present
|
|
7183
|
+
if (isExplicitlySet(rawVal)) {
|
|
7184
|
+
const heightVal = safeParseInt(rawVal);
|
|
6573
7185
|
row.setHeight(heightVal);
|
|
6574
7186
|
if (heightRule) {
|
|
6575
7187
|
row.setHeightRule(heightRule);
|
|
@@ -6581,14 +7193,14 @@ export class DocumentParser {
|
|
|
6581
7193
|
}
|
|
6582
7194
|
}
|
|
6583
7195
|
|
|
6584
|
-
// Parse table header row (w:tblHeader) per ECMA-376 Part 1 §17.4.49
|
|
7196
|
+
// Parse table header row (w:tblHeader) per ECMA-376 Part 1 §17.4.49 — CT_OnOff
|
|
6585
7197
|
if (trPrObj['w:tblHeader']) {
|
|
6586
|
-
row.setHeader(
|
|
7198
|
+
row.setHeader(parseOoxmlBoolean(trPrObj['w:tblHeader']));
|
|
6587
7199
|
}
|
|
6588
7200
|
|
|
6589
|
-
// Parse can't split (w:cantSplit) per ECMA-376 Part 1 §17.4.5
|
|
7201
|
+
// Parse can't split (w:cantSplit) per ECMA-376 Part 1 §17.4.5 — CT_OnOff
|
|
6590
7202
|
if (trPrObj['w:cantSplit']) {
|
|
6591
|
-
row.setCantSplit(
|
|
7203
|
+
row.setCantSplit(parseOoxmlBoolean(trPrObj['w:cantSplit']));
|
|
6592
7204
|
}
|
|
6593
7205
|
|
|
6594
7206
|
// Parse row justification (w:jc) per ECMA-376 Part 1 §17.4.79
|
|
@@ -6599,9 +7211,9 @@ export class DocumentParser {
|
|
|
6599
7211
|
}
|
|
6600
7212
|
}
|
|
6601
7213
|
|
|
6602
|
-
// Parse hidden (w:hidden) per ECMA-376 Part 1 §17.4.23
|
|
7214
|
+
// Parse hidden (w:hidden) per ECMA-376 Part 1 §17.4.23 — CT_OnOff
|
|
6603
7215
|
if (trPrObj['w:hidden']) {
|
|
6604
|
-
row.setHidden(
|
|
7216
|
+
row.setHidden(parseOoxmlBoolean(trPrObj['w:hidden']));
|
|
6605
7217
|
}
|
|
6606
7218
|
|
|
6607
7219
|
// Parse grid before (w:gridBefore) per ECMA-376 Part 1 §17.4.15
|
|
@@ -6620,30 +7232,36 @@ export class DocumentParser {
|
|
|
6620
7232
|
}
|
|
6621
7233
|
}
|
|
6622
7234
|
|
|
6623
|
-
// Parse width before (w:wBefore) per ECMA-376 Part 1 §17.4.83
|
|
7235
|
+
// Parse width before (w:wBefore) per ECMA-376 Part 1 §17.4.83.
|
|
7236
|
+
// w:w is ST_TblWidth; 0 paired with w:type="auto" is the idiomatic
|
|
7237
|
+
// "no width" form, and explicit 0 in dxa twips can override an
|
|
7238
|
+
// inherited wBefore. Previous `w > 0` gate silently dropped both.
|
|
6624
7239
|
if (trPrObj['w:wBefore']) {
|
|
6625
|
-
const
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
row.setWBefore(
|
|
7240
|
+
const rawW = trPrObj['w:wBefore']['@_w:w'];
|
|
7241
|
+
if (isExplicitlySet(rawW)) {
|
|
7242
|
+
const type = (trPrObj['w:wBefore']['@_w:type'] as string | undefined) || 'dxa';
|
|
7243
|
+
row.setWBefore(safeParseInt(rawW), type);
|
|
6629
7244
|
}
|
|
6630
7245
|
}
|
|
6631
7246
|
|
|
6632
|
-
// Parse width after (w:wAfter) per ECMA-376 Part 1 §17.4.82
|
|
7247
|
+
// Parse width after (w:wAfter) per ECMA-376 Part 1 §17.4.82 — same
|
|
7248
|
+
// ST_TblWidth semantics as wBefore.
|
|
6633
7249
|
if (trPrObj['w:wAfter']) {
|
|
6634
|
-
const
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
row.setWAfter(
|
|
7250
|
+
const rawW = trPrObj['w:wAfter']['@_w:w'];
|
|
7251
|
+
if (isExplicitlySet(rawW)) {
|
|
7252
|
+
const type = (trPrObj['w:wAfter']['@_w:type'] as string | undefined) || 'dxa';
|
|
7253
|
+
row.setWAfter(safeParseInt(rawW), type);
|
|
6638
7254
|
}
|
|
6639
7255
|
}
|
|
6640
7256
|
|
|
6641
|
-
// Parse row-level cell spacing (w:tblCellSpacing)
|
|
7257
|
+
// Parse row-level cell spacing (w:tblCellSpacing). Zero is a valid
|
|
7258
|
+
// override — "explicitly no extra spacing" on a row overriding a
|
|
7259
|
+
// non-zero table-level tblCellSpacing.
|
|
6642
7260
|
if (trPrObj['w:tblCellSpacing']) {
|
|
6643
|
-
const
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
row.setRowCellSpacing(
|
|
7261
|
+
const rawW = trPrObj['w:tblCellSpacing']['@_w:w'];
|
|
7262
|
+
if (isExplicitlySet(rawW)) {
|
|
7263
|
+
const type = (trPrObj['w:tblCellSpacing']['@_w:type'] as string | undefined) || 'dxa';
|
|
7264
|
+
row.setRowCellSpacing(safeParseInt(rawW), type);
|
|
6647
7265
|
}
|
|
6648
7266
|
}
|
|
6649
7267
|
|
|
@@ -6655,11 +7273,39 @@ export class DocumentParser {
|
|
|
6655
7273
|
}
|
|
6656
7274
|
}
|
|
6657
7275
|
|
|
6658
|
-
// Parse divId (w:divId) per ECMA-376 Part 1 §17.4.9
|
|
7276
|
+
// Parse divId (w:divId) per ECMA-376 Part 1 §17.4.9. `w:val` is
|
|
7277
|
+
// ST_DecimalNumber; 0 is a valid reference to the first div in web
|
|
7278
|
+
// settings. The previous `val > 0` gate silently dropped it on load.
|
|
6659
7279
|
if (trPrObj['w:divId']) {
|
|
6660
|
-
const
|
|
6661
|
-
if (
|
|
6662
|
-
|
|
7280
|
+
const rawVal = trPrObj['w:divId']['@_w:val'];
|
|
7281
|
+
if (isExplicitlySet(rawVal)) {
|
|
7282
|
+
const parsed = safeParseInt(rawVal);
|
|
7283
|
+
if (!isNaN(parsed)) row.setDivId(parsed);
|
|
7284
|
+
}
|
|
7285
|
+
}
|
|
7286
|
+
|
|
7287
|
+
// Parse tracked row insertion / deletion (CT_TrackChange inside CT_TrPr)
|
|
7288
|
+
// per ECMA-376 Part 1 §17.13.5.19 (ins) / §17.13.5.14 (del). These mark
|
|
7289
|
+
// the entire row as a tracked revision; a previous version silently
|
|
7290
|
+
// dropped both markers on load → save because the parser skipped them.
|
|
7291
|
+
if (trPrObj['w:ins']) {
|
|
7292
|
+
const insObj = Array.isArray(trPrObj['w:ins']) ? trPrObj['w:ins'][0] : trPrObj['w:ins'];
|
|
7293
|
+
if (insObj && typeof insObj === 'object') {
|
|
7294
|
+
row.setRowInsertion({
|
|
7295
|
+
id: String(insObj['@_w:id'] ?? '0'),
|
|
7296
|
+
author: String(insObj['@_w:author'] ?? ''),
|
|
7297
|
+
date: String(insObj['@_w:date'] ?? ''),
|
|
7298
|
+
});
|
|
7299
|
+
}
|
|
7300
|
+
}
|
|
7301
|
+
if (trPrObj['w:del']) {
|
|
7302
|
+
const delObj = Array.isArray(trPrObj['w:del']) ? trPrObj['w:del'][0] : trPrObj['w:del'];
|
|
7303
|
+
if (delObj && typeof delObj === 'object') {
|
|
7304
|
+
row.setRowDeletion({
|
|
7305
|
+
id: String(delObj['@_w:id'] ?? '0'),
|
|
7306
|
+
author: String(delObj['@_w:author'] ?? ''),
|
|
7307
|
+
date: String(delObj['@_w:date'] ?? ''),
|
|
7308
|
+
});
|
|
6663
7309
|
}
|
|
6664
7310
|
}
|
|
6665
7311
|
|
|
@@ -6668,8 +7314,8 @@ export class DocumentParser {
|
|
|
6668
7314
|
const changeObj = trPrObj['w:trPrChange'];
|
|
6669
7315
|
row.setTrPrChange({
|
|
6670
7316
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6671
|
-
author: changeObj['@_w:author']
|
|
6672
|
-
date: changeObj['@_w:date']
|
|
7317
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7318
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6673
7319
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:trPr']),
|
|
6674
7320
|
});
|
|
6675
7321
|
}
|
|
@@ -6685,11 +7331,15 @@ export class DocumentParser {
|
|
|
6685
7331
|
|
|
6686
7332
|
const exceptions: any = {};
|
|
6687
7333
|
|
|
6688
|
-
// Parse table width exception (w:tblW)
|
|
7334
|
+
// Parse table width exception (w:tblW). The `val > 0` gate previously
|
|
7335
|
+
// dropped both w:w="0" (explicit zero-width override, valid when
|
|
7336
|
+
// paired with w:type="nil"/"auto") and negative overrides. Route
|
|
7337
|
+
// through isExplicitlySet + safeParseInt so zero and negative widths
|
|
7338
|
+
// round-trip.
|
|
6689
7339
|
if (tblPrExObj['w:tblW']) {
|
|
6690
|
-
const
|
|
6691
|
-
if (
|
|
6692
|
-
exceptions.width =
|
|
7340
|
+
const rawW = tblPrExObj['w:tblW']['@_w:w'];
|
|
7341
|
+
if (isExplicitlySet(rawW)) {
|
|
7342
|
+
exceptions.width = safeParseInt(rawW);
|
|
6693
7343
|
}
|
|
6694
7344
|
}
|
|
6695
7345
|
|
|
@@ -6701,19 +7351,26 @@ export class DocumentParser {
|
|
|
6701
7351
|
}
|
|
6702
7352
|
}
|
|
6703
7353
|
|
|
6704
|
-
// Parse cell spacing exception (w:tblCellSpacing)
|
|
7354
|
+
// Parse cell spacing exception (w:tblCellSpacing). Zero-value
|
|
7355
|
+
// override is valid (= "explicit no cell spacing" on a row that
|
|
7356
|
+
// would otherwise inherit non-zero spacing from the table-level
|
|
7357
|
+
// tblCellSpacing).
|
|
6705
7358
|
if (tblPrExObj['w:tblCellSpacing']) {
|
|
6706
|
-
const
|
|
6707
|
-
if (
|
|
6708
|
-
exceptions.cellSpacing =
|
|
7359
|
+
const rawW = tblPrExObj['w:tblCellSpacing']['@_w:w'];
|
|
7360
|
+
if (isExplicitlySet(rawW)) {
|
|
7361
|
+
exceptions.cellSpacing = safeParseInt(rawW);
|
|
6709
7362
|
}
|
|
6710
7363
|
}
|
|
6711
7364
|
|
|
6712
|
-
// Parse table indentation exception (w:tblInd)
|
|
7365
|
+
// Parse table indentation exception (w:tblInd). Per ECMA-376
|
|
7366
|
+
// §17.4.62 CT_TblWidth, w:w is ST_MeasurementOrPercent — 0 is a
|
|
7367
|
+
// legal "reset" value and negative values indicate an outdent (table
|
|
7368
|
+
// hanging into the page margin). The previous `val > 0` check
|
|
7369
|
+
// silently dropped both.
|
|
6713
7370
|
if (tblPrExObj['w:tblInd']) {
|
|
6714
|
-
const
|
|
6715
|
-
if (
|
|
6716
|
-
exceptions.indentation =
|
|
7371
|
+
const rawW = tblPrExObj['w:tblInd']['@_w:w'];
|
|
7372
|
+
if (isExplicitlySet(rawW)) {
|
|
7373
|
+
exceptions.indentation = safeParseInt(rawW);
|
|
6717
7374
|
}
|
|
6718
7375
|
}
|
|
6719
7376
|
|
|
@@ -6744,15 +7401,40 @@ export class DocumentParser {
|
|
|
6744
7401
|
const borderNames = ['top', 'bottom', 'left', 'right', 'insideH', 'insideV'];
|
|
6745
7402
|
|
|
6746
7403
|
for (const name of borderNames) {
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
7404
|
+
// Prefer bidi-aware `w:start`/`w:end` aliases over legacy `w:left`/
|
|
7405
|
+
// `w:right` per ECMA-376 §17.4.40 CT_TblBorders. Modern Word-
|
|
7406
|
+
// authored documents emit the bidi-aware form by default; the
|
|
7407
|
+
// internal model stores under the legacy keys to match the emitter.
|
|
7408
|
+
const aliasKey = name === 'left' ? 'w:start' : name === 'right' ? 'w:end' : undefined;
|
|
7409
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${name}`];
|
|
7410
|
+
if (borderObj) {
|
|
6750
7411
|
borders[name] = {};
|
|
6751
7412
|
|
|
7413
|
+
// Full CT_Border attribute set (§17.18.2) — previously only the four
|
|
7414
|
+
// basic attrs were read, so tblPrEx borders lost themed-color linkage
|
|
7415
|
+
// on every round-trip.
|
|
6752
7416
|
if (borderObj['@_w:val']) borders[name].style = borderObj['@_w:val'];
|
|
6753
7417
|
if (borderObj['@_w:sz']) borders[name].size = parseInt(borderObj['@_w:sz'], 10);
|
|
6754
7418
|
if (borderObj['@_w:space']) borders[name].space = parseInt(borderObj['@_w:space'], 10);
|
|
6755
|
-
if (borderObj['@_w:color']) borders[name].color = borderObj['@_w:color'];
|
|
7419
|
+
if (borderObj['@_w:color']) borders[name].color = String(borderObj['@_w:color']);
|
|
7420
|
+
// String(...) cast: themeTint / themeShade are ST_UcharHexNumber
|
|
7421
|
+
// (2-char hex). XMLParser coerces purely-digit hex to numbers —
|
|
7422
|
+
// cast so the string contract on the model is preserved.
|
|
7423
|
+
if (borderObj['@_w:themeColor']) {
|
|
7424
|
+
borders[name].themeColor = String(borderObj['@_w:themeColor']);
|
|
7425
|
+
}
|
|
7426
|
+
if (borderObj['@_w:themeTint']) {
|
|
7427
|
+
borders[name].themeTint = String(borderObj['@_w:themeTint']);
|
|
7428
|
+
}
|
|
7429
|
+
if (borderObj['@_w:themeShade']) {
|
|
7430
|
+
borders[name].themeShade = String(borderObj['@_w:themeShade']);
|
|
7431
|
+
}
|
|
7432
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
7433
|
+
borders[name].shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
7434
|
+
}
|
|
7435
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
7436
|
+
borders[name].frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
7437
|
+
}
|
|
6756
7438
|
}
|
|
6757
7439
|
}
|
|
6758
7440
|
|
|
@@ -6773,12 +7455,26 @@ export class DocumentParser {
|
|
|
6773
7455
|
// Parse cell properties (w:tcPr) per ECMA-376 Part 1 §17.4.42
|
|
6774
7456
|
const tcPr = cellObj['w:tcPr'];
|
|
6775
7457
|
if (tcPr) {
|
|
6776
|
-
// Parse cell width (w:tcW) with type per ECMA-376 Part 1 §17.4.
|
|
7458
|
+
// Parse cell width (w:tcW) with type per ECMA-376 Part 1 §17.4.72
|
|
7459
|
+
// CT_TblWidth — w:w is ST_MeasurementOrPercent, w:type is
|
|
7460
|
+
// ST_TblWidth. Zero is a legal explicit override:
|
|
7461
|
+
// - `w:w="0" w:type="auto"` is the idiomatic "size to content"
|
|
7462
|
+
// form (also the default when w:tcW is absent).
|
|
7463
|
+
// - `w:w="0" w:type="dxa"` / `"pct"` / `"nil"` explicitly
|
|
7464
|
+
// override an inherited non-zero width back to zero.
|
|
7465
|
+
// The emitter at TableCell.ts:1353 uses `!== undefined`, so the
|
|
7466
|
+
// previous `widthVal > 0 || widthType === 'auto'` gate created a
|
|
7467
|
+
// parser/emitter asymmetry — any cell with an explicit zero
|
|
7468
|
+
// override in a non-auto width type silently reinherited the
|
|
7469
|
+
// style-level width on every round-trip.
|
|
6777
7470
|
if (tcPr['w:tcW']) {
|
|
6778
|
-
const
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
cell.setWidthType(
|
|
7471
|
+
const rawW = tcPr['w:tcW']['@_w:w'];
|
|
7472
|
+
if (isExplicitlySet(rawW)) {
|
|
7473
|
+
const widthType = (tcPr['w:tcW']['@_w:type'] as string | undefined) || 'dxa';
|
|
7474
|
+
cell.setWidthType(
|
|
7475
|
+
safeParseInt(rawW),
|
|
7476
|
+
widthType as import('../elements/TableCell.js').CellWidthType
|
|
7477
|
+
);
|
|
6782
7478
|
}
|
|
6783
7479
|
}
|
|
6784
7480
|
|
|
@@ -6790,7 +7486,11 @@ export class DocumentParser {
|
|
|
6790
7486
|
}
|
|
6791
7487
|
}
|
|
6792
7488
|
|
|
6793
|
-
// Parse cell borders (w:tcBorders)
|
|
7489
|
+
// Parse cell borders (w:tcBorders) per ECMA-376 Part 1 §17.4.66.
|
|
7490
|
+
// Supports both legacy LTR names (w:left / w:right) and bidi-
|
|
7491
|
+
// aware aliases (w:start / w:end). Prefer w:start / w:end when
|
|
7492
|
+
// present. Includes diagonal borders (w:tl2br / w:tr2bl) which
|
|
7493
|
+
// are cell-specific.
|
|
6794
7494
|
if (tcPr['w:tcBorders']) {
|
|
6795
7495
|
const bordersObj = tcPr['w:tcBorders'];
|
|
6796
7496
|
const borders: any = {};
|
|
@@ -6798,8 +7498,10 @@ export class DocumentParser {
|
|
|
6798
7498
|
if (bordersObj['w:top']) borders.top = this.parseBorderElement(bordersObj['w:top']);
|
|
6799
7499
|
if (bordersObj['w:bottom'])
|
|
6800
7500
|
borders.bottom = this.parseBorderElement(bordersObj['w:bottom']);
|
|
6801
|
-
|
|
6802
|
-
if (
|
|
7501
|
+
const leftBorder = bordersObj['w:start'] ?? bordersObj['w:left'];
|
|
7502
|
+
if (leftBorder) borders.left = this.parseBorderElement(leftBorder);
|
|
7503
|
+
const rightBorder = bordersObj['w:end'] ?? bordersObj['w:right'];
|
|
7504
|
+
if (rightBorder) borders.right = this.parseBorderElement(rightBorder);
|
|
6803
7505
|
if (bordersObj['w:tl2br']) borders.tl2br = this.parseBorderElement(bordersObj['w:tl2br']);
|
|
6804
7506
|
if (bordersObj['w:tr2bl']) borders.tr2bl = this.parseBorderElement(bordersObj['w:tr2bl']);
|
|
6805
7507
|
|
|
@@ -6842,10 +7544,15 @@ export class DocumentParser {
|
|
|
6842
7544
|
}
|
|
6843
7545
|
}
|
|
6844
7546
|
|
|
6845
|
-
// Parse vertical alignment (w:vAlign)
|
|
7547
|
+
// Parse vertical alignment (w:vAlign) per ECMA-376 §17.4.83.
|
|
7548
|
+
// ST_VerticalJc has four values (§17.18.101): top, center, both,
|
|
7549
|
+
// bottom. The previous whitelist dropped "both" silently — the
|
|
7550
|
+
// style-level parser accepts it, so the asymmetry truncated cell
|
|
7551
|
+
// vertical alignment on cells using the "both" (justified)
|
|
7552
|
+
// vertical alignment on load.
|
|
6846
7553
|
if (tcPr['w:vAlign']) {
|
|
6847
7554
|
const valign = tcPr['w:vAlign']['@_w:val'];
|
|
6848
|
-
if (valign
|
|
7555
|
+
if (valign === 'top' || valign === 'center' || valign === 'both' || valign === 'bottom') {
|
|
6849
7556
|
cell.setVerticalAlignment(valign);
|
|
6850
7557
|
}
|
|
6851
7558
|
}
|
|
@@ -6866,14 +7573,14 @@ export class DocumentParser {
|
|
|
6866
7573
|
}
|
|
6867
7574
|
}
|
|
6868
7575
|
|
|
6869
|
-
// Parse no wrap (w:noWrap) per ECMA-376 Part 1 §17.4.34
|
|
7576
|
+
// Parse no wrap (w:noWrap) per ECMA-376 Part 1 §17.4.34 — CT_OnOff
|
|
6870
7577
|
if (tcPr['w:noWrap']) {
|
|
6871
|
-
cell.setNoWrap(
|
|
7578
|
+
cell.setNoWrap(parseOoxmlBoolean(tcPr['w:noWrap']));
|
|
6872
7579
|
}
|
|
6873
7580
|
|
|
6874
|
-
// Parse hide mark (w:hideMark) per ECMA-376 Part 1 §17.4.24
|
|
7581
|
+
// Parse hide mark (w:hideMark) per ECMA-376 Part 1 §17.4.24 — CT_OnOff
|
|
6875
7582
|
if (tcPr['w:hideMark']) {
|
|
6876
|
-
cell.setHideMark(
|
|
7583
|
+
cell.setHideMark(parseOoxmlBoolean(tcPr['w:hideMark']));
|
|
6877
7584
|
}
|
|
6878
7585
|
|
|
6879
7586
|
// Parse headers (w:headers) per ECMA-376 Part 1 §17.4.26
|
|
@@ -6884,9 +7591,9 @@ export class DocumentParser {
|
|
|
6884
7591
|
}
|
|
6885
7592
|
}
|
|
6886
7593
|
|
|
6887
|
-
// Parse fit text (w:tcFitText) per ECMA-376 Part 1 §17.4.68
|
|
7594
|
+
// Parse fit text (w:tcFitText) per ECMA-376 Part 1 §17.4.68 — CT_OnOff
|
|
6888
7595
|
if (tcPr['w:tcFitText']) {
|
|
6889
|
-
cell.setFitText(
|
|
7596
|
+
cell.setFitText(parseOoxmlBoolean(tcPr['w:tcFitText']));
|
|
6890
7597
|
}
|
|
6891
7598
|
|
|
6892
7599
|
// Parse vertical merge (w:vMerge) per ECMA-376 Part 1 §17.4.85
|
|
@@ -6913,10 +7620,11 @@ export class DocumentParser {
|
|
|
6913
7620
|
// Parse table cell insertion marker (w:cellIns) per ECMA-376 Part 1 §17.13.5.5
|
|
6914
7621
|
if (tcPr['w:cellIns']) {
|
|
6915
7622
|
const cellIns = tcPr['w:cellIns'];
|
|
6916
|
-
const id = parseInt(cellIns['@_w:id']
|
|
6917
|
-
const author =
|
|
7623
|
+
const id = parseInt(String(cellIns['@_w:id'] ?? '0'), 10);
|
|
7624
|
+
const author =
|
|
7625
|
+
cellIns['@_w:author'] !== undefined ? String(cellIns['@_w:author']) : 'Unknown';
|
|
6918
7626
|
const dateAttr = cellIns['@_w:date'];
|
|
6919
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7627
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6920
7628
|
|
|
6921
7629
|
const revision = new Revision({
|
|
6922
7630
|
id,
|
|
@@ -6931,10 +7639,11 @@ export class DocumentParser {
|
|
|
6931
7639
|
// Parse table cell deletion marker (w:cellDel) per ECMA-376 Part 1 §17.13.5.6
|
|
6932
7640
|
if (tcPr['w:cellDel']) {
|
|
6933
7641
|
const cellDel = tcPr['w:cellDel'];
|
|
6934
|
-
const id = parseInt(cellDel['@_w:id']
|
|
6935
|
-
const author =
|
|
7642
|
+
const id = parseInt(String(cellDel['@_w:id'] ?? '0'), 10);
|
|
7643
|
+
const author =
|
|
7644
|
+
cellDel['@_w:author'] !== undefined ? String(cellDel['@_w:author']) : 'Unknown';
|
|
6936
7645
|
const dateAttr = cellDel['@_w:date'];
|
|
6937
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7646
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6938
7647
|
|
|
6939
7648
|
const revision = new Revision({
|
|
6940
7649
|
id,
|
|
@@ -6949,10 +7658,11 @@ export class DocumentParser {
|
|
|
6949
7658
|
// Parse table cell merge marker (w:cellMerge) per ECMA-376 Part 1 §17.13.5.4
|
|
6950
7659
|
if (tcPr['w:cellMerge']) {
|
|
6951
7660
|
const cellMerge = tcPr['w:cellMerge'];
|
|
6952
|
-
const id = parseInt(cellMerge['@_w:id']
|
|
6953
|
-
const author =
|
|
7661
|
+
const id = parseInt(String(cellMerge['@_w:id'] ?? '0'), 10);
|
|
7662
|
+
const author =
|
|
7663
|
+
cellMerge['@_w:author'] !== undefined ? String(cellMerge['@_w:author']) : 'Unknown';
|
|
6954
7664
|
const dateAttr = cellMerge['@_w:date'];
|
|
6955
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7665
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6956
7666
|
const vMergeAttr = cellMerge['@_w:vMerge'];
|
|
6957
7667
|
const vMergeOrigAttr = cellMerge['@_w:vMergeOrig'];
|
|
6958
7668
|
// ST_AnnotationVMerge uses "rest"/"cont" but API uses "restart"/"continue"
|
|
@@ -6977,8 +7687,8 @@ export class DocumentParser {
|
|
|
6977
7687
|
const changeObj = tcPr['w:tcPrChange'];
|
|
6978
7688
|
cell.setTcPrChange({
|
|
6979
7689
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6980
|
-
author: changeObj['@_w:author']
|
|
6981
|
-
date: changeObj['@_w:date']
|
|
7690
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7691
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6982
7692
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:tcPr']),
|
|
6983
7693
|
});
|
|
6984
7694
|
}
|
|
@@ -7226,28 +7936,40 @@ export class DocumentParser {
|
|
|
7226
7936
|
// Parse SDT properties (sdtPr)
|
|
7227
7937
|
const sdtPr = sdtObj['w:sdtPr'];
|
|
7228
7938
|
if (sdtPr) {
|
|
7229
|
-
// Parse
|
|
7939
|
+
// Parse `<w:id w:val="…"/>` per ECMA-376 §17.5.2.18. `w:val` is
|
|
7940
|
+
// ST_DecimalNumber (xsd:integer) — 0 is legal. XMLParser coerces
|
|
7941
|
+
// `"0"` to the number `0`, so the previous truthy gate silently
|
|
7942
|
+
// dropped w:id=0 on every load → save cycle. The emitter uses
|
|
7943
|
+
// `!== undefined`, creating a parser/emitter asymmetry.
|
|
7230
7944
|
const idElement = sdtPr['w:id'];
|
|
7231
|
-
if (idElement?.['@_w:val']) {
|
|
7232
|
-
|
|
7945
|
+
if (isExplicitlySet(idElement?.['@_w:val'])) {
|
|
7946
|
+
const parsed = safeParseInt(idElement['@_w:val']);
|
|
7947
|
+
if (!isNaN(parsed)) properties.id = parsed;
|
|
7233
7948
|
}
|
|
7234
7949
|
|
|
7235
|
-
// Parse tag
|
|
7950
|
+
// Parse `<w:tag w:val="…"/>` per ECMA-376 §17.5.2.34. `w:val`
|
|
7951
|
+
// is ST_String — any string is legal, including numeric-looking
|
|
7952
|
+
// strings like "123" that XMLParser coerces to the number 123.
|
|
7953
|
+
// Cast via `String(…)` so the tag round-trips as text rather
|
|
7954
|
+
// than leaking a JS number into a `tag?: string` field.
|
|
7236
7955
|
const tagElement = sdtPr['w:tag'];
|
|
7237
|
-
if (tagElement?.['@_w:val']) {
|
|
7238
|
-
properties.tag = tagElement['@_w:val'];
|
|
7956
|
+
if (tagElement?.['@_w:val'] !== undefined) {
|
|
7957
|
+
properties.tag = String(tagElement['@_w:val']);
|
|
7239
7958
|
}
|
|
7240
7959
|
|
|
7241
|
-
// Parse lock
|
|
7960
|
+
// Parse lock — ST_Lock enum: "sdtLocked" / "contentLocked" /
|
|
7961
|
+
// "sdtContentLocked" / "unlocked". Always a non-numeric string,
|
|
7962
|
+
// so no XMLParser coercion concern; truthy check fine.
|
|
7242
7963
|
const lockElement = sdtPr['w:lock'];
|
|
7243
7964
|
if (lockElement?.['@_w:val']) {
|
|
7244
7965
|
properties.lock = lockElement['@_w:val'];
|
|
7245
7966
|
}
|
|
7246
7967
|
|
|
7247
|
-
// Parse alias
|
|
7968
|
+
// Parse alias — ST_String. Same numeric-coercion concern as
|
|
7969
|
+
// `w:tag`; cast via `String(…)`.
|
|
7248
7970
|
const aliasElement = sdtPr['w:alias'];
|
|
7249
|
-
if (aliasElement?.['@_w:val']) {
|
|
7250
|
-
properties.alias = aliasElement['@_w:val'];
|
|
7971
|
+
if (aliasElement?.['@_w:val'] !== undefined) {
|
|
7972
|
+
properties.alias = String(aliasElement['@_w:val']);
|
|
7251
7973
|
}
|
|
7252
7974
|
|
|
7253
7975
|
// Parse control type from various elements
|
|
@@ -7256,9 +7978,18 @@ export class DocumentParser {
|
|
|
7256
7978
|
} else if (sdtPr['w:text']) {
|
|
7257
7979
|
properties.controlType = 'plainText';
|
|
7258
7980
|
const textElement = sdtPr['w:text'];
|
|
7981
|
+
// w:multiLine is an OPTIONAL ST_OnOff attribute per ECMA-376
|
|
7982
|
+
// §17.5.2.33 CT_SdtText. Only record a value when the source
|
|
7983
|
+
// actually set it — otherwise leave the field undefined so
|
|
7984
|
+
// the emitter (which uses `!== undefined`) preserves the
|
|
7985
|
+
// "attribute absent" state on round-trip. Previously the
|
|
7986
|
+
// parser unconditionally stored `false` for any absent
|
|
7987
|
+
// attribute, then the emitter wrote `w:multiLine="0"` —
|
|
7988
|
+
// adding spec-noise that wasn't in the source.
|
|
7989
|
+
const rawMultiLine = textElement?.['@_w:multiLine'];
|
|
7259
7990
|
properties.plainText = {
|
|
7260
7991
|
multiLine:
|
|
7261
|
-
|
|
7992
|
+
rawMultiLine === undefined ? undefined : parseOnOffAttribute(String(rawMultiLine)),
|
|
7262
7993
|
};
|
|
7263
7994
|
} else if (sdtPr['w:comboBox']) {
|
|
7264
7995
|
properties.controlType = 'comboBox';
|
|
@@ -7286,14 +8017,11 @@ export class DocumentParser {
|
|
|
7286
8017
|
} else if (sdtPr['w14:checkbox']) {
|
|
7287
8018
|
properties.controlType = 'checkbox';
|
|
7288
8019
|
const checkboxElement = sdtPr['w14:checkbox'];
|
|
7289
|
-
//
|
|
7290
|
-
|
|
8020
|
+
// <w14:checked> is CT_OnOff in the Word 2010+ extension namespace.
|
|
8021
|
+
// Honour every ST_OnOff literal ("1"/"0"/"true"/"false"/"on"/"off")
|
|
8022
|
+
// and treat a bare self-closing `<w14:checked/>` as true.
|
|
7291
8023
|
properties.checkbox = {
|
|
7292
|
-
checked:
|
|
7293
|
-
checkedVal === 1 ||
|
|
7294
|
-
checkedVal === '1' ||
|
|
7295
|
-
checkedVal === true ||
|
|
7296
|
-
checkedVal === 'true',
|
|
8024
|
+
checked: parseOoxmlBoolean(checkboxElement?.['w14:checked'], '@_w14:val'),
|
|
7297
8025
|
checkedState: String(checkboxElement?.['w14:checkedState']?.['@_w14:val'] ?? ''),
|
|
7298
8026
|
uncheckedState: String(checkboxElement?.['w14:uncheckedState']?.['@_w14:val'] ?? ''),
|
|
7299
8027
|
};
|
|
@@ -7343,11 +8071,10 @@ export class DocumentParser {
|
|
|
7343
8071
|
};
|
|
7344
8072
|
}
|
|
7345
8073
|
|
|
7346
|
-
// Parse showing placeholder flag (w:showingPlcHdr)
|
|
8074
|
+
// Parse showing placeholder flag (w:showingPlcHdr) — CT_OnOff per ECMA-376 §17.5.2.40
|
|
7347
8075
|
const showingPlcHdr = sdtPr['w:showingPlcHdr'];
|
|
7348
8076
|
if (showingPlcHdr) {
|
|
7349
|
-
|
|
7350
|
-
properties.showingPlcHdr = val === '1' || val === 'true' || val === true;
|
|
8077
|
+
properties.showingPlcHdr = parseOoxmlBoolean(showingPlcHdr);
|
|
7351
8078
|
}
|
|
7352
8079
|
}
|
|
7353
8080
|
|
|
@@ -7887,7 +8614,25 @@ export class DocumentParser {
|
|
|
7887
8614
|
}
|
|
7888
8615
|
|
|
7889
8616
|
/**
|
|
7890
|
-
* Helper to parse list items for combo box / dropdown
|
|
8617
|
+
* Helper to parse list items for combo box / dropdown per ECMA-376
|
|
8618
|
+
* Part 1 §17.5.2.13 CT_SdtListItem. `w:value` is required; both
|
|
8619
|
+
* `w:displayText` and `w:value` are ST_String so any string
|
|
8620
|
+
* (including the empty string) is legal.
|
|
8621
|
+
*
|
|
8622
|
+
* The previous truthy gate dropped legitimate list items whenever:
|
|
8623
|
+
* - `w:value="0"` / `w:value="123"` — XMLParser coerces numeric
|
|
8624
|
+
* strings to numbers; `0` fails the truthy check entirely, and
|
|
8625
|
+
* storing a raw number instead of a string breaks the `ListItem`
|
|
8626
|
+
* `value: string` contract downstream.
|
|
8627
|
+
* - `w:displayText=""` — empty displayText is legal (e.g. a
|
|
8628
|
+
* separator / blank choice); the gate dropped it.
|
|
8629
|
+
* The fix:
|
|
8630
|
+
* - Gate on presence (`!== undefined`), not truthiness.
|
|
8631
|
+
* - Coerce both attributes to `String(…)` so numeric-coerced
|
|
8632
|
+
* attribute values serialise back to their original textual form.
|
|
8633
|
+
* - Default missing `w:displayText` to the stringified `w:value`
|
|
8634
|
+
* (the idiomatic Word fallback when authors author list items
|
|
8635
|
+
* with only a value attribute).
|
|
7891
8636
|
*/
|
|
7892
8637
|
private parseListItems(element: any): any {
|
|
7893
8638
|
const items: any[] = [];
|
|
@@ -7895,17 +8640,18 @@ export class DocumentParser {
|
|
|
7895
8640
|
const itemArray = Array.isArray(listItems) ? listItems : listItems ? [listItems] : [];
|
|
7896
8641
|
|
|
7897
8642
|
for (const item of itemArray) {
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
}
|
|
8643
|
+
const rawValue = item['@_w:value'];
|
|
8644
|
+
if (rawValue === undefined) continue; // w:value is required by the schema
|
|
8645
|
+
const value = String(rawValue);
|
|
8646
|
+
const rawDisplay = item['@_w:displayText'];
|
|
8647
|
+
const displayText = rawDisplay === undefined ? value : String(rawDisplay);
|
|
8648
|
+
items.push({ displayText, value });
|
|
7904
8649
|
}
|
|
7905
8650
|
|
|
8651
|
+
const rawLast = element?.['@_w:lastValue'];
|
|
7906
8652
|
return {
|
|
7907
8653
|
items,
|
|
7908
|
-
lastValue:
|
|
8654
|
+
lastValue: rawLast === undefined ? undefined : String(rawLast),
|
|
7909
8655
|
};
|
|
7910
8656
|
}
|
|
7911
8657
|
|
|
@@ -8381,12 +9127,21 @@ export class DocumentParser {
|
|
|
8381
9127
|
if (color) border.color = color;
|
|
8382
9128
|
const space = XMLParser.extractAttribute(sideXml, 'w:space');
|
|
8383
9129
|
if (space) border.space = parseInt(space.toString(), 10);
|
|
9130
|
+
// w:shadow and w:frame are ST_OnOff per ECMA-376 §17.17.4.
|
|
9131
|
+
// Use `!== undefined` gating so explicit-false survives round-trip
|
|
9132
|
+
// (previous code only stored `true`, silently dropping `w:shadow="0"`).
|
|
8384
9133
|
const shadow = XMLParser.extractAttribute(sideXml, 'w:shadow');
|
|
8385
|
-
if (shadow
|
|
9134
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
8386
9135
|
const frame = XMLParser.extractAttribute(sideXml, 'w:frame');
|
|
8387
|
-
if (frame
|
|
9136
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
8388
9137
|
const themeColor = XMLParser.extractAttribute(sideXml, 'w:themeColor');
|
|
8389
9138
|
if (themeColor) border.themeColor = themeColor;
|
|
9139
|
+
// Theme tint / shade per §17.18.82 — CT_TopBorder/CT_BottomBorder extend
|
|
9140
|
+
// CT_Border so inherit the full themed-color attribute set.
|
|
9141
|
+
const themeTint = XMLParser.extractAttribute(sideXml, 'w:themeTint');
|
|
9142
|
+
if (themeTint) border.themeTint = themeTint;
|
|
9143
|
+
const themeShade = XMLParser.extractAttribute(sideXml, 'w:themeShade');
|
|
9144
|
+
if (themeShade) border.themeShade = themeShade;
|
|
8390
9145
|
const artId = XMLParser.extractAttribute(sideXml, 'w:id');
|
|
8391
9146
|
if (artId) border.artId = parseInt(artId.toString(), 10);
|
|
8392
9147
|
return Object.keys(border).length > 0 ? border : undefined;
|
|
@@ -8407,7 +9162,14 @@ export class DocumentParser {
|
|
|
8407
9162
|
}
|
|
8408
9163
|
}
|
|
8409
9164
|
|
|
8410
|
-
// Parse columns
|
|
9165
|
+
// Parse columns per ECMA-376 §17.6.4 CT_Columns. Every attribute
|
|
9166
|
+
// (num / sep / space / equalWidth) is optional with spec-defined
|
|
9167
|
+
// defaults — num defaults to 1, equalWidth to true, sep to false,
|
|
9168
|
+
// space to 720 twips. The previous `if (num)` gate silently dropped
|
|
9169
|
+
// every `<w:cols>` that relied on the default num=1 (e.g. a bare
|
|
9170
|
+
// `<w:cols w:sep="1" w:space="720"/>` specifying a single column
|
|
9171
|
+
// with a separator), which is the exact form Word emits when the
|
|
9172
|
+
// user toggles the column separator without changing column count.
|
|
8411
9173
|
const colsElements = XMLParser.extractElements(sectPr, 'w:cols');
|
|
8412
9174
|
if (colsElements.length > 0) {
|
|
8413
9175
|
const cols = colsElements[0];
|
|
@@ -8436,19 +9198,23 @@ export class DocumentParser {
|
|
|
8436
9198
|
}
|
|
8437
9199
|
}
|
|
8438
9200
|
|
|
8439
|
-
//
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
9201
|
+
// Spec default for num is 1; fall back to column-count from
|
|
9202
|
+
// child `<w:col>` children when available (the expanded per-column
|
|
9203
|
+
// form), otherwise to the literal default.
|
|
9204
|
+
const count = num
|
|
9205
|
+
? parseInt(num.toString(), 10)
|
|
9206
|
+
: columnWidths.length > 0
|
|
9207
|
+
? columnWidths.length
|
|
9208
|
+
: 1;
|
|
9209
|
+
|
|
9210
|
+
sectionProps.columns = {
|
|
9211
|
+
count,
|
|
9212
|
+
space: space ? parseInt(space.toString(), 10) : undefined,
|
|
9213
|
+
equalWidth: equalWidth ? parseOnOffAttribute(equalWidth) : undefined,
|
|
9214
|
+
separator: sep ? parseOnOffAttribute(sep) : undefined,
|
|
9215
|
+
columnWidths: columnWidths.length > 0 ? columnWidths : undefined,
|
|
9216
|
+
columnSpaces: hasColumnSpaces ? columnSpaces : undefined,
|
|
9217
|
+
};
|
|
8452
9218
|
}
|
|
8453
9219
|
}
|
|
8454
9220
|
|
|
@@ -8489,9 +9255,13 @@ export class DocumentParser {
|
|
|
8489
9255
|
}
|
|
8490
9256
|
}
|
|
8491
9257
|
|
|
8492
|
-
// Parse title page flag
|
|
8493
|
-
|
|
8494
|
-
|
|
9258
|
+
// Parse title page flag (w:titlePg) — CT_OnOff per ECMA-376 §17.6.23;
|
|
9259
|
+
// honour w:val so an explicit `w:val="0"` override of an inherited
|
|
9260
|
+
// true is not silently flipped to true.
|
|
9261
|
+
const titlePgEls = XMLParser.extractElements(sectPr, 'w:titlePg');
|
|
9262
|
+
if (titlePgEls.length > 0 && titlePgEls[0]) {
|
|
9263
|
+
const v = XMLParser.extractAttribute(titlePgEls[0], 'w:val');
|
|
9264
|
+
sectionProps.titlePage = parseOnOffAttribute(v, true);
|
|
8495
9265
|
}
|
|
8496
9266
|
|
|
8497
9267
|
// Parse header references
|
|
@@ -8574,14 +9344,18 @@ export class DocumentParser {
|
|
|
8574
9344
|
}
|
|
8575
9345
|
}
|
|
8576
9346
|
|
|
8577
|
-
// Parse bidi (
|
|
8578
|
-
|
|
8579
|
-
|
|
9347
|
+
// Parse bidi (w:bidi) — CT_OnOff per ECMA-376 §17.6.1 (RTL section)
|
|
9348
|
+
const bidiEls = XMLParser.extractElements(sectPr, 'w:bidi');
|
|
9349
|
+
if (bidiEls.length > 0 && bidiEls[0]) {
|
|
9350
|
+
const v = XMLParser.extractAttribute(bidiEls[0], 'w:val');
|
|
9351
|
+
sectionProps.bidi = parseOnOffAttribute(v, true);
|
|
8580
9352
|
}
|
|
8581
9353
|
|
|
8582
|
-
// Parse RTL gutter
|
|
8583
|
-
|
|
8584
|
-
|
|
9354
|
+
// Parse RTL gutter (w:rtlGutter) — CT_OnOff per ECMA-376 §17.6.16
|
|
9355
|
+
const rtlGutterEls = XMLParser.extractElements(sectPr, 'w:rtlGutter');
|
|
9356
|
+
if (rtlGutterEls.length > 0 && rtlGutterEls[0]) {
|
|
9357
|
+
const v = XMLParser.extractAttribute(rtlGutterEls[0], 'w:val');
|
|
9358
|
+
sectionProps.rtlGutter = parseOnOffAttribute(v, true);
|
|
8585
9359
|
}
|
|
8586
9360
|
|
|
8587
9361
|
// Parse document grid (w:docGrid)
|
|
@@ -8659,14 +9433,18 @@ export class DocumentParser {
|
|
|
8659
9433
|
if (Object.keys(props).length > 0) sectionProps.endnotePr = props;
|
|
8660
9434
|
}
|
|
8661
9435
|
|
|
8662
|
-
// Parse noEndnote
|
|
8663
|
-
|
|
8664
|
-
|
|
9436
|
+
// Parse noEndnote (w:noEndnote) — CT_OnOff per ECMA-376 §17.11.14
|
|
9437
|
+
const noEndEls = XMLParser.extractElements(sectPr, 'w:noEndnote');
|
|
9438
|
+
if (noEndEls.length > 0 && noEndEls[0]) {
|
|
9439
|
+
const v = XMLParser.extractAttribute(noEndEls[0], 'w:val');
|
|
9440
|
+
sectionProps.noEndnote = parseOnOffAttribute(v, true);
|
|
8665
9441
|
}
|
|
8666
9442
|
|
|
8667
|
-
// Parse form protection
|
|
8668
|
-
|
|
8669
|
-
|
|
9443
|
+
// Parse form protection (w:formProt) — CT_OnOff per ECMA-376 §17.6.8
|
|
9444
|
+
const formProtEls = XMLParser.extractElements(sectPr, 'w:formProt');
|
|
9445
|
+
if (formProtEls.length > 0 && formProtEls[0]) {
|
|
9446
|
+
const v = XMLParser.extractAttribute(formProtEls[0], 'w:val');
|
|
9447
|
+
sectionProps.formProt = parseOnOffAttribute(v, true);
|
|
8670
9448
|
}
|
|
8671
9449
|
|
|
8672
9450
|
// Parse printer settings (w:printerSettings r:id)
|
|
@@ -8789,34 +9567,45 @@ export class DocumentParser {
|
|
|
8789
9567
|
runFormatting = this.parseRunFormattingFromXml(rPrXml);
|
|
8790
9568
|
}
|
|
8791
9569
|
|
|
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 ');
|
|
9570
|
+
// Parse metadata CT_OnOff flags per ECMA-376 §17.7.4 (OnOffType bindings).
|
|
9571
|
+
// Each flag honours `w:val` so an explicit `<w:qFormat w:val="0"/>` override
|
|
9572
|
+
// of a based-on style's qFormat=true round-trips as `false`. The old code
|
|
9573
|
+
// detected presence via `styleXml.includes('<w:qFormat/>')` which ignored
|
|
9574
|
+
// w:val entirely and flipped any explicit-false to true.
|
|
9575
|
+
const parseStyleOnOffFlag = (tagName: string): boolean | undefined => {
|
|
9576
|
+
const els = XMLParser.extractElements(styleXml, tagName);
|
|
9577
|
+
if (els.length === 0 || !els[0]) return undefined;
|
|
9578
|
+
const v = XMLParser.extractAttribute(els[0], 'w:val');
|
|
9579
|
+
return parseOnOffAttribute(v, true);
|
|
9580
|
+
};
|
|
8816
9581
|
|
|
8817
|
-
|
|
8818
|
-
const
|
|
8819
|
-
|
|
9582
|
+
const qFormat = parseStyleOnOffFlag('w:qFormat');
|
|
9583
|
+
const semiHidden = parseStyleOnOffFlag('w:semiHidden');
|
|
9584
|
+
const unhideWhenUsed = parseStyleOnOffFlag('w:unhideWhenUsed');
|
|
9585
|
+
const locked = parseStyleOnOffFlag('w:locked');
|
|
9586
|
+
const personal = parseStyleOnOffFlag('w:personal');
|
|
9587
|
+
const personalCompose = parseStyleOnOffFlag('w:personalCompose');
|
|
9588
|
+
const personalReply = parseStyleOnOffFlag('w:personalReply');
|
|
9589
|
+
const autoRedefine = parseStyleOnOffFlag('w:autoRedefine');
|
|
9590
|
+
// `<w:hidden>` (CT_Style §17.7.4, OnOffOnlyType) — completely hide the
|
|
9591
|
+
// style. Previously not modeled; now round-trips as `properties.hidden`.
|
|
9592
|
+
const hidden = parseStyleOnOffFlag('w:hidden');
|
|
9593
|
+
|
|
9594
|
+
// `<w:rsid w:val="HEX"/>` (CT_Style §17.7.4, CT_LongHexNumber §17.18.50) —
|
|
9595
|
+
// revision-save ID stamp identifying the session in which this style
|
|
9596
|
+
// definition was last edited. Schema position: between `personalReply`
|
|
9597
|
+
// and `pPr`. Previously dropped entirely on parse, now preserved on
|
|
9598
|
+
// StyleProperties so round-trips stay faithful.
|
|
9599
|
+
let styleRsid: string | undefined;
|
|
9600
|
+
if (styleXml.includes('<w:rsid')) {
|
|
9601
|
+
const rsidTag = XMLParser.extractSelfClosingTag(styleXml, 'w:rsid');
|
|
9602
|
+
if (rsidTag) {
|
|
9603
|
+
const v = XMLParser.extractAttribute(`<w:rsid${rsidTag}`, 'w:val');
|
|
9604
|
+
if (v && v.length > 0) {
|
|
9605
|
+
styleRsid = v;
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
}
|
|
8820
9609
|
|
|
8821
9610
|
// uiPriority - Sort order
|
|
8822
9611
|
let uiPriority: number | undefined;
|
|
@@ -8855,7 +9644,7 @@ export class DocumentParser {
|
|
|
8855
9644
|
}
|
|
8856
9645
|
|
|
8857
9646
|
// Parse table style properties (Phase 5.1)
|
|
8858
|
-
let tableStyle: import('../formatting/Style').TableStyleProperties | undefined;
|
|
9647
|
+
let tableStyle: import('../formatting/Style.js').TableStyleProperties | undefined;
|
|
8859
9648
|
if (typeAttr === 'table') {
|
|
8860
9649
|
tableStyle = this.parseTableStyleProperties(styleXml);
|
|
8861
9650
|
}
|
|
@@ -8867,21 +9656,27 @@ export class DocumentParser {
|
|
|
8867
9656
|
type: typeAttr,
|
|
8868
9657
|
basedOn,
|
|
8869
9658
|
next,
|
|
8870
|
-
|
|
8871
|
-
|
|
9659
|
+
// w:default and w:customStyle are ST_OnOff per ECMA-376 §17.17.4
|
|
9660
|
+
isDefault: parseOnOffAttribute(defaultAttr),
|
|
9661
|
+
customStyle: parseOnOffAttribute(customStyleAttr),
|
|
8872
9662
|
paragraphFormatting,
|
|
8873
9663
|
numPr: styleNumPr,
|
|
8874
9664
|
runFormatting,
|
|
8875
9665
|
tableStyle,
|
|
8876
|
-
// Metadata
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
9666
|
+
// Metadata CT_OnOff flags (ECMA-376 §17.7.4). parseStyleOnOffFlag returns
|
|
9667
|
+
// undefined when the element is absent, or the actual boolean (true/false)
|
|
9668
|
+
// when present — preserve both "explicit false" (override) and "absent"
|
|
9669
|
+
// (inherit) faithfully through the Style properties record.
|
|
9670
|
+
qFormat,
|
|
9671
|
+
semiHidden,
|
|
9672
|
+
hidden,
|
|
9673
|
+
unhideWhenUsed,
|
|
9674
|
+
locked,
|
|
9675
|
+
personal,
|
|
9676
|
+
personalCompose,
|
|
9677
|
+
personalReply,
|
|
9678
|
+
rsid: styleRsid,
|
|
9679
|
+
autoRedefine,
|
|
8885
9680
|
uiPriority,
|
|
8886
9681
|
link,
|
|
8887
9682
|
aliases,
|
|
@@ -8898,6 +9693,86 @@ export class DocumentParser {
|
|
|
8898
9693
|
private parseParagraphFormattingFromXml(pPrXml: string): ParagraphFormatting {
|
|
8899
9694
|
const formatting: ParagraphFormatting = {};
|
|
8900
9695
|
|
|
9696
|
+
// Parse framePr (text frame properties) per ECMA-376 Part 1 §17.3.1.11 —
|
|
9697
|
+
// CT_FramePr is a CT_PPrBase child (#5, between pageBreakBefore and
|
|
9698
|
+
// widowControl). Each attribute is independently optional; numeric
|
|
9699
|
+
// attributes (w/h/x/y/hSpace/vSpace/lines) may legitimately be zero
|
|
9700
|
+
// so use explicit string presence rather than truthy checks.
|
|
9701
|
+
const framePrTag = XMLParser.extractSelfClosingTag(pPrXml, 'w:framePr');
|
|
9702
|
+
if (framePrTag) {
|
|
9703
|
+
const fpStr = `<w:framePr${framePrTag}`;
|
|
9704
|
+
const frameProps: NonNullable<ParagraphFormatting['framePr']> = {};
|
|
9705
|
+
const wAttr = XMLParser.extractAttribute(fpStr, 'w:w');
|
|
9706
|
+
if (wAttr !== undefined) frameProps.w = parseInt(wAttr, 10);
|
|
9707
|
+
const hAttr = XMLParser.extractAttribute(fpStr, 'w:h');
|
|
9708
|
+
if (hAttr !== undefined) frameProps.h = parseInt(hAttr, 10);
|
|
9709
|
+
const hRule = XMLParser.extractAttribute(fpStr, 'w:hRule');
|
|
9710
|
+
if (hRule === 'auto' || hRule === 'atLeast' || hRule === 'exact') {
|
|
9711
|
+
frameProps.hRule = hRule;
|
|
9712
|
+
}
|
|
9713
|
+
const xAttr = XMLParser.extractAttribute(fpStr, 'w:x');
|
|
9714
|
+
if (xAttr !== undefined) frameProps.x = parseInt(xAttr, 10);
|
|
9715
|
+
const yAttr = XMLParser.extractAttribute(fpStr, 'w:y');
|
|
9716
|
+
if (yAttr !== undefined) frameProps.y = parseInt(yAttr, 10);
|
|
9717
|
+
const xAlign = XMLParser.extractAttribute(fpStr, 'w:xAlign');
|
|
9718
|
+
if (
|
|
9719
|
+
xAlign === 'left' ||
|
|
9720
|
+
xAlign === 'center' ||
|
|
9721
|
+
xAlign === 'right' ||
|
|
9722
|
+
xAlign === 'inside' ||
|
|
9723
|
+
xAlign === 'outside'
|
|
9724
|
+
) {
|
|
9725
|
+
frameProps.xAlign = xAlign;
|
|
9726
|
+
}
|
|
9727
|
+
const yAlign = XMLParser.extractAttribute(fpStr, 'w:yAlign');
|
|
9728
|
+
if (
|
|
9729
|
+
yAlign === 'top' ||
|
|
9730
|
+
yAlign === 'center' ||
|
|
9731
|
+
yAlign === 'bottom' ||
|
|
9732
|
+
yAlign === 'inline' ||
|
|
9733
|
+
yAlign === 'inside' ||
|
|
9734
|
+
yAlign === 'outside'
|
|
9735
|
+
) {
|
|
9736
|
+
frameProps.yAlign = yAlign;
|
|
9737
|
+
}
|
|
9738
|
+
const hAnchor = XMLParser.extractAttribute(fpStr, 'w:hAnchor');
|
|
9739
|
+
if (hAnchor === 'page' || hAnchor === 'margin' || hAnchor === 'text') {
|
|
9740
|
+
frameProps.hAnchor = hAnchor;
|
|
9741
|
+
}
|
|
9742
|
+
const vAnchor = XMLParser.extractAttribute(fpStr, 'w:vAnchor');
|
|
9743
|
+
if (vAnchor === 'page' || vAnchor === 'margin' || vAnchor === 'text') {
|
|
9744
|
+
frameProps.vAnchor = vAnchor;
|
|
9745
|
+
}
|
|
9746
|
+
const hSpace = XMLParser.extractAttribute(fpStr, 'w:hSpace');
|
|
9747
|
+
if (hSpace !== undefined) frameProps.hSpace = parseInt(hSpace, 10);
|
|
9748
|
+
const vSpace = XMLParser.extractAttribute(fpStr, 'w:vSpace');
|
|
9749
|
+
if (vSpace !== undefined) frameProps.vSpace = parseInt(vSpace, 10);
|
|
9750
|
+
const wrap = XMLParser.extractAttribute(fpStr, 'w:wrap');
|
|
9751
|
+
if (
|
|
9752
|
+
wrap === 'around' ||
|
|
9753
|
+
wrap === 'auto' ||
|
|
9754
|
+
wrap === 'none' ||
|
|
9755
|
+
wrap === 'notBeside' ||
|
|
9756
|
+
wrap === 'through' ||
|
|
9757
|
+
wrap === 'tight'
|
|
9758
|
+
) {
|
|
9759
|
+
frameProps.wrap = wrap;
|
|
9760
|
+
}
|
|
9761
|
+
const dropCap = XMLParser.extractAttribute(fpStr, 'w:dropCap');
|
|
9762
|
+
if (dropCap === 'none' || dropCap === 'drop' || dropCap === 'margin') {
|
|
9763
|
+
frameProps.dropCap = dropCap;
|
|
9764
|
+
}
|
|
9765
|
+
const lines = XMLParser.extractAttribute(fpStr, 'w:lines');
|
|
9766
|
+
if (lines !== undefined) frameProps.lines = parseInt(lines, 10);
|
|
9767
|
+
const anchorLock = XMLParser.extractAttribute(fpStr, 'w:anchorLock');
|
|
9768
|
+
if (anchorLock !== undefined) {
|
|
9769
|
+
frameProps.anchorLock = parseOnOffAttribute(anchorLock, true);
|
|
9770
|
+
}
|
|
9771
|
+
if (Object.keys(frameProps).length > 0) {
|
|
9772
|
+
formatting.framePr = frameProps;
|
|
9773
|
+
}
|
|
9774
|
+
}
|
|
9775
|
+
|
|
8901
9776
|
// Parse alignment (w:jc)
|
|
8902
9777
|
const jcElement = XMLParser.extractSelfClosingTag(pPrXml, 'w:jc');
|
|
8903
9778
|
if (jcElement) {
|
|
@@ -8937,15 +9812,17 @@ export class DocumentParser {
|
|
|
8937
9812
|
lineRule: validatedLineRule,
|
|
8938
9813
|
beforeLines: beforeLines ? parseInt(beforeLines, 10) : undefined,
|
|
8939
9814
|
afterLines: afterLines ? parseInt(afterLines, 10) : undefined,
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
afterAutospacing: afterAutosp ? afterAutosp === '1' || afterAutosp === 'true' : undefined,
|
|
9815
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
9816
|
+
beforeAutospacing: beforeAutosp ? parseOnOffAttribute(beforeAutosp) : undefined,
|
|
9817
|
+
afterAutospacing: afterAutosp ? parseOnOffAttribute(afterAutosp) : undefined,
|
|
8944
9818
|
};
|
|
8945
9819
|
}
|
|
8946
9820
|
|
|
8947
9821
|
// Parse indentation (w:ind)
|
|
8948
|
-
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to
|
|
9822
|
+
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to
|
|
9823
|
+
// w:left/w:right. §17.3.1.12 also defines six CJK character-unit variants
|
|
9824
|
+
// (ST_DecimalNumber) — parse those alongside so styles authored in CJK
|
|
9825
|
+
// locales preserve their character-unit indent spec through round-trip.
|
|
8949
9826
|
const indElement = XMLParser.extractSelfClosingTag(pPrXml, 'w:ind');
|
|
8950
9827
|
if (indElement) {
|
|
8951
9828
|
const indTag = `<w:ind${indElement}`;
|
|
@@ -8955,33 +9832,137 @@ export class DocumentParser {
|
|
|
8955
9832
|
const right = XMLParser.extractAttribute(indTag, 'w:right');
|
|
8956
9833
|
const firstLine = XMLParser.extractAttribute(indTag, 'w:firstLine');
|
|
8957
9834
|
const hanging = XMLParser.extractAttribute(indTag, 'w:hanging');
|
|
9835
|
+
// CJK character-unit variants. startChars/endChars collapse to
|
|
9836
|
+
// leftChars/rightChars (same bidi-aware rule as the twips pair).
|
|
9837
|
+
const startChars = XMLParser.extractAttribute(indTag, 'w:startChars');
|
|
9838
|
+
const leftChars = XMLParser.extractAttribute(indTag, 'w:leftChars');
|
|
9839
|
+
const endChars = XMLParser.extractAttribute(indTag, 'w:endChars');
|
|
9840
|
+
const rightChars = XMLParser.extractAttribute(indTag, 'w:rightChars');
|
|
9841
|
+
const firstLineChars = XMLParser.extractAttribute(indTag, 'w:firstLineChars');
|
|
9842
|
+
const hangingChars = XMLParser.extractAttribute(indTag, 'w:hangingChars');
|
|
8958
9843
|
|
|
8959
9844
|
const leftVal = start || left;
|
|
8960
9845
|
const rightVal = end || right;
|
|
9846
|
+
const leftCharsVal = startChars || leftChars;
|
|
9847
|
+
const rightCharsVal = endChars || rightChars;
|
|
8961
9848
|
|
|
8962
9849
|
formatting.indentation = {
|
|
8963
9850
|
left: leftVal ? parseInt(leftVal, 10) : undefined,
|
|
8964
9851
|
right: rightVal ? parseInt(rightVal, 10) : undefined,
|
|
8965
9852
|
firstLine: firstLine ? parseInt(firstLine, 10) : undefined,
|
|
8966
9853
|
hanging: hanging ? parseInt(hanging, 10) : undefined,
|
|
9854
|
+
leftChars: leftCharsVal ? parseInt(leftCharsVal, 10) : undefined,
|
|
9855
|
+
rightChars: rightCharsVal ? parseInt(rightCharsVal, 10) : undefined,
|
|
9856
|
+
firstLineChars: firstLineChars ? parseInt(firstLineChars, 10) : undefined,
|
|
9857
|
+
hangingChars: hangingChars ? parseInt(hangingChars, 10) : undefined,
|
|
8967
9858
|
};
|
|
8968
9859
|
}
|
|
8969
9860
|
|
|
8970
|
-
// Parse boolean
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
9861
|
+
// Parse CT_OnOff boolean flags per ECMA-376 §17.17.4 / §17.3.1. The previous
|
|
9862
|
+
// substring-only detection (`pPrXml.includes('<w:keepNext/>') ||
|
|
9863
|
+
// pPrXml.includes('<w:keepNext ')`) hard-coded the flag to true whenever
|
|
9864
|
+
// the element appeared at all — silently flipping `<w:keepNext w:val="0"/>`
|
|
9865
|
+
// (explicit override) into an enabled flag. Read w:val when present and
|
|
9866
|
+
// honour every ST_OnOff literal (1/0/true/false/on/off).
|
|
9867
|
+
const parseStylePPrCtOnOff = (tagName: string): boolean | undefined => {
|
|
9868
|
+
// extractSelfClosingTag returns the ATTRIBUTE STRING (possibly empty)
|
|
9869
|
+
// when found, or `undefined` when absent. Earlier this helper checked
|
|
9870
|
+
// `=== null` by mistake — that let the "absent" case fall through and
|
|
9871
|
+
// construct a garbage tag that produced `true`, silently enabling the
|
|
9872
|
+
// flag on every style that didn't set it.
|
|
9873
|
+
const el = XMLParser.extractSelfClosingTag(pPrXml, tagName);
|
|
9874
|
+
if (el === undefined) return undefined;
|
|
9875
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
9876
|
+
if (val === undefined) return true;
|
|
9877
|
+
return parseOnOffAttribute(val, true);
|
|
9878
|
+
};
|
|
9879
|
+
|
|
9880
|
+
const keepNextVal = parseStylePPrCtOnOff('w:keepNext');
|
|
9881
|
+
if (keepNextVal !== undefined) formatting.keepNext = keepNextVal;
|
|
9882
|
+
|
|
9883
|
+
const keepLinesVal = parseStylePPrCtOnOff('w:keepLines');
|
|
9884
|
+
if (keepLinesVal !== undefined) formatting.keepLines = keepLinesVal;
|
|
9885
|
+
|
|
9886
|
+
const pageBreakBeforeVal = parseStylePPrCtOnOff('w:pageBreakBefore');
|
|
9887
|
+
if (pageBreakBeforeVal !== undefined) formatting.pageBreakBefore = pageBreakBeforeVal;
|
|
9888
|
+
|
|
9889
|
+
// Contextual spacing per ECMA-376 Part 1 §17.3.1.9
|
|
9890
|
+
// "Don't add space between paragraphs of the same style"
|
|
9891
|
+
const contextualSpacingVal = parseStylePPrCtOnOff('w:contextualSpacing');
|
|
9892
|
+
if (contextualSpacingVal !== undefined) formatting.contextualSpacing = contextualSpacingVal;
|
|
9893
|
+
|
|
9894
|
+
// Remaining CT_PPrBase CT_OnOff flags per ECMA-376 Part 1 §17.3.1.
|
|
9895
|
+
// The main paragraph parser handles all of these; the style-level parser
|
|
9896
|
+
// previously dropped them (substring matches existed only for the four
|
|
9897
|
+
// flags above). Any style using the explicit-false form to override a
|
|
9898
|
+
// based-on style's enabled flag was silently losing the override.
|
|
9899
|
+
const widowControlVal = parseStylePPrCtOnOff('w:widowControl');
|
|
9900
|
+
if (widowControlVal !== undefined) formatting.widowControl = widowControlVal;
|
|
9901
|
+
|
|
9902
|
+
const suppressLineNumbersVal = parseStylePPrCtOnOff('w:suppressLineNumbers');
|
|
9903
|
+
if (suppressLineNumbersVal !== undefined)
|
|
9904
|
+
formatting.suppressLineNumbers = suppressLineNumbersVal;
|
|
9905
|
+
|
|
9906
|
+
const bidiVal = parseStylePPrCtOnOff('w:bidi');
|
|
9907
|
+
if (bidiVal !== undefined) formatting.bidi = bidiVal;
|
|
9908
|
+
|
|
9909
|
+
const mirrorIndentsVal = parseStylePPrCtOnOff('w:mirrorIndents');
|
|
9910
|
+
if (mirrorIndentsVal !== undefined) formatting.mirrorIndents = mirrorIndentsVal;
|
|
9911
|
+
|
|
9912
|
+
const adjustRightIndVal = parseStylePPrCtOnOff('w:adjustRightInd');
|
|
9913
|
+
if (adjustRightIndVal !== undefined) formatting.adjustRightInd = adjustRightIndVal;
|
|
9914
|
+
|
|
9915
|
+
const suppressAutoHyphensVal = parseStylePPrCtOnOff('w:suppressAutoHyphens');
|
|
9916
|
+
if (suppressAutoHyphensVal !== undefined)
|
|
9917
|
+
formatting.suppressAutoHyphens = suppressAutoHyphensVal;
|
|
9918
|
+
|
|
9919
|
+
const kinsokuVal = parseStylePPrCtOnOff('w:kinsoku');
|
|
9920
|
+
if (kinsokuVal !== undefined) formatting.kinsoku = kinsokuVal;
|
|
9921
|
+
|
|
9922
|
+
const wordWrapVal = parseStylePPrCtOnOff('w:wordWrap');
|
|
9923
|
+
if (wordWrapVal !== undefined) formatting.wordWrap = wordWrapVal;
|
|
9924
|
+
|
|
9925
|
+
const overflowPunctVal = parseStylePPrCtOnOff('w:overflowPunct');
|
|
9926
|
+
if (overflowPunctVal !== undefined) formatting.overflowPunct = overflowPunctVal;
|
|
9927
|
+
|
|
9928
|
+
const topLinePunctVal = parseStylePPrCtOnOff('w:topLinePunct');
|
|
9929
|
+
if (topLinePunctVal !== undefined) formatting.topLinePunct = topLinePunctVal;
|
|
9930
|
+
|
|
9931
|
+
const autoSpaceDEVal = parseStylePPrCtOnOff('w:autoSpaceDE');
|
|
9932
|
+
if (autoSpaceDEVal !== undefined) formatting.autoSpaceDE = autoSpaceDEVal;
|
|
9933
|
+
|
|
9934
|
+
const autoSpaceDNVal = parseStylePPrCtOnOff('w:autoSpaceDN');
|
|
9935
|
+
if (autoSpaceDNVal !== undefined) formatting.autoSpaceDN = autoSpaceDNVal;
|
|
9936
|
+
|
|
9937
|
+
const suppressOverlapVal = parseStylePPrCtOnOff('w:suppressOverlap');
|
|
9938
|
+
if (suppressOverlapVal !== undefined) formatting.suppressOverlap = suppressOverlapVal;
|
|
9939
|
+
|
|
9940
|
+
// Parse `w:val`-attribute string-enum children per CT_PPrBase.
|
|
9941
|
+
// Position #28 textDirection (ST_TextDirection), #29 textAlignment
|
|
9942
|
+
// (ST_TextAlignment), #30 textboxTightWrap (ST_TextboxTightWrapType).
|
|
9943
|
+
// The main paragraph parser handles these; the style-level parser
|
|
9944
|
+
// previously dropped them because the substring scan was never
|
|
9945
|
+
// extended past the iteration-25 CT_OnOff helper.
|
|
9946
|
+
const parseStylePPrValAttr = (tagName: string): string | undefined => {
|
|
9947
|
+
const el = XMLParser.extractSelfClosingTag(pPrXml, tagName);
|
|
9948
|
+
if (el === undefined) return undefined;
|
|
9949
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
9950
|
+
return val === undefined ? undefined : String(val);
|
|
9951
|
+
};
|
|
9952
|
+
|
|
9953
|
+
const textDirectionVal = parseStylePPrValAttr('w:textDirection');
|
|
9954
|
+
if (textDirectionVal !== undefined) {
|
|
9955
|
+
formatting.textDirection = textDirectionVal as ParagraphFormatting['textDirection'];
|
|
8976
9956
|
}
|
|
8977
|
-
|
|
8978
|
-
|
|
9957
|
+
|
|
9958
|
+
const textAlignmentVal = parseStylePPrValAttr('w:textAlignment');
|
|
9959
|
+
if (textAlignmentVal !== undefined) {
|
|
9960
|
+
formatting.textAlignment = textAlignmentVal as ParagraphFormatting['textAlignment'];
|
|
8979
9961
|
}
|
|
8980
9962
|
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
formatting.contextualSpacing = true;
|
|
9963
|
+
const textboxTightWrapVal = parseStylePPrValAttr('w:textboxTightWrap');
|
|
9964
|
+
if (textboxTightWrapVal !== undefined) {
|
|
9965
|
+
formatting.textboxTightWrap = textboxTightWrapVal as ParagraphFormatting['textboxTightWrap'];
|
|
8985
9966
|
}
|
|
8986
9967
|
|
|
8987
9968
|
// Parse outline level (w:outlineLvl) - used for TOC generation
|
|
@@ -8997,7 +9978,29 @@ export class DocumentParser {
|
|
|
8997
9978
|
}
|
|
8998
9979
|
}
|
|
8999
9980
|
|
|
9000
|
-
// Parse
|
|
9981
|
+
// Parse divId (CT_PPrBase #32, §17.3.1.10) — numeric HTML div
|
|
9982
|
+
// association. Previously dropped on the style parser; the main
|
|
9983
|
+
// paragraph parser reads it at the pPrObj level but the style pPr
|
|
9984
|
+
// parser used string-based extraction and skipped both divId and
|
|
9985
|
+
// cnfStyle below.
|
|
9986
|
+
const divIdVal = parseStylePPrValAttr('w:divId');
|
|
9987
|
+
if (divIdVal !== undefined) {
|
|
9988
|
+
const parsedDivId = parseInt(divIdVal, 10);
|
|
9989
|
+
if (!isNaN(parsedDivId)) formatting.divId = parsedDivId;
|
|
9990
|
+
}
|
|
9991
|
+
|
|
9992
|
+
// Parse cnfStyle (CT_PPrBase #33, §17.3.1.8) — conditional formatting
|
|
9993
|
+
// bitmask string (12-char 0/1 sequence, e.g. "100000000100").
|
|
9994
|
+
const cnfStyleVal = parseStylePPrValAttr('w:cnfStyle');
|
|
9995
|
+
if (cnfStyleVal !== undefined) formatting.cnfStyle = cnfStyleVal;
|
|
9996
|
+
|
|
9997
|
+
// Parse paragraph borders (w:pBdr) per ECMA-376 Part 1 §17.3.1.24.
|
|
9998
|
+
// Covers the full CT_Border attribute set (§17.18.2): val, sz, space,
|
|
9999
|
+
// color, themeColor, themeTint, themeShade, shadow, frame. The style
|
|
10000
|
+
// *emitter* already round-trips all nine, so any style-pBdr authored
|
|
10001
|
+
// by Word with themed or shadow/frame attributes was silently flattened
|
|
10002
|
+
// here before this fix. Shadow/frame route through parseOnOffAttribute
|
|
10003
|
+
// so ST_OnOff literals ("on"/"off"/"1"/"0"/"true"/"false") resolve.
|
|
9001
10004
|
const pBdrXml = XMLParser.extractBetweenTags(pPrXml, '<w:pBdr>', '</w:pBdr>');
|
|
9002
10005
|
if (pBdrXml) {
|
|
9003
10006
|
const borders: any = {};
|
|
@@ -9011,11 +10014,21 @@ export class DocumentParser {
|
|
|
9011
10014
|
const size = XMLParser.extractAttribute(bTag, 'w:sz');
|
|
9012
10015
|
const space = XMLParser.extractAttribute(bTag, 'w:space');
|
|
9013
10016
|
const color = XMLParser.extractAttribute(bTag, 'w:color');
|
|
10017
|
+
const themeColor = XMLParser.extractAttribute(bTag, 'w:themeColor');
|
|
10018
|
+
const themeTint = XMLParser.extractAttribute(bTag, 'w:themeTint');
|
|
10019
|
+
const themeShade = XMLParser.extractAttribute(bTag, 'w:themeShade');
|
|
10020
|
+
const shadow = XMLParser.extractAttribute(bTag, 'w:shadow');
|
|
10021
|
+
const frame = XMLParser.extractAttribute(bTag, 'w:frame');
|
|
9014
10022
|
const border: any = {};
|
|
9015
10023
|
if (style) border.style = style;
|
|
9016
10024
|
if (size) border.size = parseInt(size, 10);
|
|
9017
10025
|
if (space) border.space = parseInt(space, 10);
|
|
9018
10026
|
if (color) border.color = color;
|
|
10027
|
+
if (themeColor) border.themeColor = themeColor;
|
|
10028
|
+
if (themeTint) border.themeTint = themeTint;
|
|
10029
|
+
if (themeShade) border.themeShade = themeShade;
|
|
10030
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
10031
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
9019
10032
|
if (Object.keys(border).length > 0) borders[type] = border;
|
|
9020
10033
|
}
|
|
9021
10034
|
}
|
|
@@ -9062,37 +10075,113 @@ export class DocumentParser {
|
|
|
9062
10075
|
private parseRunFormattingFromXml(rPrXml: string): RunFormatting {
|
|
9063
10076
|
const formatting: RunFormatting = {};
|
|
9064
10077
|
|
|
9065
|
-
//
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
10078
|
+
// CT_OnOff rPr children per ECMA-376 §17.3.2. Previously detected via
|
|
10079
|
+
// substring-include which hard-coded the flag to `true` whenever the
|
|
10080
|
+
// element appeared — silently flipping `<w:b w:val="0"/>` (explicit
|
|
10081
|
+
// override of a based-on style's bold) into an enabled flag, and
|
|
10082
|
+
// never setting the field to `false` for legitimate overrides.
|
|
10083
|
+
// Mirrors the pPr `parseStylePPrCtOnOff` helper introduced in
|
|
10084
|
+
// iteration 25 / 26.
|
|
10085
|
+
const parseStyleRPrCtOnOff = (tagName: string): boolean | undefined => {
|
|
10086
|
+
const el = XMLParser.extractSelfClosingTag(rPrXml, tagName);
|
|
10087
|
+
if (el === undefined) return undefined;
|
|
10088
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
10089
|
+
if (val === undefined) return true;
|
|
10090
|
+
return parseOnOffAttribute(val, true);
|
|
10091
|
+
};
|
|
10092
|
+
|
|
10093
|
+
const boldVal = parseStyleRPrCtOnOff('w:b');
|
|
10094
|
+
if (boldVal !== undefined) formatting.bold = boldVal;
|
|
9081
10095
|
|
|
9082
|
-
|
|
10096
|
+
const italicVal = parseStyleRPrCtOnOff('w:i');
|
|
10097
|
+
if (italicVal !== undefined) formatting.italic = italicVal;
|
|
10098
|
+
|
|
10099
|
+
const strikeVal = parseStyleRPrCtOnOff('w:strike');
|
|
10100
|
+
if (strikeVal !== undefined) formatting.strike = strikeVal;
|
|
10101
|
+
|
|
10102
|
+
const smallCapsVal = parseStyleRPrCtOnOff('w:smallCaps');
|
|
10103
|
+
if (smallCapsVal !== undefined) formatting.smallCaps = smallCapsVal;
|
|
10104
|
+
|
|
10105
|
+
const allCapsVal = parseStyleRPrCtOnOff('w:caps');
|
|
10106
|
+
if (allCapsVal !== undefined) formatting.allCaps = allCapsVal;
|
|
10107
|
+
|
|
10108
|
+
// Extended CT_OnOff run children per ECMA-376 §17.3.2. The style-level
|
|
10109
|
+
// rPr parser previously dropped all of these silently, so character
|
|
10110
|
+
// styles setting dstrike, outline, shadow, emboss, imprint, rtl,
|
|
10111
|
+
// vanish, noProof, snapToGrid, specVanish, webHidden, or complex-script
|
|
10112
|
+
// variants (bCs / iCs / cs) lost their overrides on programmatic save.
|
|
10113
|
+
const boldCsVal = parseStyleRPrCtOnOff('w:bCs');
|
|
10114
|
+
if (boldCsVal !== undefined) formatting.complexScriptBold = boldCsVal;
|
|
10115
|
+
|
|
10116
|
+
const italicCsVal = parseStyleRPrCtOnOff('w:iCs');
|
|
10117
|
+
if (italicCsVal !== undefined) formatting.complexScriptItalic = italicCsVal;
|
|
10118
|
+
|
|
10119
|
+
const csVal = parseStyleRPrCtOnOff('w:cs');
|
|
10120
|
+
if (csVal !== undefined) formatting.complexScript = csVal;
|
|
10121
|
+
|
|
10122
|
+
const dstrikeVal = parseStyleRPrCtOnOff('w:dstrike');
|
|
10123
|
+
if (dstrikeVal !== undefined) formatting.dstrike = dstrikeVal;
|
|
10124
|
+
|
|
10125
|
+
const outlineVal = parseStyleRPrCtOnOff('w:outline');
|
|
10126
|
+
if (outlineVal !== undefined) formatting.outline = outlineVal;
|
|
10127
|
+
|
|
10128
|
+
const shadowVal = parseStyleRPrCtOnOff('w:shadow');
|
|
10129
|
+
if (shadowVal !== undefined) formatting.shadow = shadowVal;
|
|
10130
|
+
|
|
10131
|
+
const embossVal = parseStyleRPrCtOnOff('w:emboss');
|
|
10132
|
+
if (embossVal !== undefined) formatting.emboss = embossVal;
|
|
10133
|
+
|
|
10134
|
+
const imprintVal = parseStyleRPrCtOnOff('w:imprint');
|
|
10135
|
+
if (imprintVal !== undefined) formatting.imprint = imprintVal;
|
|
10136
|
+
|
|
10137
|
+
const rtlVal = parseStyleRPrCtOnOff('w:rtl');
|
|
10138
|
+
if (rtlVal !== undefined) formatting.rtl = rtlVal;
|
|
10139
|
+
|
|
10140
|
+
const vanishVal = parseStyleRPrCtOnOff('w:vanish');
|
|
10141
|
+
if (vanishVal !== undefined) formatting.vanish = vanishVal;
|
|
10142
|
+
|
|
10143
|
+
const noProofVal = parseStyleRPrCtOnOff('w:noProof');
|
|
10144
|
+
if (noProofVal !== undefined) formatting.noProof = noProofVal;
|
|
10145
|
+
|
|
10146
|
+
const snapToGridVal = parseStyleRPrCtOnOff('w:snapToGrid');
|
|
10147
|
+
if (snapToGridVal !== undefined) formatting.snapToGrid = snapToGridVal;
|
|
10148
|
+
|
|
10149
|
+
const specVanishVal = parseStyleRPrCtOnOff('w:specVanish');
|
|
10150
|
+
if (specVanishVal !== undefined) formatting.specVanish = specVanishVal;
|
|
10151
|
+
|
|
10152
|
+
const webHiddenVal = parseStyleRPrCtOnOff('w:webHidden');
|
|
10153
|
+
if (webHiddenVal !== undefined) formatting.webHidden = webHiddenVal;
|
|
10154
|
+
|
|
10155
|
+
// Parse underline — all attributes per ECMA-376 §17.3.2.40.
|
|
10156
|
+
// Whitelist covers the full ST_Underline enumeration (18 values);
|
|
10157
|
+
// unknown / out-of-spec values fall through to `underline = true`
|
|
10158
|
+
// (underline enabled with default style) to match the main parser.
|
|
9083
10159
|
const uElement = XMLParser.extractSelfClosingTag(rPrXml, 'w:u');
|
|
9084
10160
|
if (uElement) {
|
|
9085
10161
|
const uTag = `<w:u${uElement}`;
|
|
9086
10162
|
const uVal = XMLParser.extractAttribute(uTag, 'w:val');
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
10163
|
+
const ST_UNDERLINE = new Set<string>([
|
|
10164
|
+
'single',
|
|
10165
|
+
'words',
|
|
10166
|
+
'double',
|
|
10167
|
+
'thick',
|
|
10168
|
+
'dotted',
|
|
10169
|
+
'dottedHeavy',
|
|
10170
|
+
'dash',
|
|
10171
|
+
'dashedHeavy',
|
|
10172
|
+
'dashLong',
|
|
10173
|
+
'dashLongHeavy',
|
|
10174
|
+
'dotDash',
|
|
10175
|
+
'dashDotHeavy',
|
|
10176
|
+
'dotDotDash',
|
|
10177
|
+
'dashDotDotHeavy',
|
|
10178
|
+
'wave',
|
|
10179
|
+
'wavyHeavy',
|
|
10180
|
+
'wavyDouble',
|
|
10181
|
+
'none',
|
|
10182
|
+
]);
|
|
10183
|
+
if (uVal !== undefined && ST_UNDERLINE.has(String(uVal))) {
|
|
10184
|
+
formatting.underline = String(uVal) as RunFormatting['underline'];
|
|
9096
10185
|
} else {
|
|
9097
10186
|
formatting.underline = true;
|
|
9098
10187
|
}
|
|
@@ -9100,7 +10189,8 @@ export class DocumentParser {
|
|
|
9100
10189
|
if (uColor) formatting.underlineColor = uColor;
|
|
9101
10190
|
const uThemeColor = XMLParser.extractAttribute(uTag, 'w:themeColor');
|
|
9102
10191
|
if (uThemeColor) {
|
|
9103
|
-
formatting.underlineThemeColor =
|
|
10192
|
+
formatting.underlineThemeColor =
|
|
10193
|
+
uThemeColor as import('../elements/Run.js').ThemeColorValue;
|
|
9104
10194
|
}
|
|
9105
10195
|
const uThemeTint = XMLParser.extractAttribute(uTag, 'w:themeTint');
|
|
9106
10196
|
if (uThemeTint) formatting.underlineThemeTint = parseInt(uThemeTint, 16);
|
|
@@ -9167,17 +10257,25 @@ export class DocumentParser {
|
|
|
9167
10257
|
}
|
|
9168
10258
|
}
|
|
9169
10259
|
|
|
9170
|
-
// Parse color (w:color) — all attributes per ECMA-376 §17.3.2.6
|
|
10260
|
+
// Parse color (w:color) — all attributes per ECMA-376 §17.3.2.6 / ST_HexColor
|
|
10261
|
+
// per §17.18.38. `w:val="auto"` is a valid ST_HexColorAuto sentinel that
|
|
10262
|
+
// tells Word to use the automatic/window text color; the previous parser
|
|
10263
|
+
// dropped it (only storing non-auto hex values), so a style-level rPr with
|
|
10264
|
+
// `<w:color w:val="auto"/>` silently lost that marker on round-trip and
|
|
10265
|
+
// the emitter defaulted to `"000000"` — changing the rendering of any
|
|
10266
|
+
// style that relied on the auto fallback. Preserve the literal "auto" so
|
|
10267
|
+
// it survives through emission. (Matches the object-format parser path
|
|
10268
|
+
// for direct-run rPr at parseRunFromObject line ~5210.)
|
|
9171
10269
|
const colorElement = XMLParser.extractSelfClosingTag(rPrXml, 'w:color');
|
|
9172
10270
|
if (colorElement) {
|
|
9173
10271
|
const colorTag = `<w:color${colorElement}`;
|
|
9174
10272
|
const val = XMLParser.extractAttribute(colorTag, 'w:val');
|
|
9175
|
-
if (val
|
|
10273
|
+
if (val) {
|
|
9176
10274
|
formatting.color = val;
|
|
9177
10275
|
}
|
|
9178
10276
|
const themeColor = XMLParser.extractAttribute(colorTag, 'w:themeColor');
|
|
9179
10277
|
if (themeColor) {
|
|
9180
|
-
formatting.themeColor = themeColor as import('../elements/Run').ThemeColorValue;
|
|
10278
|
+
formatting.themeColor = themeColor as import('../elements/Run.js').ThemeColorValue;
|
|
9181
10279
|
}
|
|
9182
10280
|
const themeTint = XMLParser.extractAttribute(colorTag, 'w:themeTint');
|
|
9183
10281
|
if (themeTint) {
|
|
@@ -9242,6 +10340,186 @@ export class DocumentParser {
|
|
|
9242
10340
|
formatting.shading = shading;
|
|
9243
10341
|
}
|
|
9244
10342
|
|
|
10343
|
+
// Character spacing (w:spacing §17.3.2.35, ST_SignedTwipsMeasure) —
|
|
10344
|
+
// previously dropped on the style parser; 0 and negative values are
|
|
10345
|
+
// valid per spec.
|
|
10346
|
+
const spacingEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:spacing');
|
|
10347
|
+
if (spacingEl !== undefined) {
|
|
10348
|
+
const val = XMLParser.extractAttribute(`<w:spacing${spacingEl}`, 'w:val');
|
|
10349
|
+
if (val !== undefined) {
|
|
10350
|
+
const n = parseInt(String(val), 10);
|
|
10351
|
+
if (!isNaN(n)) formatting.characterSpacing = n;
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
|
|
10355
|
+
// Vertical position (w:position §17.3.2.31, ST_SignedHpsMeasure).
|
|
10356
|
+
// 0 = baseline (explicit reset); negative = lowered; positive = raised.
|
|
10357
|
+
const positionEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:position');
|
|
10358
|
+
if (positionEl !== undefined) {
|
|
10359
|
+
const val = XMLParser.extractAttribute(`<w:position${positionEl}`, 'w:val');
|
|
10360
|
+
if (val !== undefined) {
|
|
10361
|
+
const n = parseInt(String(val), 10);
|
|
10362
|
+
if (!isNaN(n)) formatting.position = n;
|
|
10363
|
+
}
|
|
10364
|
+
}
|
|
10365
|
+
|
|
10366
|
+
// Kerning threshold (w:kern §17.3.2.20, ST_HpsMeasure). 0 = kern at
|
|
10367
|
+
// every size (no minimum font-size threshold).
|
|
10368
|
+
const kernEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:kern');
|
|
10369
|
+
if (kernEl !== undefined) {
|
|
10370
|
+
const val = XMLParser.extractAttribute(`<w:kern${kernEl}`, 'w:val');
|
|
10371
|
+
if (val !== undefined) {
|
|
10372
|
+
const n = parseInt(String(val), 10);
|
|
10373
|
+
if (!isNaN(n)) formatting.kerning = n;
|
|
10374
|
+
}
|
|
10375
|
+
}
|
|
10376
|
+
|
|
10377
|
+
// Language (w:lang §17.3.2.20, CT_Language). Single val → plain string;
|
|
10378
|
+
// multi-script (eastAsia and/or bidi present) → LanguageConfig object.
|
|
10379
|
+
const langEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:lang');
|
|
10380
|
+
if (langEl !== undefined) {
|
|
10381
|
+
const langTag = `<w:lang${langEl}`;
|
|
10382
|
+
const val = XMLParser.extractAttribute(langTag, 'w:val');
|
|
10383
|
+
const eastAsia = XMLParser.extractAttribute(langTag, 'w:eastAsia');
|
|
10384
|
+
const bidi = XMLParser.extractAttribute(langTag, 'w:bidi');
|
|
10385
|
+
if (eastAsia || bidi) {
|
|
10386
|
+
formatting.language = {
|
|
10387
|
+
val: val ? String(val) : undefined,
|
|
10388
|
+
eastAsia: eastAsia ? String(eastAsia) : undefined,
|
|
10389
|
+
bidi: bidi ? String(bidi) : undefined,
|
|
10390
|
+
};
|
|
10391
|
+
} else if (val) {
|
|
10392
|
+
formatting.language = String(val);
|
|
10393
|
+
}
|
|
10394
|
+
}
|
|
10395
|
+
|
|
10396
|
+
// Horizontal scaling (w:w §17.3.2.43, ST_TextScale — percentage,
|
|
10397
|
+
// min 1 per spec, so 0 is not valid and we keep a truthy check).
|
|
10398
|
+
const scaleEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:w');
|
|
10399
|
+
if (scaleEl !== undefined) {
|
|
10400
|
+
const val = XMLParser.extractAttribute(`<w:w${scaleEl}`, 'w:val');
|
|
10401
|
+
if (val) {
|
|
10402
|
+
const n = parseInt(String(val), 10);
|
|
10403
|
+
if (!isNaN(n)) formatting.scaling = n;
|
|
10404
|
+
}
|
|
10405
|
+
}
|
|
10406
|
+
|
|
10407
|
+
// Emphasis mark (w:em §17.3.2.13, ST_Em — "dot"/"comma"/"circle"/
|
|
10408
|
+
// "underDot"/"none"). Commonly paired with East Asian typography.
|
|
10409
|
+
const emEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:em');
|
|
10410
|
+
if (emEl !== undefined) {
|
|
10411
|
+
const val = XMLParser.extractAttribute(`<w:em${emEl}`, 'w:val');
|
|
10412
|
+
if (val) {
|
|
10413
|
+
formatting.emphasis = String(val) as RunFormatting['emphasis'];
|
|
10414
|
+
}
|
|
10415
|
+
}
|
|
10416
|
+
|
|
10417
|
+
// Animated text effect (w:effect §17.3.2.12, ST_TextEffect —
|
|
10418
|
+
// "blinkBackground"/"lights"/"antsBlack"/"antsRed"/"shimmer"/"sparkle"/
|
|
10419
|
+
// "none"). Legacy feature but still valid per schema.
|
|
10420
|
+
const effectEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:effect');
|
|
10421
|
+
if (effectEl !== undefined) {
|
|
10422
|
+
const val = XMLParser.extractAttribute(`<w:effect${effectEl}`, 'w:val');
|
|
10423
|
+
if (val) {
|
|
10424
|
+
formatting.effect = String(val) as RunFormatting['effect'];
|
|
10425
|
+
}
|
|
10426
|
+
}
|
|
10427
|
+
|
|
10428
|
+
// Text border (w:bdr §17.3.2.5) — character/run border. Full CT_Border
|
|
10429
|
+
// attribute set (§17.18.2): val / sz / space / color / themeColor /
|
|
10430
|
+
// themeTint / themeShade / shadow / frame.
|
|
10431
|
+
const bdrEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:bdr');
|
|
10432
|
+
if (bdrEl !== undefined) {
|
|
10433
|
+
const bdrTag = `<w:bdr${bdrEl}`;
|
|
10434
|
+
const border: {
|
|
10435
|
+
style?: string;
|
|
10436
|
+
size?: number;
|
|
10437
|
+
color?: string;
|
|
10438
|
+
space?: number;
|
|
10439
|
+
themeColor?: string;
|
|
10440
|
+
themeTint?: string;
|
|
10441
|
+
themeShade?: string;
|
|
10442
|
+
shadow?: boolean;
|
|
10443
|
+
frame?: boolean;
|
|
10444
|
+
} = {};
|
|
10445
|
+
const val = XMLParser.extractAttribute(bdrTag, 'w:val');
|
|
10446
|
+
if (val) border.style = String(val);
|
|
10447
|
+
const sz = XMLParser.extractAttribute(bdrTag, 'w:sz');
|
|
10448
|
+
if (sz !== undefined) {
|
|
10449
|
+
const n = parseInt(String(sz), 10);
|
|
10450
|
+
if (!isNaN(n)) border.size = n;
|
|
10451
|
+
}
|
|
10452
|
+
const color = XMLParser.extractAttribute(bdrTag, 'w:color');
|
|
10453
|
+
if (color) border.color = String(color);
|
|
10454
|
+
const space = XMLParser.extractAttribute(bdrTag, 'w:space');
|
|
10455
|
+
if (space !== undefined) {
|
|
10456
|
+
const n = parseInt(String(space), 10);
|
|
10457
|
+
if (!isNaN(n)) border.space = n;
|
|
10458
|
+
}
|
|
10459
|
+
const themeColor = XMLParser.extractAttribute(bdrTag, 'w:themeColor');
|
|
10460
|
+
if (themeColor) border.themeColor = String(themeColor);
|
|
10461
|
+
const themeTint = XMLParser.extractAttribute(bdrTag, 'w:themeTint');
|
|
10462
|
+
if (themeTint) border.themeTint = String(themeTint);
|
|
10463
|
+
const themeShade = XMLParser.extractAttribute(bdrTag, 'w:themeShade');
|
|
10464
|
+
if (themeShade) border.themeShade = String(themeShade);
|
|
10465
|
+
const shadow = XMLParser.extractAttribute(bdrTag, 'w:shadow');
|
|
10466
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
10467
|
+
const frame = XMLParser.extractAttribute(bdrTag, 'w:frame');
|
|
10468
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
10469
|
+
if (Object.keys(border).length > 0) {
|
|
10470
|
+
formatting.border = border as RunFormatting['border'];
|
|
10471
|
+
}
|
|
10472
|
+
}
|
|
10473
|
+
|
|
10474
|
+
// Manual run width (w:fitText §17.3.2.15). Value is twips; 0 is
|
|
10475
|
+
// technically representable as "explicit zero" — use `!== undefined`.
|
|
10476
|
+
const fitTextEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:fitText');
|
|
10477
|
+
if (fitTextEl !== undefined) {
|
|
10478
|
+
const val = XMLParser.extractAttribute(`<w:fitText${fitTextEl}`, 'w:val');
|
|
10479
|
+
if (val !== undefined) {
|
|
10480
|
+
const n = parseInt(String(val), 10);
|
|
10481
|
+
if (!isNaN(n)) formatting.fitText = n;
|
|
10482
|
+
}
|
|
10483
|
+
}
|
|
10484
|
+
|
|
10485
|
+
// East Asian layout (w:eastAsianLayout §17.3.2.10) — combined
|
|
10486
|
+
// characters / vertical text / compression attributes.
|
|
10487
|
+
const ealEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:eastAsianLayout');
|
|
10488
|
+
if (ealEl !== undefined) {
|
|
10489
|
+
const ealTag = `<w:eastAsianLayout${ealEl}`;
|
|
10490
|
+
const layout: Partial<{
|
|
10491
|
+
id: number;
|
|
10492
|
+
vert: boolean;
|
|
10493
|
+
vertCompress: boolean;
|
|
10494
|
+
combine: boolean;
|
|
10495
|
+
combineBrackets: 'none' | 'round' | 'square' | 'angle' | 'curly';
|
|
10496
|
+
}> = {};
|
|
10497
|
+
const id = XMLParser.extractAttribute(ealTag, 'w:id');
|
|
10498
|
+
if (id !== undefined) {
|
|
10499
|
+
const n = Number(id);
|
|
10500
|
+
if (!isNaN(n)) layout.id = n;
|
|
10501
|
+
}
|
|
10502
|
+
const vert = XMLParser.extractAttribute(ealTag, 'w:vert');
|
|
10503
|
+
if (vert !== undefined && parseOnOffAttribute(vert, true)) layout.vert = true;
|
|
10504
|
+
const vertCompress = XMLParser.extractAttribute(ealTag, 'w:vertCompress');
|
|
10505
|
+
if (vertCompress !== undefined && parseOnOffAttribute(vertCompress, true))
|
|
10506
|
+
layout.vertCompress = true;
|
|
10507
|
+
const combine = XMLParser.extractAttribute(ealTag, 'w:combine');
|
|
10508
|
+
if (combine !== undefined && parseOnOffAttribute(combine, true)) layout.combine = true;
|
|
10509
|
+
const combineBrackets = XMLParser.extractAttribute(ealTag, 'w:combineBrackets');
|
|
10510
|
+
if (combineBrackets) {
|
|
10511
|
+
layout.combineBrackets = String(combineBrackets) as
|
|
10512
|
+
| 'none'
|
|
10513
|
+
| 'round'
|
|
10514
|
+
| 'square'
|
|
10515
|
+
| 'angle'
|
|
10516
|
+
| 'curly';
|
|
10517
|
+
}
|
|
10518
|
+
if (Object.keys(layout).length > 0) {
|
|
10519
|
+
formatting.eastAsianLayout = layout as RunFormatting['eastAsianLayout'];
|
|
10520
|
+
}
|
|
10521
|
+
}
|
|
10522
|
+
|
|
9245
10523
|
return formatting;
|
|
9246
10524
|
}
|
|
9247
10525
|
|
|
@@ -9252,8 +10530,8 @@ export class DocumentParser {
|
|
|
9252
10530
|
*/
|
|
9253
10531
|
private parseTableStyleProperties(
|
|
9254
10532
|
styleXml: string
|
|
9255
|
-
): import('../formatting/Style').TableStyleProperties {
|
|
9256
|
-
const tableStyle: import('../formatting/Style').TableStyleProperties = {};
|
|
10533
|
+
): import('../formatting/Style.js').TableStyleProperties {
|
|
10534
|
+
const tableStyle: import('../formatting/Style.js').TableStyleProperties = {};
|
|
9257
10535
|
|
|
9258
10536
|
// Parse tblPr (table properties)
|
|
9259
10537
|
const tblPrXml = XMLParser.extractBetweenTags(styleXml, '<w:tblPr>', '</w:tblPr>');
|
|
@@ -9306,8 +10584,8 @@ export class DocumentParser {
|
|
|
9306
10584
|
*/
|
|
9307
10585
|
private parseTableFormattingFromXml(
|
|
9308
10586
|
tblPrXml: string
|
|
9309
|
-
): import('../formatting/Style').TableStyleFormatting {
|
|
9310
|
-
const formatting: import('../formatting/Style').TableStyleFormatting = {};
|
|
10587
|
+
): import('../formatting/Style.js').TableStyleFormatting {
|
|
10588
|
+
const formatting: import('../formatting/Style.js').TableStyleFormatting = {};
|
|
9311
10589
|
|
|
9312
10590
|
// Parse indent (w:tblInd) — preserve w:type per ECMA-376 ST_TblWidth
|
|
9313
10591
|
if (tblPrXml.includes('<w:tblInd')) {
|
|
@@ -9320,18 +10598,27 @@ export class DocumentParser {
|
|
|
9320
10598
|
}
|
|
9321
10599
|
const type = XMLParser.extractAttribute(tblIndTag, 'w:type');
|
|
9322
10600
|
if (type) {
|
|
9323
|
-
formatting.indentType = type as import('../elements/Table').TableWidthType;
|
|
10601
|
+
formatting.indentType = type as import('../elements/Table.js').TableWidthType;
|
|
9324
10602
|
}
|
|
9325
10603
|
}
|
|
9326
10604
|
}
|
|
9327
10605
|
|
|
9328
|
-
// Parse alignment
|
|
10606
|
+
// Parse alignment — ST_JcTable has 5 values (start, end, center, left,
|
|
10607
|
+
// right) per ECMA-376 §17.18.45. The whitelist previously only accepted
|
|
10608
|
+
// the three legacy LTR-centric values, silently dropping `start` / `end`
|
|
10609
|
+
// (the bidi-aware defaults a modern authoring tool emits).
|
|
9329
10610
|
if (tblPrXml.includes('<w:jc')) {
|
|
9330
10611
|
const tag = XMLParser.extractSelfClosingTag(tblPrXml, 'w:jc');
|
|
9331
10612
|
if (tag) {
|
|
9332
10613
|
const val = XMLParser.extractAttribute(`<w:jc${tag}`, 'w:val');
|
|
9333
|
-
if (
|
|
9334
|
-
|
|
10614
|
+
if (
|
|
10615
|
+
val === 'left' ||
|
|
10616
|
+
val === 'center' ||
|
|
10617
|
+
val === 'right' ||
|
|
10618
|
+
val === 'start' ||
|
|
10619
|
+
val === 'end'
|
|
10620
|
+
) {
|
|
10621
|
+
formatting.alignment = val as import('../formatting/Style.js').TableAlignment;
|
|
9335
10622
|
}
|
|
9336
10623
|
}
|
|
9337
10624
|
}
|
|
@@ -9372,8 +10659,8 @@ export class DocumentParser {
|
|
|
9372
10659
|
*/
|
|
9373
10660
|
private parseTableCellFormattingFromXml(
|
|
9374
10661
|
tcPrXml: string
|
|
9375
|
-
): import('../formatting/Style').TableCellStyleFormatting {
|
|
9376
|
-
const formatting: import('../formatting/Style').TableCellStyleFormatting = {};
|
|
10662
|
+
): import('../formatting/Style.js').TableCellStyleFormatting {
|
|
10663
|
+
const formatting: import('../formatting/Style.js').TableCellStyleFormatting = {};
|
|
9377
10664
|
|
|
9378
10665
|
// Parse borders
|
|
9379
10666
|
const bordersXml = XMLParser.extractBetweenTags(tcPrXml, '<w:tcBorders>', '</w:tcBorders>');
|
|
@@ -9381,7 +10668,7 @@ export class DocumentParser {
|
|
|
9381
10668
|
formatting.borders = this.parseBordersFromXml(
|
|
9382
10669
|
bordersXml,
|
|
9383
10670
|
true
|
|
9384
|
-
) as import('../formatting/Style').CellBorders;
|
|
10671
|
+
) as import('../formatting/Style.js').CellBorders;
|
|
9385
10672
|
}
|
|
9386
10673
|
|
|
9387
10674
|
// Parse shading
|
|
@@ -9395,12 +10682,15 @@ export class DocumentParser {
|
|
|
9395
10682
|
formatting.margins = this.parseCellMarginsFromXml(marginXml);
|
|
9396
10683
|
}
|
|
9397
10684
|
|
|
9398
|
-
// Parse vertical alignment
|
|
10685
|
+
// Parse vertical alignment — ST_VerticalJc has four values
|
|
10686
|
+
// (top / center / both / bottom) per ECMA-376 §17.18.101. Previously
|
|
10687
|
+
// the whitelist only accepted the first three, silently dropping
|
|
10688
|
+
// `<w:vAlign w:val="both"/>` on cell styles.
|
|
9399
10689
|
if (tcPrXml.includes('<w:vAlign')) {
|
|
9400
10690
|
const tag = XMLParser.extractSelfClosingTag(tcPrXml, 'w:vAlign');
|
|
9401
10691
|
if (tag) {
|
|
9402
10692
|
const val = XMLParser.extractAttribute(`<w:vAlign${tag}`, 'w:val');
|
|
9403
|
-
if (val === 'top' || val === 'center' || val === 'bottom') {
|
|
10693
|
+
if (val === 'top' || val === 'center' || val === 'both' || val === 'bottom') {
|
|
9404
10694
|
formatting.verticalAlignment = val;
|
|
9405
10695
|
}
|
|
9406
10696
|
}
|
|
@@ -9414,8 +10704,8 @@ export class DocumentParser {
|
|
|
9414
10704
|
*/
|
|
9415
10705
|
private parseTableRowFormattingFromXml(
|
|
9416
10706
|
trPrXml: string
|
|
9417
|
-
): import('../formatting/Style').TableRowStyleFormatting {
|
|
9418
|
-
const formatting: import('../formatting/Style').TableRowStyleFormatting = {};
|
|
10707
|
+
): import('../formatting/Style.js').TableRowStyleFormatting {
|
|
10708
|
+
const formatting: import('../formatting/Style.js').TableRowStyleFormatting = {};
|
|
9419
10709
|
|
|
9420
10710
|
// Parse height
|
|
9421
10711
|
if (trPrXml.includes('<w:trHeight')) {
|
|
@@ -9432,15 +10722,25 @@ export class DocumentParser {
|
|
|
9432
10722
|
}
|
|
9433
10723
|
}
|
|
9434
10724
|
|
|
9435
|
-
// Parse cantSplit
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
10725
|
+
// Parse cantSplit / tblHeader — both OnOffOnlyType (§17.4.6, §17.4.50).
|
|
10726
|
+
// Previous substring-include detection hard-coded the flag to `true`
|
|
10727
|
+
// whenever the element appeared, silently flipping an explicit-off
|
|
10728
|
+
// override (e.g., a tblStylePr conditional un-splitting a header row)
|
|
10729
|
+
// into an enabled flag. Reuse parseOnOffAttribute so bare, "on", and
|
|
10730
|
+
// "off" all map correctly, and so absent stays undefined.
|
|
10731
|
+
const parseTrPrOnOffOnly = (tagName: string): boolean | undefined => {
|
|
10732
|
+
const el = XMLParser.extractSelfClosingTag(trPrXml, tagName);
|
|
10733
|
+
if (el === undefined) return undefined;
|
|
10734
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
10735
|
+
if (val === undefined) return true;
|
|
10736
|
+
return parseOnOffAttribute(val, true);
|
|
10737
|
+
};
|
|
9439
10738
|
|
|
9440
|
-
|
|
9441
|
-
if (
|
|
9442
|
-
|
|
9443
|
-
|
|
10739
|
+
const cantSplitVal = parseTrPrOnOffOnly('w:cantSplit');
|
|
10740
|
+
if (cantSplitVal !== undefined) formatting.cantSplit = cantSplitVal;
|
|
10741
|
+
|
|
10742
|
+
const tblHeaderVal = parseTrPrOnOffOnly('w:tblHeader');
|
|
10743
|
+
if (tblHeaderVal !== undefined) formatting.isHeader = tblHeaderVal;
|
|
9444
10744
|
|
|
9445
10745
|
return formatting;
|
|
9446
10746
|
}
|
|
@@ -9450,8 +10750,8 @@ export class DocumentParser {
|
|
|
9450
10750
|
*/
|
|
9451
10751
|
private parseConditionalFormattingFromXml(
|
|
9452
10752
|
styleXml: string
|
|
9453
|
-
): import('../formatting/Style').ConditionalTableFormatting[] | undefined {
|
|
9454
|
-
const conditionalFormatting: import('../formatting/Style').ConditionalTableFormatting[] = [];
|
|
10753
|
+
): import('../formatting/Style.js').ConditionalTableFormatting[] | undefined {
|
|
10754
|
+
const conditionalFormatting: import('../formatting/Style.js').ConditionalTableFormatting[] = [];
|
|
9455
10755
|
|
|
9456
10756
|
// Find all tblStylePr elements
|
|
9457
10757
|
let searchFrom = 0;
|
|
@@ -9467,8 +10767,8 @@ export class DocumentParser {
|
|
|
9467
10767
|
// Extract type attribute
|
|
9468
10768
|
const typeAttr = XMLParser.extractAttribute(tblStylePrXml, 'w:type');
|
|
9469
10769
|
if (typeAttr) {
|
|
9470
|
-
const conditional: import('../formatting/Style').ConditionalTableFormatting = {
|
|
9471
|
-
type: typeAttr as import('../formatting/Style').ConditionalFormattingType,
|
|
10770
|
+
const conditional: import('../formatting/Style.js').ConditionalTableFormatting = {
|
|
10771
|
+
type: typeAttr as import('../formatting/Style.js').ConditionalFormattingType,
|
|
9472
10772
|
};
|
|
9473
10773
|
|
|
9474
10774
|
// Parse pPr
|
|
@@ -9518,29 +10818,58 @@ export class DocumentParser {
|
|
|
9518
10818
|
private parseBordersFromXml(
|
|
9519
10819
|
bordersXml: string,
|
|
9520
10820
|
includeDiagonals: boolean
|
|
9521
|
-
): import('../formatting/Style').TableBorders | import('../formatting/Style').CellBorders {
|
|
10821
|
+
): import('../formatting/Style.js').TableBorders | import('../formatting/Style.js').CellBorders {
|
|
9522
10822
|
const borders: any = {};
|
|
9523
10823
|
|
|
10824
|
+
// Local helper so both the main-side loop and the diagonal loop share
|
|
10825
|
+
// the full CT_Border attribute set (§17.18.2): val / sz / space / color
|
|
10826
|
+
// / themeColor / themeTint / themeShade / shadow / frame. Previously
|
|
10827
|
+
// this parser only extracted the four "basic" attrs, so themed borders
|
|
10828
|
+
// and shadow/frame flags on page/table/cell borders were silently
|
|
10829
|
+
// dropped on every load → save round-trip.
|
|
10830
|
+
const parseBorderAttrs = (
|
|
10831
|
+
type: string
|
|
10832
|
+
): import('../formatting/Style.js').BorderProperties | null => {
|
|
10833
|
+
const tag = XMLParser.extractSelfClosingTag(bordersXml, `w:${type}`);
|
|
10834
|
+
if (!tag) return null;
|
|
10835
|
+
const ref = `<w:${type}${tag}`;
|
|
10836
|
+
const border: import('../formatting/Style.js').BorderProperties = {};
|
|
10837
|
+
const style = XMLParser.extractAttribute(ref, 'w:val');
|
|
10838
|
+
const size = XMLParser.extractAttribute(ref, 'w:sz');
|
|
10839
|
+
const space = XMLParser.extractAttribute(ref, 'w:space');
|
|
10840
|
+
const color = XMLParser.extractAttribute(ref, 'w:color');
|
|
10841
|
+
const themeColor = XMLParser.extractAttribute(ref, 'w:themeColor');
|
|
10842
|
+
const themeTint = XMLParser.extractAttribute(ref, 'w:themeTint');
|
|
10843
|
+
const themeShade = XMLParser.extractAttribute(ref, 'w:themeShade');
|
|
10844
|
+
const shadow = XMLParser.extractAttribute(ref, 'w:shadow');
|
|
10845
|
+
const frame = XMLParser.extractAttribute(ref, 'w:frame');
|
|
10846
|
+
if (style) border.style = style as any;
|
|
10847
|
+
if (size) border.size = parseInt(size, 10);
|
|
10848
|
+
if (space) border.space = parseInt(space, 10);
|
|
10849
|
+
if (color) border.color = color;
|
|
10850
|
+
if (themeColor) (border as any).themeColor = themeColor;
|
|
10851
|
+
if (themeTint) (border as any).themeTint = themeTint;
|
|
10852
|
+
if (themeShade) (border as any).themeShade = themeShade;
|
|
10853
|
+
if (shadow !== undefined) (border as any).shadow = parseOnOffAttribute(shadow, true);
|
|
10854
|
+
if (frame !== undefined) (border as any).frame = parseOnOffAttribute(frame, true);
|
|
10855
|
+
return Object.keys(border).length > 0 ? border : null;
|
|
10856
|
+
};
|
|
10857
|
+
|
|
10858
|
+
// Per ECMA-376 §17.4.40 CT_TblBorders and §17.4.66 CT_TcBorders the
|
|
10859
|
+
// left / right borders have bidi-aware aliases `w:start` / `w:end`.
|
|
10860
|
+
// Modern authoring tools (Word 2013+, Google Docs) emit the bidi-
|
|
10861
|
+
// aware form by default — prefer those over the legacy `w:left` /
|
|
10862
|
+
// `w:right` so bidi-authored tables round-trip their side borders
|
|
10863
|
+
// (the internal model stores under the left/right keys, matching
|
|
10864
|
+
// the emitter).
|
|
9524
10865
|
const borderTypes = ['top', 'bottom', 'left', 'right', 'insideH', 'insideV'];
|
|
9525
10866
|
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
|
-
}
|
|
10867
|
+
// For left/right: prefer bidi-aware start/end alias if present.
|
|
10868
|
+
const alias = type === 'left' ? 'start' : type === 'right' ? 'end' : type;
|
|
10869
|
+
const tagNameToRead = bordersXml.includes(`<w:${alias}`) ? alias : type;
|
|
10870
|
+
if (bordersXml.includes(`<w:${tagNameToRead}`)) {
|
|
10871
|
+
const border = parseBorderAttrs(tagNameToRead);
|
|
10872
|
+
if (border) borders[type] = border;
|
|
9544
10873
|
}
|
|
9545
10874
|
}
|
|
9546
10875
|
|
|
@@ -9549,23 +10878,8 @@ export class DocumentParser {
|
|
|
9549
10878
|
const diagonalTypes = ['tl2br', 'tr2bl'];
|
|
9550
10879
|
for (const type of diagonalTypes) {
|
|
9551
10880
|
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
|
-
}
|
|
10881
|
+
const border = parseBorderAttrs(type);
|
|
10882
|
+
if (border) borders[type] = border;
|
|
9569
10883
|
}
|
|
9570
10884
|
}
|
|
9571
10885
|
}
|
|
@@ -9578,16 +10892,25 @@ export class DocumentParser {
|
|
|
9578
10892
|
* Extracts all 9 ECMA-376 shading attributes including theme colors.
|
|
9579
10893
|
*/
|
|
9580
10894
|
private parseShadingFromObj(shd: any): ShadingConfig | undefined {
|
|
10895
|
+
// Per ECMA-376 §17.3.1.32 CT_Shd, every string-typed attribute
|
|
10896
|
+
// (ST_UcharHexNumber tint/shade, ST_ThemeColor theme refs,
|
|
10897
|
+
// ST_HexColor fill/color, ST_Shd pattern) can be purely numeric in
|
|
10898
|
+
// hex form — "80", "00", "FF", "80000000", etc. XMLParser's
|
|
10899
|
+
// `parseAttributeValue: true` coerces purely-digit hex strings like
|
|
10900
|
+
// "80" to the JS number 80, violating the `string` type on
|
|
10901
|
+
// ShadingConfig. Previously stored values leaked downstream as
|
|
10902
|
+
// numbers (e.g. `.toUpperCase()` would throw); cast every attribute
|
|
10903
|
+
// through `String(...)` so the declared-type contract holds.
|
|
9581
10904
|
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'];
|
|
10905
|
+
if (shd['@_w:val']) shading.pattern = String(shd['@_w:val']) as ShadingConfig['pattern'];
|
|
10906
|
+
if (shd['@_w:fill']) shading.fill = String(shd['@_w:fill']);
|
|
10907
|
+
if (shd['@_w:color']) shading.color = String(shd['@_w:color']);
|
|
10908
|
+
if (shd['@_w:themeFill']) shading.themeFill = String(shd['@_w:themeFill']);
|
|
10909
|
+
if (shd['@_w:themeColor']) shading.themeColor = String(shd['@_w:themeColor']);
|
|
10910
|
+
if (shd['@_w:themeFillTint']) shading.themeFillTint = String(shd['@_w:themeFillTint']);
|
|
10911
|
+
if (shd['@_w:themeFillShade']) shading.themeFillShade = String(shd['@_w:themeFillShade']);
|
|
10912
|
+
if (shd['@_w:themeTint']) shading.themeTint = String(shd['@_w:themeTint']);
|
|
10913
|
+
if (shd['@_w:themeShade']) shading.themeShade = String(shd['@_w:themeShade']);
|
|
9591
10914
|
return Object.keys(shading).length > 0 ? shading : undefined;
|
|
9592
10915
|
}
|
|
9593
10916
|
|
|
@@ -9628,8 +10951,8 @@ export class DocumentParser {
|
|
|
9628
10951
|
*/
|
|
9629
10952
|
private parseCellMarginsFromXml(
|
|
9630
10953
|
marginXml: string
|
|
9631
|
-
): import('../formatting/Style').CellMargins | undefined {
|
|
9632
|
-
const margins: import('../formatting/Style').CellMargins = {};
|
|
10954
|
+
): import('../formatting/Style.js').CellMargins | undefined {
|
|
10955
|
+
const margins: import('../formatting/Style.js').CellMargins = {};
|
|
9633
10956
|
|
|
9634
10957
|
// Parse top and bottom directly
|
|
9635
10958
|
for (const type of ['top', 'bottom'] as const) {
|
|
@@ -9894,23 +11217,23 @@ export class DocumentParser {
|
|
|
9894
11217
|
imageManager: ImageManager
|
|
9895
11218
|
): Promise<{
|
|
9896
11219
|
headers: {
|
|
9897
|
-
header: import('../elements/Header').Header;
|
|
11220
|
+
header: import('../elements/Header.js').Header;
|
|
9898
11221
|
relationshipId: string;
|
|
9899
11222
|
filename: string;
|
|
9900
11223
|
}[];
|
|
9901
11224
|
footers: {
|
|
9902
|
-
footer: import('../elements/Footer').Footer;
|
|
11225
|
+
footer: import('../elements/Footer.js').Footer;
|
|
9903
11226
|
relationshipId: string;
|
|
9904
11227
|
filename: string;
|
|
9905
11228
|
}[];
|
|
9906
11229
|
}> {
|
|
9907
11230
|
const headers: {
|
|
9908
|
-
header: import('../elements/Header').Header;
|
|
11231
|
+
header: import('../elements/Header.js').Header;
|
|
9909
11232
|
relationshipId: string;
|
|
9910
11233
|
filename: string;
|
|
9911
11234
|
}[] = [];
|
|
9912
11235
|
const footers: {
|
|
9913
|
-
footer: import('../elements/Footer').Footer;
|
|
11236
|
+
footer: import('../elements/Footer.js').Footer;
|
|
9914
11237
|
relationshipId: string;
|
|
9915
11238
|
filename: string;
|
|
9916
11239
|
}[] = [];
|
|
@@ -9924,7 +11247,7 @@ export class DocumentParser {
|
|
|
9924
11247
|
// Parse headers
|
|
9925
11248
|
// Track already-parsed headers by rId to avoid creating duplicates
|
|
9926
11249
|
// when multiple section property types (default, first, even) reference the same header file
|
|
9927
|
-
const parsedHeadersByRId = new Map<string, import('../elements/Header').Header>();
|
|
11250
|
+
const parsedHeadersByRId = new Map<string, import('../elements/Header.js').Header>();
|
|
9928
11251
|
|
|
9929
11252
|
if (sectionProps.headers) {
|
|
9930
11253
|
for (const [type, rId] of Object.entries(sectionProps.headers)) {
|
|
@@ -9987,7 +11310,7 @@ export class DocumentParser {
|
|
|
9987
11310
|
// Parse footers
|
|
9988
11311
|
// Track already-parsed footers by rId to avoid creating duplicates
|
|
9989
11312
|
// when multiple section property types (default, first, even) reference the same footer file
|
|
9990
|
-
const parsedFootersByRId = new Map<string, import('../elements/Footer').Footer>();
|
|
11313
|
+
const parsedFootersByRId = new Map<string, import('../elements/Footer.js').Footer>();
|
|
9991
11314
|
|
|
9992
11315
|
if (sectionProps.footers) {
|
|
9993
11316
|
for (const [type, rId] of Object.entries(sectionProps.footers)) {
|
|
@@ -10176,29 +11499,42 @@ export class DocumentParser {
|
|
|
10176
11499
|
|
|
10177
11500
|
// Table-level properties (w:tblPr context)
|
|
10178
11501
|
if (propsObj['w:tblStyle']) {
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
11502
|
+
// w:tblStyle w:val is ST_String (§17.7.4.62). XMLParser coerces
|
|
11503
|
+
// purely-numeric style IDs (e.g. "2025") to numbers; cast so the
|
|
11504
|
+
// declared `string` contract holds on tracked-change history.
|
|
11505
|
+
const v = propsObj['w:tblStyle']['@_w:val'];
|
|
11506
|
+
result.style = v !== undefined && v !== null ? String(v) : '';
|
|
11507
|
+
}
|
|
11508
|
+
// tblpPr (floating table position) — mirror main-path zero-value
|
|
11509
|
+
// preservation. The tblPrChange emitter re-emits position via
|
|
11510
|
+
// `!== undefined`, so dropping zero-valued tracked "previous"
|
|
11511
|
+
// positions here lost them silently on round-trip.
|
|
10182
11512
|
if (propsObj['w:tblpPr']) {
|
|
10183
11513
|
const tblpPr = propsObj['w:tblpPr'];
|
|
10184
11514
|
const pos: any = {};
|
|
10185
|
-
if (tblpPr['@_w:tblpX']) pos.x =
|
|
10186
|
-
if (tblpPr['@_w:tblpY']) pos.y =
|
|
11515
|
+
if (isExplicitlySet(tblpPr['@_w:tblpX'])) pos.x = safeParseInt(tblpPr['@_w:tblpX']);
|
|
11516
|
+
if (isExplicitlySet(tblpPr['@_w:tblpY'])) pos.y = safeParseInt(tblpPr['@_w:tblpY']);
|
|
10187
11517
|
if (tblpPr['@_w:horzAnchor']) pos.horizontalAnchor = tblpPr['@_w:horzAnchor'];
|
|
10188
11518
|
if (tblpPr['@_w:vertAnchor']) pos.verticalAnchor = tblpPr['@_w:vertAnchor'];
|
|
10189
|
-
if (tblpPr['@_w:leftFromText'])
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
if (tblpPr['@_w:
|
|
10193
|
-
|
|
10194
|
-
|
|
11519
|
+
if (isExplicitlySet(tblpPr['@_w:leftFromText'])) {
|
|
11520
|
+
pos.leftFromText = safeParseInt(tblpPr['@_w:leftFromText']);
|
|
11521
|
+
}
|
|
11522
|
+
if (isExplicitlySet(tblpPr['@_w:rightFromText'])) {
|
|
11523
|
+
pos.rightFromText = safeParseInt(tblpPr['@_w:rightFromText']);
|
|
11524
|
+
}
|
|
11525
|
+
if (isExplicitlySet(tblpPr['@_w:topFromText'])) {
|
|
11526
|
+
pos.topFromText = safeParseInt(tblpPr['@_w:topFromText']);
|
|
11527
|
+
}
|
|
11528
|
+
if (isExplicitlySet(tblpPr['@_w:bottomFromText'])) {
|
|
11529
|
+
pos.bottomFromText = safeParseInt(tblpPr['@_w:bottomFromText']);
|
|
11530
|
+
}
|
|
10195
11531
|
if (Object.keys(pos).length > 0) result.position = pos;
|
|
10196
11532
|
}
|
|
10197
11533
|
if (propsObj['w:tblOverlap']) {
|
|
10198
11534
|
result.overlap = propsObj['w:tblOverlap']['@_w:val'];
|
|
10199
11535
|
}
|
|
10200
11536
|
if (propsObj['w:bidiVisual']) {
|
|
10201
|
-
result.bidiVisual =
|
|
11537
|
+
result.bidiVisual = parseOoxmlBoolean(propsObj['w:bidiVisual']);
|
|
10202
11538
|
}
|
|
10203
11539
|
if (propsObj['w:tblStyleRowBandSize']) {
|
|
10204
11540
|
result.tblStyleRowBandSize = parseInt(
|
|
@@ -10244,21 +11580,55 @@ export class DocumentParser {
|
|
|
10244
11580
|
const borders: any = {};
|
|
10245
11581
|
const bordersObj = propsObj['w:tblBorders'];
|
|
10246
11582
|
for (const side of ['top', 'bottom', 'left', 'right', 'insideH', 'insideV']) {
|
|
10247
|
-
|
|
10248
|
-
|
|
11583
|
+
// Prefer bidi-aware w:start/w:end aliases over legacy w:left/
|
|
11584
|
+
// w:right (ECMA-376 §17.4.40 CT_TblBorders). Same pattern as
|
|
11585
|
+
// the main table borders parser — the bidi-aware form is the
|
|
11586
|
+
// preferred modern spelling.
|
|
11587
|
+
const aliasKey = side === 'left' ? 'w:start' : side === 'right' ? 'w:end' : undefined;
|
|
11588
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${side}`];
|
|
11589
|
+
if (borderObj) {
|
|
11590
|
+
borders[side] = this.parseBorderElement(borderObj);
|
|
10249
11591
|
}
|
|
10250
11592
|
}
|
|
10251
11593
|
if (Object.keys(borders).length > 0) result.borders = borders;
|
|
10252
11594
|
}
|
|
11595
|
+
// tblLook per ECMA-376 §17.4.57 — supports both hex-string format
|
|
11596
|
+
// (w:val="04A0") AND the expanded individual-attribute form
|
|
11597
|
+
// (firstRow/lastRow/firstColumn/lastColumn/noHBand/noVBand).
|
|
11598
|
+
// Word often emits the expanded form (no w:val) inside *PrChange
|
|
11599
|
+
// previous-properties; the hex-only read silently collapsed every
|
|
11600
|
+
// flag to "0000" on round-trip.
|
|
10253
11601
|
if (propsObj['w:tblLook']) {
|
|
10254
11602
|
const look = propsObj['w:tblLook'];
|
|
10255
|
-
|
|
11603
|
+
if (look['@_w:val']) {
|
|
11604
|
+
result.tblLook = String(look['@_w:val']);
|
|
11605
|
+
} else {
|
|
11606
|
+
const attrIsOn = (name: string): boolean => {
|
|
11607
|
+
const v = look[name];
|
|
11608
|
+
if (v === undefined) return false;
|
|
11609
|
+
return parseOoxmlBoolean({ '@_w:val': v });
|
|
11610
|
+
};
|
|
11611
|
+
let value = 0;
|
|
11612
|
+
if (attrIsOn('@_w:firstRow')) value |= 0x0020;
|
|
11613
|
+
if (attrIsOn('@_w:lastRow')) value |= 0x0040;
|
|
11614
|
+
if (attrIsOn('@_w:firstColumn')) value |= 0x0080;
|
|
11615
|
+
if (attrIsOn('@_w:lastColumn')) value |= 0x0100;
|
|
11616
|
+
if (attrIsOn('@_w:noHBand')) value |= 0x0200;
|
|
11617
|
+
if (attrIsOn('@_w:noVBand')) value |= 0x0400;
|
|
11618
|
+
result.tblLook = value.toString(16).toUpperCase().padStart(4, '0');
|
|
11619
|
+
}
|
|
10256
11620
|
}
|
|
10257
11621
|
if (propsObj['w:tblCaption']) {
|
|
10258
|
-
|
|
11622
|
+
// w:tblCaption w:val is ST_String (§17.4.62). Cast through
|
|
11623
|
+
// String() so purely-numeric caption text round-trips as a
|
|
11624
|
+
// string inside the tracked-change previousProperties.
|
|
11625
|
+
const v = propsObj['w:tblCaption']['@_w:val'];
|
|
11626
|
+
result.caption = v !== undefined && v !== null ? String(v) : undefined;
|
|
10259
11627
|
}
|
|
10260
11628
|
if (propsObj['w:tblDescription']) {
|
|
10261
|
-
|
|
11629
|
+
// w:tblDescription w:val is ST_String (§17.4.63).
|
|
11630
|
+
const v = propsObj['w:tblDescription']['@_w:val'];
|
|
11631
|
+
result.description = v !== undefined && v !== null ? String(v) : undefined;
|
|
10262
11632
|
}
|
|
10263
11633
|
|
|
10264
11634
|
// Row-level properties (w:trPr context) — all CT_TrPr elements
|
|
@@ -10289,14 +11659,15 @@ export class DocumentParser {
|
|
|
10289
11659
|
const rule = propsObj['w:trHeight']['@_w:hRule'];
|
|
10290
11660
|
if (rule) result.heightRule = rule;
|
|
10291
11661
|
}
|
|
11662
|
+
// Row CT_OnOff — honour w:val per ECMA-376 §17.17.4 (ST_OnOff)
|
|
10292
11663
|
if (propsObj['w:tblHeader']) {
|
|
10293
|
-
result.isHeader =
|
|
11664
|
+
result.isHeader = parseOoxmlBoolean(propsObj['w:tblHeader']);
|
|
10294
11665
|
}
|
|
10295
11666
|
if (propsObj['w:cantSplit']) {
|
|
10296
|
-
result.cantSplit =
|
|
11667
|
+
result.cantSplit = parseOoxmlBoolean(propsObj['w:cantSplit']);
|
|
10297
11668
|
}
|
|
10298
11669
|
if (propsObj['w:hidden']) {
|
|
10299
|
-
result.hidden =
|
|
11670
|
+
result.hidden = parseOoxmlBoolean(propsObj['w:hidden']);
|
|
10300
11671
|
}
|
|
10301
11672
|
|
|
10302
11673
|
// Cell-level properties (w:tcPr context) — all CT_TcPr elements
|
|
@@ -10317,14 +11688,19 @@ export class DocumentParser {
|
|
|
10317
11688
|
const borders: any = {};
|
|
10318
11689
|
const bordersObj = propsObj['w:tcBorders'];
|
|
10319
11690
|
for (const side of ['top', 'bottom', 'left', 'right', 'tl2br', 'tr2bl']) {
|
|
10320
|
-
|
|
10321
|
-
|
|
11691
|
+
// Prefer bidi-aware w:start/w:end aliases for left/right
|
|
11692
|
+
// (ECMA-376 §17.4.66 CT_TcBorders). Diagonals (tl2br/tr2bl)
|
|
11693
|
+
// have no bidi aliases.
|
|
11694
|
+
const aliasKey = side === 'left' ? 'w:start' : side === 'right' ? 'w:end' : undefined;
|
|
11695
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${side}`];
|
|
11696
|
+
if (borderObj) {
|
|
11697
|
+
borders[side] = this.parseBorderElement(borderObj);
|
|
10322
11698
|
}
|
|
10323
11699
|
}
|
|
10324
11700
|
if (Object.keys(borders).length > 0) result.borders = borders;
|
|
10325
11701
|
}
|
|
10326
11702
|
if (propsObj['w:noWrap']) {
|
|
10327
|
-
result.noWrap =
|
|
11703
|
+
result.noWrap = parseOoxmlBoolean(propsObj['w:noWrap']);
|
|
10328
11704
|
}
|
|
10329
11705
|
if (propsObj['w:tcMar']) {
|
|
10330
11706
|
const tcMar = propsObj['w:tcMar'];
|
|
@@ -10341,13 +11717,13 @@ export class DocumentParser {
|
|
|
10341
11717
|
result.textDirection = propsObj['w:textDirection']['@_w:val'];
|
|
10342
11718
|
}
|
|
10343
11719
|
if (propsObj['w:tcFitText']) {
|
|
10344
|
-
result.fitText =
|
|
11720
|
+
result.fitText = parseOoxmlBoolean(propsObj['w:tcFitText']);
|
|
10345
11721
|
}
|
|
10346
11722
|
if (propsObj['w:vAlign']) {
|
|
10347
11723
|
result.verticalAlignment = propsObj['w:vAlign']['@_w:val'];
|
|
10348
11724
|
}
|
|
10349
11725
|
if (propsObj['w:hideMark']) {
|
|
10350
|
-
result.hideMark =
|
|
11726
|
+
result.hideMark = parseOoxmlBoolean(propsObj['w:hideMark']);
|
|
10351
11727
|
}
|
|
10352
11728
|
if (propsObj['w:cnfStyle']) {
|
|
10353
11729
|
result.cnfStyle = propsObj['w:cnfStyle']['@_w:val'];
|
|
@@ -10377,6 +11753,79 @@ export class DocumentParser {
|
|
|
10377
11753
|
if (!sectPrXml) return {};
|
|
10378
11754
|
const result: Record<string, any> = {};
|
|
10379
11755
|
|
|
11756
|
+
// Footnote properties (w:footnotePr) per §17.11.9. The main sectPr parser
|
|
11757
|
+
// reads these, and the emitter supports prev.footnotePr, but the
|
|
11758
|
+
// sectPrChange parser previously dropped them entirely — tracked history
|
|
11759
|
+
// of changes to footnote numbering format / position / start / restart
|
|
11760
|
+
// was lost on every round-trip.
|
|
11761
|
+
const footnotePrElements = XMLParser.extractElements(sectPrXml, 'w:footnotePr');
|
|
11762
|
+
if (footnotePrElements.length > 0 && footnotePrElements[0]) {
|
|
11763
|
+
const fnPr = footnotePrElements[0];
|
|
11764
|
+
const fnObj: any = {};
|
|
11765
|
+
const posElements = XMLParser.extractElements(fnPr, 'w:pos');
|
|
11766
|
+
if (posElements[0]) {
|
|
11767
|
+
const pos = XMLParser.extractAttribute(posElements[0], 'w:val');
|
|
11768
|
+
if (pos) fnObj.position = pos;
|
|
11769
|
+
}
|
|
11770
|
+
const numFmtElements = XMLParser.extractElements(fnPr, 'w:numFmt');
|
|
11771
|
+
if (numFmtElements[0]) {
|
|
11772
|
+
const fmt = XMLParser.extractAttribute(numFmtElements[0], 'w:val');
|
|
11773
|
+
if (fmt) fnObj.numberFormat = fmt;
|
|
11774
|
+
}
|
|
11775
|
+
const numStartElements = XMLParser.extractElements(fnPr, 'w:numStart');
|
|
11776
|
+
if (numStartElements[0]) {
|
|
11777
|
+
const start = XMLParser.extractAttribute(numStartElements[0], 'w:val');
|
|
11778
|
+
if (start !== undefined) fnObj.startNumber = parseInt(String(start), 10);
|
|
11779
|
+
}
|
|
11780
|
+
const numRestartElements = XMLParser.extractElements(fnPr, 'w:numRestart');
|
|
11781
|
+
if (numRestartElements[0]) {
|
|
11782
|
+
const restart = XMLParser.extractAttribute(numRestartElements[0], 'w:val');
|
|
11783
|
+
if (restart) fnObj.restart = restart;
|
|
11784
|
+
}
|
|
11785
|
+
if (Object.keys(fnObj).length > 0) result.footnotePr = fnObj;
|
|
11786
|
+
}
|
|
11787
|
+
|
|
11788
|
+
// Endnote properties (w:endnotePr) per §17.11.5 — mirror of footnotePr.
|
|
11789
|
+
const endnotePrElements = XMLParser.extractElements(sectPrXml, 'w:endnotePr');
|
|
11790
|
+
if (endnotePrElements.length > 0 && endnotePrElements[0]) {
|
|
11791
|
+
const enPr = endnotePrElements[0];
|
|
11792
|
+
const enObj: any = {};
|
|
11793
|
+
const posElements = XMLParser.extractElements(enPr, 'w:pos');
|
|
11794
|
+
if (posElements[0]) {
|
|
11795
|
+
const pos = XMLParser.extractAttribute(posElements[0], 'w:val');
|
|
11796
|
+
if (pos) enObj.position = pos;
|
|
11797
|
+
}
|
|
11798
|
+
const numFmtElements = XMLParser.extractElements(enPr, 'w:numFmt');
|
|
11799
|
+
if (numFmtElements[0]) {
|
|
11800
|
+
const fmt = XMLParser.extractAttribute(numFmtElements[0], 'w:val');
|
|
11801
|
+
if (fmt) enObj.numberFormat = fmt;
|
|
11802
|
+
}
|
|
11803
|
+
const numStartElements = XMLParser.extractElements(enPr, 'w:numStart');
|
|
11804
|
+
if (numStartElements[0]) {
|
|
11805
|
+
const start = XMLParser.extractAttribute(numStartElements[0], 'w:val');
|
|
11806
|
+
if (start !== undefined) enObj.startNumber = parseInt(String(start), 10);
|
|
11807
|
+
}
|
|
11808
|
+
const numRestartElements = XMLParser.extractElements(enPr, 'w:numRestart');
|
|
11809
|
+
if (numRestartElements[0]) {
|
|
11810
|
+
const restart = XMLParser.extractAttribute(numRestartElements[0], 'w:val');
|
|
11811
|
+
if (restart) enObj.restart = restart;
|
|
11812
|
+
}
|
|
11813
|
+
if (Object.keys(enObj).length > 0) result.endnotePr = enObj;
|
|
11814
|
+
}
|
|
11815
|
+
|
|
11816
|
+
// Paper source (w:paperSrc) per §17.6.12 CT_PaperSource — first-page / other
|
|
11817
|
+
// paper tray selection. Both attributes optional per schema.
|
|
11818
|
+
const paperSrcElements = XMLParser.extractElements(sectPrXml, 'w:paperSrc');
|
|
11819
|
+
if (paperSrcElements.length > 0 && paperSrcElements[0]) {
|
|
11820
|
+
const ps = paperSrcElements[0];
|
|
11821
|
+
const psObj: any = {};
|
|
11822
|
+
const first = XMLParser.extractAttribute(ps, 'w:first');
|
|
11823
|
+
if (first !== undefined) psObj.first = parseInt(String(first), 10);
|
|
11824
|
+
const other = XMLParser.extractAttribute(ps, 'w:other');
|
|
11825
|
+
if (other !== undefined) psObj.other = parseInt(String(other), 10);
|
|
11826
|
+
if (Object.keys(psObj).length > 0) result.paperSource = psObj;
|
|
11827
|
+
}
|
|
11828
|
+
|
|
10380
11829
|
// Page size
|
|
10381
11830
|
const pgSzElements = XMLParser.extractElements(sectPrXml, 'w:pgSz');
|
|
10382
11831
|
if (pgSzElements.length > 0 && pgSzElements[0]) {
|
|
@@ -10395,7 +11844,10 @@ export class DocumentParser {
|
|
|
10395
11844
|
}
|
|
10396
11845
|
}
|
|
10397
11846
|
|
|
10398
|
-
// Margins
|
|
11847
|
+
// Margins — full CT_PageMar attribute set (§17.6.11) including w:gutter
|
|
11848
|
+
// (the book-binding margin). Previously gutter was dropped on sectPrChange
|
|
11849
|
+
// history, so any tracked change to a binding-gutter value lost the
|
|
11850
|
+
// previous value on round-trip.
|
|
10399
11851
|
const pgMarElements = XMLParser.extractElements(sectPrXml, 'w:pgMar');
|
|
10400
11852
|
if (pgMarElements.length > 0 && pgMarElements[0]) {
|
|
10401
11853
|
const pgMar = pgMarElements[0];
|
|
@@ -10412,6 +11864,8 @@ export class DocumentParser {
|
|
|
10412
11864
|
if (header) margins.header = parseInt(header, 10);
|
|
10413
11865
|
const footer = XMLParser.extractAttribute(pgMar, 'w:footer');
|
|
10414
11866
|
if (footer) margins.footer = parseInt(footer, 10);
|
|
11867
|
+
const gutter = XMLParser.extractAttribute(pgMar, 'w:gutter');
|
|
11868
|
+
if (gutter) margins.gutter = parseInt(gutter, 10);
|
|
10415
11869
|
if (Object.keys(margins).length > 0) result.margins = margins;
|
|
10416
11870
|
}
|
|
10417
11871
|
|
|
@@ -10438,7 +11892,13 @@ export class DocumentParser {
|
|
|
10438
11892
|
if (Object.keys(lnObj).length > 0) result.lineNumbering = lnObj;
|
|
10439
11893
|
}
|
|
10440
11894
|
|
|
10441
|
-
// Page numbering
|
|
11895
|
+
// Page numbering — full CT_PageNumber attribute set (§17.6.12):
|
|
11896
|
+
// fmt / start / chapStyle / chapSep. Previously only fmt+start were read,
|
|
11897
|
+
// so tracked-change history of chapter-numbering edits (e.g. switching
|
|
11898
|
+
// from "Heading 1" to "Heading 2" as the chapter marker, or changing the
|
|
11899
|
+
// chapter separator from hyphen to emDash) lost the previous values.
|
|
11900
|
+
// The Section.ts emitter stores chapStyle / chapSep as top-level
|
|
11901
|
+
// properties rather than on pageNumbering, so expose them the same way.
|
|
10442
11902
|
const pgNumElements = XMLParser.extractElements(sectPrXml, 'w:pgNumType');
|
|
10443
11903
|
if (pgNumElements.length > 0 && pgNumElements[0]) {
|
|
10444
11904
|
const pn = pgNumElements[0];
|
|
@@ -10448,6 +11908,12 @@ export class DocumentParser {
|
|
|
10448
11908
|
const fmt = XMLParser.extractAttribute(pn, 'w:fmt');
|
|
10449
11909
|
if (fmt) pnObj.format = fmt;
|
|
10450
11910
|
if (Object.keys(pnObj).length > 0) result.pageNumbering = pnObj;
|
|
11911
|
+
// Mirror the main-sectPr parser: chapStyle / chapSep live at the root
|
|
11912
|
+
// of the section properties, not inside pageNumbering.
|
|
11913
|
+
const chapStyle = XMLParser.extractAttribute(pn, 'w:chapStyle');
|
|
11914
|
+
if (chapStyle !== undefined) result.chapStyle = parseInt(String(chapStyle), 10);
|
|
11915
|
+
const chapSep = XMLParser.extractAttribute(pn, 'w:chapSep');
|
|
11916
|
+
if (chapSep) result.chapSep = chapSep;
|
|
10451
11917
|
}
|
|
10452
11918
|
|
|
10453
11919
|
// Columns
|
|
@@ -10456,16 +11922,58 @@ export class DocumentParser {
|
|
|
10456
11922
|
const cols = colsElements[0];
|
|
10457
11923
|
const num = XMLParser.extractAttribute(cols, 'w:num');
|
|
10458
11924
|
const space = XMLParser.extractAttribute(cols, 'w:space');
|
|
11925
|
+
// Full CT_Columns attribute set (§17.6.4): num / space / equalWidth / sep
|
|
11926
|
+
// plus the child <w:col w:w="..." w:space="..."/> entries for per-column
|
|
11927
|
+
// widths. Previously only num+space were read, so sectPrChange history of
|
|
11928
|
+
// a columns-layout change dropped equalWidth, the separator line, and
|
|
11929
|
+
// the entire custom column-width / per-column-space configuration.
|
|
11930
|
+
const equalWidth = XMLParser.extractAttribute(cols, 'w:equalWidth');
|
|
11931
|
+
const sep = XMLParser.extractAttribute(cols, 'w:sep');
|
|
11932
|
+
|
|
11933
|
+
// Extract individual <w:col> children for non-equal-width layouts.
|
|
11934
|
+
const colChildElements = XMLParser.extractElements(cols, 'w:col');
|
|
11935
|
+
const columnWidths: number[] = [];
|
|
11936
|
+
const columnSpaces: number[] = [];
|
|
11937
|
+
let hasColumnSpaces = false;
|
|
11938
|
+
for (const col of colChildElements) {
|
|
11939
|
+
const width = XMLParser.extractAttribute(col, 'w:w');
|
|
11940
|
+
if (width) columnWidths.push(parseInt(width, 10));
|
|
11941
|
+
const colSpace = XMLParser.extractAttribute(col, 'w:space');
|
|
11942
|
+
if (colSpace) {
|
|
11943
|
+
columnSpaces.push(parseInt(colSpace, 10));
|
|
11944
|
+
hasColumnSpaces = true;
|
|
11945
|
+
} else {
|
|
11946
|
+
columnSpaces.push(0);
|
|
11947
|
+
}
|
|
11948
|
+
}
|
|
11949
|
+
|
|
10459
11950
|
if (num) {
|
|
10460
11951
|
result.columns = {
|
|
10461
11952
|
count: parseInt(num, 10),
|
|
10462
11953
|
space: space ? parseInt(space, 10) : undefined,
|
|
11954
|
+
equalWidth: equalWidth ? parseOnOffAttribute(equalWidth) : undefined,
|
|
11955
|
+
separator: sep ? parseOnOffAttribute(sep) : undefined,
|
|
11956
|
+
columnWidths: columnWidths.length > 0 ? columnWidths : undefined,
|
|
11957
|
+
columnSpaces: hasColumnSpaces ? columnSpaces : undefined,
|
|
10463
11958
|
};
|
|
10464
11959
|
}
|
|
10465
11960
|
}
|
|
10466
11961
|
|
|
10467
|
-
//
|
|
10468
|
-
|
|
11962
|
+
// CT_OnOff sectPr flags — honour w:val per ECMA-376 §17.17.4 (ST_OnOff).
|
|
11963
|
+
// Previously these used substring `.includes()`, which both ignored w:val
|
|
11964
|
+
// (flipping explicit false to true) and could false-positive on prefix
|
|
11965
|
+
// matches (e.g. "<w:bidi" inside "<w:bidiVisual"). Use extractElements +
|
|
11966
|
+
// extractAttribute + parseOnOffAttribute instead.
|
|
11967
|
+
const parseSectCtOnOff = (tagName: string): boolean | undefined => {
|
|
11968
|
+
const els = XMLParser.extractElements(sectPrXml, tagName);
|
|
11969
|
+
if (els.length === 0 || !els[0]) return undefined;
|
|
11970
|
+
const v = XMLParser.extractAttribute(els[0], 'w:val');
|
|
11971
|
+
return parseOnOffAttribute(v, true);
|
|
11972
|
+
};
|
|
11973
|
+
|
|
11974
|
+
// Form protection (w:formProt) — CT_OnOff
|
|
11975
|
+
const formProtVal = parseSectCtOnOff('w:formProt');
|
|
11976
|
+
if (formProtVal !== undefined) result.formProt = formProtVal;
|
|
10469
11977
|
|
|
10470
11978
|
// Vertical alignment
|
|
10471
11979
|
const vAlignElements = XMLParser.extractElements(sectPrXml, 'w:vAlign');
|
|
@@ -10474,11 +11982,13 @@ export class DocumentParser {
|
|
|
10474
11982
|
if (val) result.verticalAlignment = val;
|
|
10475
11983
|
}
|
|
10476
11984
|
|
|
10477
|
-
// Suppress endnotes
|
|
10478
|
-
|
|
11985
|
+
// Suppress endnotes (w:noEndnote) — CT_OnOff
|
|
11986
|
+
const noEndnoteVal = parseSectCtOnOff('w:noEndnote');
|
|
11987
|
+
if (noEndnoteVal !== undefined) result.noEndnote = noEndnoteVal;
|
|
10479
11988
|
|
|
10480
|
-
// Title page
|
|
10481
|
-
|
|
11989
|
+
// Title page (w:titlePg) — CT_OnOff
|
|
11990
|
+
const titlePgVal = parseSectCtOnOff('w:titlePg');
|
|
11991
|
+
if (titlePgVal !== undefined) result.titlePage = titlePgVal;
|
|
10482
11992
|
|
|
10483
11993
|
// Text direction
|
|
10484
11994
|
const textDirElements = XMLParser.extractElements(sectPrXml, 'w:textDirection');
|
|
@@ -10487,11 +11997,13 @@ export class DocumentParser {
|
|
|
10487
11997
|
if (val) result.textDirection = val;
|
|
10488
11998
|
}
|
|
10489
11999
|
|
|
10490
|
-
// Bidi section
|
|
10491
|
-
|
|
12000
|
+
// Bidi section (w:bidi) — CT_OnOff
|
|
12001
|
+
const bidiVal = parseSectCtOnOff('w:bidi');
|
|
12002
|
+
if (bidiVal !== undefined) result.bidi = bidiVal;
|
|
10492
12003
|
|
|
10493
|
-
// RTL gutter
|
|
10494
|
-
|
|
12004
|
+
// RTL gutter (w:rtlGutter) — CT_OnOff
|
|
12005
|
+
const rtlGutterVal = parseSectCtOnOff('w:rtlGutter');
|
|
12006
|
+
if (rtlGutterVal !== undefined) result.rtlGutter = rtlGutterVal;
|
|
10495
12007
|
|
|
10496
12008
|
// Document grid
|
|
10497
12009
|
const docGridElements = XMLParser.extractElements(sectPrXml, 'w:docGrid');
|
|
@@ -10507,6 +12019,64 @@ export class DocumentParser {
|
|
|
10507
12019
|
if (Object.keys(dgObj).length > 0) result.docGrid = dgObj;
|
|
10508
12020
|
}
|
|
10509
12021
|
|
|
12022
|
+
// Page borders (w:pgBorders) per ECMA-376 §17.6.10. The main sectPr parser
|
|
12023
|
+
// reads these, but the sectPrChange previous-sectPr parser previously
|
|
12024
|
+
// didn't — so a tracked-change history of page-border edits lost the
|
|
12025
|
+
// entire "previous" border configuration (style, color, themeColor,
|
|
12026
|
+
// themeTint, themeShade, shadow, frame) every round-trip. The emitter
|
|
12027
|
+
// supports prev.pageBorders already; this is the missing parser half.
|
|
12028
|
+
const pgBordersElements = XMLParser.extractElements(sectPrXml, 'w:pgBorders');
|
|
12029
|
+
if (pgBordersElements.length > 0 && pgBordersElements[0]) {
|
|
12030
|
+
const pgBordersXml = pgBordersElements[0];
|
|
12031
|
+
const pageBorders: any = {};
|
|
12032
|
+
const offsetFrom = XMLParser.extractAttribute(pgBordersXml, 'w:offsetFrom');
|
|
12033
|
+
if (offsetFrom) pageBorders.offsetFrom = offsetFrom;
|
|
12034
|
+
const display = XMLParser.extractAttribute(pgBordersXml, 'w:display');
|
|
12035
|
+
if (display) pageBorders.display = display;
|
|
12036
|
+
const zOrder = XMLParser.extractAttribute(pgBordersXml, 'w:zOrder');
|
|
12037
|
+
if (zOrder) pageBorders.zOrder = zOrder;
|
|
12038
|
+
|
|
12039
|
+
// Per-side border parser mirrors the main-sectPr logic — full CT_Border
|
|
12040
|
+
// attribute set including themed colors and shadow/frame flags.
|
|
12041
|
+
const parsePrevBorder = (sideXml: string): any | undefined => {
|
|
12042
|
+
if (!sideXml) return undefined;
|
|
12043
|
+
const border: any = {};
|
|
12044
|
+
const val = XMLParser.extractAttribute(sideXml, 'w:val');
|
|
12045
|
+
if (val) border.style = val;
|
|
12046
|
+
const sz = XMLParser.extractAttribute(sideXml, 'w:sz');
|
|
12047
|
+
if (sz) border.size = parseInt(sz, 10);
|
|
12048
|
+
const color = XMLParser.extractAttribute(sideXml, 'w:color');
|
|
12049
|
+
if (color) border.color = color;
|
|
12050
|
+
const space = XMLParser.extractAttribute(sideXml, 'w:space');
|
|
12051
|
+
if (space) border.space = parseInt(space, 10);
|
|
12052
|
+
const shadow = XMLParser.extractAttribute(sideXml, 'w:shadow');
|
|
12053
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
12054
|
+
const frame = XMLParser.extractAttribute(sideXml, 'w:frame');
|
|
12055
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
12056
|
+
const themeColor = XMLParser.extractAttribute(sideXml, 'w:themeColor');
|
|
12057
|
+
if (themeColor) border.themeColor = themeColor;
|
|
12058
|
+
const themeTint = XMLParser.extractAttribute(sideXml, 'w:themeTint');
|
|
12059
|
+
if (themeTint) border.themeTint = themeTint;
|
|
12060
|
+
const themeShade = XMLParser.extractAttribute(sideXml, 'w:themeShade');
|
|
12061
|
+
if (themeShade) border.themeShade = themeShade;
|
|
12062
|
+
const artId = XMLParser.extractAttribute(sideXml, 'w:id');
|
|
12063
|
+
if (artId) border.artId = parseInt(artId, 10);
|
|
12064
|
+
return Object.keys(border).length > 0 ? border : undefined;
|
|
12065
|
+
};
|
|
12066
|
+
|
|
12067
|
+
for (const side of ['top', 'left', 'bottom', 'right']) {
|
|
12068
|
+
const sideElements = XMLParser.extractElements(pgBordersXml, `w:${side}`);
|
|
12069
|
+
if (sideElements.length > 0 && sideElements[0]) {
|
|
12070
|
+
const border = parsePrevBorder(sideElements[0]);
|
|
12071
|
+
if (border) pageBorders[side] = border;
|
|
12072
|
+
}
|
|
12073
|
+
}
|
|
12074
|
+
|
|
12075
|
+
if (Object.keys(pageBorders).length > 0) {
|
|
12076
|
+
result.pageBorders = pageBorders;
|
|
12077
|
+
}
|
|
12078
|
+
}
|
|
12079
|
+
|
|
10510
12080
|
return result;
|
|
10511
12081
|
}
|
|
10512
12082
|
}
|