docxmlater 10.4.0 → 11.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/constants/legacyCompatFlags.d.ts +1 -1
- package/dist/constants/legacyCompatFlags.d.ts.map +1 -1
- package/dist/constants/legacyCompatFlags.js.map +1 -1
- package/dist/core/Document.d.ts +75 -67
- package/dist/core/Document.d.ts.map +1 -1
- package/dist/core/Document.js +618 -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 +2100 -1076
- package/dist/core/DocumentParser.js.map +1 -1
- package/dist/core/DocumentValidator.d.ts +3 -3
- package/dist/core/DocumentValidator.d.ts.map +1 -1
- package/dist/core/DocumentValidator.js +31 -31
- package/dist/core/DocumentValidator.js.map +1 -1
- package/dist/core/ElementRegistry.d.ts +22 -0
- package/dist/core/ElementRegistry.d.ts.map +1 -0
- package/dist/core/ElementRegistry.js +27 -0
- package/dist/core/ElementRegistry.js.map +1 -0
- package/dist/core/Relationship.js +4 -4
- package/dist/core/Relationship.js.map +1 -1
- package/dist/core/RelationshipManager.d.ts +1 -1
- package/dist/core/RelationshipManager.d.ts.map +1 -1
- package/dist/core/RelationshipManager.js +32 -32
- package/dist/core/RelationshipManager.js.map +1 -1
- package/dist/elements/AlternateContent.d.ts +1 -1
- package/dist/elements/AlternateContent.d.ts.map +1 -1
- package/dist/elements/AlternateContent.js.map +1 -1
- package/dist/elements/Bookmark.d.ts +6 -1
- package/dist/elements/Bookmark.d.ts.map +1 -1
- package/dist/elements/Bookmark.js +19 -3
- package/dist/elements/Bookmark.js.map +1 -1
- package/dist/elements/BookmarkManager.d.ts +1 -1
- package/dist/elements/BookmarkManager.d.ts.map +1 -1
- package/dist/elements/BookmarkManager.js +7 -7
- package/dist/elements/BookmarkManager.js.map +1 -1
- package/dist/elements/Comment.d.ts +2 -2
- package/dist/elements/Comment.d.ts.map +1 -1
- package/dist/elements/Comment.js +4 -4
- package/dist/elements/Comment.js.map +1 -1
- package/dist/elements/CommentManager.d.ts +2 -2
- package/dist/elements/CommentManager.d.ts.map +1 -1
- package/dist/elements/CommentManager.js +9 -9
- package/dist/elements/CommentManager.js.map +1 -1
- package/dist/elements/CommonTypes.d.ts +9 -4
- package/dist/elements/CommonTypes.d.ts.map +1 -1
- package/dist/elements/CommonTypes.js +1 -0
- package/dist/elements/CommonTypes.js.map +1 -1
- package/dist/elements/CustomXml.d.ts +1 -1
- package/dist/elements/CustomXml.d.ts.map +1 -1
- package/dist/elements/CustomXml.js.map +1 -1
- package/dist/elements/Endnote.d.ts +2 -2
- package/dist/elements/Endnote.d.ts.map +1 -1
- package/dist/elements/Endnote.js +9 -9
- package/dist/elements/Endnote.js.map +1 -1
- package/dist/elements/EndnoteManager.d.ts +1 -1
- package/dist/elements/EndnoteManager.d.ts.map +1 -1
- package/dist/elements/EndnoteManager.js +11 -11
- package/dist/elements/EndnoteManager.js.map +1 -1
- package/dist/elements/Field.d.ts +9 -5
- package/dist/elements/Field.d.ts.map +1 -1
- package/dist/elements/Field.js +21 -9
- package/dist/elements/Field.js.map +1 -1
- package/dist/elements/FieldHelpers.d.ts +1 -1
- package/dist/elements/FieldHelpers.d.ts.map +1 -1
- package/dist/elements/FieldHelpers.js +10 -10
- package/dist/elements/FieldHelpers.js.map +1 -1
- package/dist/elements/Footer.d.ts +3 -3
- package/dist/elements/Footer.d.ts.map +1 -1
- package/dist/elements/Footer.js +5 -5
- package/dist/elements/Footer.js.map +1 -1
- package/dist/elements/Footnote.d.ts +2 -2
- package/dist/elements/Footnote.d.ts.map +1 -1
- package/dist/elements/Footnote.js +9 -9
- package/dist/elements/Footnote.js.map +1 -1
- package/dist/elements/FootnoteManager.d.ts +1 -1
- package/dist/elements/FootnoteManager.d.ts.map +1 -1
- package/dist/elements/FootnoteManager.js +11 -11
- package/dist/elements/FootnoteManager.js.map +1 -1
- package/dist/elements/Header.d.ts +3 -3
- package/dist/elements/Header.d.ts.map +1 -1
- package/dist/elements/Header.js +5 -5
- package/dist/elements/Header.js.map +1 -1
- package/dist/elements/HeaderFooterManager.d.ts +2 -2
- package/dist/elements/HeaderFooterManager.d.ts.map +1 -1
- package/dist/elements/HeaderFooterManager.js.map +1 -1
- package/dist/elements/Hyperlink.d.ts +5 -5
- package/dist/elements/Hyperlink.d.ts.map +1 -1
- package/dist/elements/Hyperlink.js +29 -29
- package/dist/elements/Hyperlink.js.map +1 -1
- package/dist/elements/Image.d.ts +1 -1
- package/dist/elements/Image.d.ts.map +1 -1
- package/dist/elements/Image.js +67 -67
- package/dist/elements/Image.js.map +1 -1
- package/dist/elements/ImageManager.d.ts +1 -1
- package/dist/elements/ImageManager.d.ts.map +1 -1
- package/dist/elements/ImageManager.js +4 -4
- package/dist/elements/ImageManager.js.map +1 -1
- package/dist/elements/ImageRun.d.ts +3 -3
- package/dist/elements/ImageRun.d.ts.map +1 -1
- package/dist/elements/ImageRun.js +2 -2
- package/dist/elements/ImageRun.js.map +1 -1
- package/dist/elements/MathElement.d.ts +1 -1
- package/dist/elements/MathElement.d.ts.map +1 -1
- package/dist/elements/MathElement.js.map +1 -1
- package/dist/elements/Paragraph.d.ts +34 -19
- package/dist/elements/Paragraph.d.ts.map +1 -1
- package/dist/elements/Paragraph.js +286 -231
- package/dist/elements/Paragraph.js.map +1 -1
- package/dist/elements/PreservedElement.d.ts +1 -1
- package/dist/elements/PreservedElement.d.ts.map +1 -1
- package/dist/elements/PreservedElement.js.map +1 -1
- package/dist/elements/PropertyChangeTypes.d.ts +2 -2
- package/dist/elements/PropertyChangeTypes.d.ts.map +1 -1
- package/dist/elements/PropertyChangeTypes.js.map +1 -1
- package/dist/elements/RangeMarker.d.ts +14 -1
- package/dist/elements/RangeMarker.d.ts.map +1 -1
- package/dist/elements/RangeMarker.js +46 -8
- package/dist/elements/RangeMarker.js.map +1 -1
- package/dist/elements/RegisteredBodyElement.d.ts +15 -0
- package/dist/elements/RegisteredBodyElement.d.ts.map +1 -0
- package/dist/elements/RegisteredBodyElement.js +44 -0
- package/dist/elements/RegisteredBodyElement.js.map +1 -0
- package/dist/elements/Revision.d.ts +8 -8
- package/dist/elements/Revision.d.ts.map +1 -1
- package/dist/elements/Revision.js +12 -12
- package/dist/elements/Revision.js.map +1 -1
- package/dist/elements/RevisionContent.d.ts +3 -3
- package/dist/elements/RevisionContent.d.ts.map +1 -1
- package/dist/elements/RevisionContent.js.map +1 -1
- package/dist/elements/RevisionManager.d.ts +2 -2
- package/dist/elements/RevisionManager.d.ts.map +1 -1
- package/dist/elements/RevisionManager.js +2 -2
- package/dist/elements/RevisionManager.js.map +1 -1
- package/dist/elements/Run.d.ts +16 -10
- package/dist/elements/Run.d.ts.map +1 -1
- package/dist/elements/Run.js +199 -173
- package/dist/elements/Run.js.map +1 -1
- package/dist/elements/Section.d.ts +4 -2
- package/dist/elements/Section.d.ts.map +1 -1
- package/dist/elements/Section.js +152 -145
- package/dist/elements/Section.js.map +1 -1
- package/dist/elements/Shape.d.ts +3 -3
- package/dist/elements/Shape.d.ts.map +1 -1
- package/dist/elements/Shape.js +12 -12
- package/dist/elements/Shape.js.map +1 -1
- package/dist/elements/StructuredDocumentTag.d.ts +3 -3
- package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
- package/dist/elements/StructuredDocumentTag.js +39 -39
- package/dist/elements/StructuredDocumentTag.js.map +1 -1
- package/dist/elements/Table.d.ts +16 -10
- package/dist/elements/Table.d.ts.map +1 -1
- package/dist/elements/Table.js +118 -89
- package/dist/elements/Table.js.map +1 -1
- package/dist/elements/TableCell.d.ts +11 -11
- package/dist/elements/TableCell.d.ts.map +1 -1
- package/dist/elements/TableCell.js +108 -78
- package/dist/elements/TableCell.js.map +1 -1
- package/dist/elements/TableGridChange.d.ts +1 -1
- package/dist/elements/TableGridChange.d.ts.map +1 -1
- package/dist/elements/TableGridChange.js +3 -3
- package/dist/elements/TableGridChange.js.map +1 -1
- package/dist/elements/TableOfContents.d.ts +1 -1
- package/dist/elements/TableOfContents.d.ts.map +1 -1
- package/dist/elements/TableOfContents.js +2 -2
- package/dist/elements/TableOfContents.js.map +1 -1
- package/dist/elements/TableOfContentsElement.d.ts +2 -2
- package/dist/elements/TableOfContentsElement.d.ts.map +1 -1
- package/dist/elements/TableOfContentsElement.js +5 -5
- package/dist/elements/TableOfContentsElement.js.map +1 -1
- package/dist/elements/TableRow.d.ts +18 -7
- package/dist/elements/TableRow.d.ts.map +1 -1
- package/dist/elements/TableRow.js +127 -74
- package/dist/elements/TableRow.js.map +1 -1
- package/dist/elements/TextBox.d.ts +4 -4
- package/dist/elements/TextBox.d.ts.map +1 -1
- package/dist/elements/TextBox.js +6 -6
- package/dist/elements/TextBox.js.map +1 -1
- package/dist/esm/constants/legacyCompatFlags.js +97 -0
- package/dist/esm/constants/legacyCompatFlags.js.map +1 -0
- package/dist/esm/constants/limits.js +36 -0
- package/dist/esm/constants/limits.js.map +1 -0
- package/dist/esm/core/Document.js +8498 -0
- package/dist/esm/core/Document.js.map +1 -0
- package/dist/esm/core/DocumentContent.js +190 -0
- package/dist/esm/core/DocumentContent.js.map +1 -0
- package/dist/esm/core/DocumentEvents.js +47 -0
- package/dist/esm/core/DocumentEvents.js.map +1 -0
- package/dist/esm/core/DocumentGenerator.js +764 -0
- package/dist/esm/core/DocumentGenerator.js.map +1 -0
- package/dist/esm/core/DocumentIdManager.js +67 -0
- package/dist/esm/core/DocumentIdManager.js.map +1 -0
- package/dist/esm/core/DocumentParser.js +8760 -0
- package/dist/esm/core/DocumentParser.js.map +1 -0
- package/dist/esm/core/DocumentValidator.js +222 -0
- package/dist/esm/core/DocumentValidator.js.map +1 -0
- package/dist/esm/core/ElementRegistry.js +24 -0
- package/dist/esm/core/ElementRegistry.js.map +1 -0
- package/dist/esm/core/Relationship.js +177 -0
- package/dist/esm/core/Relationship.js.map +1 -0
- package/dist/esm/core/RelationshipManager.js +202 -0
- package/dist/esm/core/RelationshipManager.js.map +1 -0
- package/dist/esm/elements/AlternateContent.js +19 -0
- package/dist/esm/elements/AlternateContent.js.map +1 -0
- package/dist/esm/elements/Bookmark.js +115 -0
- package/dist/esm/elements/Bookmark.js.map +1 -0
- package/dist/esm/elements/BookmarkManager.js +99 -0
- package/dist/esm/elements/BookmarkManager.js.map +1 -0
- package/dist/esm/elements/Comment.js +181 -0
- package/dist/esm/elements/Comment.js.map +1 -0
- package/dist/esm/elements/CommentManager.js +233 -0
- package/dist/esm/elements/CommentManager.js.map +1 -0
- package/dist/esm/elements/CommonTypes.js +106 -0
- package/dist/esm/elements/CommonTypes.js.map +1 -0
- package/dist/esm/elements/CustomXml.js +19 -0
- package/dist/esm/elements/CustomXml.js.map +1 -0
- package/dist/esm/elements/Endnote.js +107 -0
- package/dist/esm/elements/Endnote.js.map +1 -0
- package/dist/esm/elements/EndnoteManager.js +119 -0
- package/dist/esm/elements/EndnoteManager.js.map +1 -0
- package/dist/esm/elements/Field.js +856 -0
- package/dist/esm/elements/Field.js.map +1 -0
- package/dist/esm/elements/FieldHelpers.js +134 -0
- package/dist/esm/elements/FieldHelpers.js.map +1 -0
- package/dist/esm/elements/FontManager.js +158 -0
- package/dist/esm/elements/FontManager.js.map +1 -0
- package/dist/esm/elements/Footer.js +141 -0
- package/dist/esm/elements/Footer.js.map +1 -0
- package/dist/esm/elements/Footnote.js +107 -0
- package/dist/esm/elements/Footnote.js.map +1 -0
- package/dist/esm/elements/FootnoteManager.js +119 -0
- package/dist/esm/elements/FootnoteManager.js.map +1 -0
- package/dist/esm/elements/Header.js +141 -0
- package/dist/esm/elements/Header.js.map +1 -0
- package/dist/esm/elements/HeaderFooterManager.js +87 -0
- package/dist/esm/elements/HeaderFooterManager.js.map +1 -0
- package/dist/esm/elements/Hyperlink.js +586 -0
- package/dist/esm/elements/Hyperlink.js.map +1 -0
- package/dist/esm/elements/Image.js +1288 -0
- package/dist/esm/elements/Image.js.map +1 -0
- package/dist/esm/elements/ImageManager.js +223 -0
- package/dist/esm/elements/ImageManager.js.map +1 -0
- package/dist/esm/elements/ImageRun.js +29 -0
- package/dist/esm/elements/ImageRun.js.map +1 -0
- package/dist/esm/elements/MathElement.js +37 -0
- package/dist/esm/elements/MathElement.js.map +1 -0
- package/dist/esm/elements/Paragraph.js +2308 -0
- package/dist/esm/elements/Paragraph.js.map +1 -0
- package/dist/esm/elements/PreservedElement.js +29 -0
- package/dist/esm/elements/PreservedElement.js.map +1 -0
- package/dist/esm/elements/PropertyChangeTypes.js +53 -0
- package/dist/esm/elements/PropertyChangeTypes.js.map +1 -0
- package/dist/esm/elements/RangeMarker.js +219 -0
- package/dist/esm/elements/RangeMarker.js.map +1 -0
- package/dist/esm/elements/RegisteredBodyElement.js +40 -0
- package/dist/esm/elements/RegisteredBodyElement.js.map +1 -0
- package/dist/esm/elements/Revision.js +498 -0
- package/dist/esm/elements/Revision.js.map +1 -0
- package/dist/esm/elements/RevisionContent.js +18 -0
- package/dist/esm/elements/RevisionContent.js.map +1 -0
- package/dist/esm/elements/RevisionManager.js +486 -0
- package/dist/esm/elements/RevisionManager.js.map +1 -0
- package/dist/esm/elements/Run.js +1465 -0
- package/dist/esm/elements/Run.js.map +1 -0
- package/dist/esm/elements/Section.js +978 -0
- package/dist/esm/elements/Section.js.map +1 -0
- package/dist/esm/elements/Shape.js +493 -0
- package/dist/esm/elements/Shape.js.map +1 -0
- package/dist/esm/elements/StructuredDocumentTag.js +471 -0
- package/dist/esm/elements/StructuredDocumentTag.js.map +1 -0
- package/dist/esm/elements/Table.js +1456 -0
- package/dist/esm/elements/Table.js.map +1 -0
- package/dist/esm/elements/TableCell.js +835 -0
- package/dist/esm/elements/TableCell.js.map +1 -0
- package/dist/esm/elements/TableGridChange.js +52 -0
- package/dist/esm/elements/TableGridChange.js.map +1 -0
- package/dist/esm/elements/TableOfContents.js +389 -0
- package/dist/esm/elements/TableOfContents.js.map +1 -0
- package/dist/esm/elements/TableOfContentsElement.js +29 -0
- package/dist/esm/elements/TableOfContentsElement.js.map +1 -0
- package/dist/esm/elements/TableRow.js +555 -0
- package/dist/esm/elements/TableRow.js.map +1 -0
- package/dist/esm/elements/TextBox.js +459 -0
- package/dist/esm/elements/TextBox.js.map +1 -0
- package/dist/esm/formatting/AbstractNumbering.js +325 -0
- package/dist/esm/formatting/AbstractNumbering.js.map +1 -0
- package/dist/esm/formatting/NumberingInstance.js +150 -0
- package/dist/esm/formatting/NumberingInstance.js.map +1 -0
- package/dist/esm/formatting/NumberingLevel.js +608 -0
- package/dist/esm/formatting/NumberingLevel.js.map +1 -0
- package/dist/esm/formatting/NumberingManager.js +423 -0
- package/dist/esm/formatting/NumberingManager.js.map +1 -0
- package/dist/esm/formatting/Style.js +1151 -0
- package/dist/esm/formatting/Style.js.map +1 -0
- package/dist/esm/formatting/StylesManager.js +557 -0
- package/dist/esm/formatting/StylesManager.js.map +1 -0
- package/dist/esm/helpers/CleanupHelper.js +350 -0
- package/dist/esm/helpers/CleanupHelper.js.map +1 -0
- package/dist/esm/images/ImageOptimizer.js +161 -0
- package/dist/esm/images/ImageOptimizer.js.map +1 -0
- package/dist/esm/index.js +75 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal.js +16 -0
- package/dist/esm/internal.js.map +1 -0
- package/dist/esm/managers/DrawingManager.js +163 -0
- package/dist/esm/managers/DrawingManager.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/processors/ChangelogGenerator.js +970 -0
- package/dist/esm/processors/ChangelogGenerator.js.map +1 -0
- package/dist/esm/processors/CompatibilityUpgrader.js +130 -0
- package/dist/esm/processors/CompatibilityUpgrader.js.map +1 -0
- package/dist/esm/processors/InMemoryRevisionAcceptor.js +530 -0
- package/dist/esm/processors/InMemoryRevisionAcceptor.js.map +1 -0
- package/dist/esm/processors/MoveOperationHelper.js +57 -0
- package/dist/esm/processors/MoveOperationHelper.js.map +1 -0
- package/dist/esm/processors/RevisionAwareProcessor.js +232 -0
- package/dist/esm/processors/RevisionAwareProcessor.js.map +1 -0
- package/dist/esm/processors/RevisionWalker.js +278 -0
- package/dist/esm/processors/RevisionWalker.js.map +1 -0
- package/dist/{utils → esm/processors}/SelectiveRevisionAcceptor.js +81 -42
- package/dist/esm/processors/SelectiveRevisionAcceptor.js.map +1 -0
- package/dist/esm/processors/ShadingResolver.js +66 -0
- package/dist/esm/processors/ShadingResolver.js.map +1 -0
- package/dist/esm/processors/acceptRevisions.js +416 -0
- package/dist/esm/processors/acceptRevisions.js.map +1 -0
- package/dist/esm/processors/cnfStyleDecoder.js +89 -0
- package/dist/esm/processors/cnfStyleDecoder.js.map +1 -0
- package/dist/esm/processors/stripTrackedChanges.js +201 -0
- package/dist/esm/processors/stripTrackedChanges.js.map +1 -0
- package/dist/esm/tracking/DocumentTrackingContext.js +531 -0
- package/dist/esm/tracking/DocumentTrackingContext.js.map +1 -0
- package/dist/esm/tracking/TrackingContext.js +2 -0
- package/dist/esm/tracking/TrackingContext.js.map +1 -0
- package/dist/esm/types/compatibility-types.js +8 -0
- package/dist/esm/types/compatibility-types.js.map +1 -0
- package/dist/esm/types/document-types.js +2 -0
- package/dist/esm/types/document-types.js.map +1 -0
- package/dist/esm/types/formatting.js +2 -0
- package/dist/esm/types/formatting.js.map +1 -0
- package/dist/esm/types/list-types.js +2 -0
- package/dist/esm/types/list-types.js.map +1 -0
- package/dist/esm/types/settings-types.js +2 -0
- package/dist/esm/types/settings-types.js.map +1 -0
- package/dist/esm/types/styleConfig.js +2 -0
- package/dist/esm/types/styleConfig.js.map +1 -0
- package/dist/esm/utils/KeyedRegistry.js +32 -0
- package/dist/esm/utils/KeyedRegistry.js.map +1 -0
- package/dist/esm/utils/corruptionDetection.js +155 -0
- package/dist/esm/utils/corruptionDetection.js.map +1 -0
- package/dist/esm/utils/dateFormatting.js +4 -0
- package/dist/esm/utils/dateFormatting.js.map +1 -0
- package/dist/esm/utils/deepClone.js +40 -0
- package/dist/esm/utils/deepClone.js.map +1 -0
- package/dist/esm/utils/deepEqual.js +47 -0
- package/dist/esm/utils/deepEqual.js.map +1 -0
- package/dist/esm/utils/diagnostics.js +69 -0
- package/dist/esm/utils/diagnostics.js.map +1 -0
- package/dist/esm/utils/errorHandling.js +36 -0
- package/dist/esm/utils/errorHandling.js.map +1 -0
- package/dist/esm/utils/formatting.js +93 -0
- package/dist/esm/utils/formatting.js.map +1 -0
- package/dist/esm/utils/list-detection.js +148 -0
- package/dist/esm/utils/list-detection.js.map +1 -0
- package/dist/esm/utils/logger.js +205 -0
- package/dist/esm/utils/logger.js.map +1 -0
- package/dist/esm/utils/parsingHelpers.js +56 -0
- package/dist/esm/utils/parsingHelpers.js.map +1 -0
- package/dist/esm/utils/textDiff.js +42 -0
- package/dist/esm/utils/textDiff.js.map +1 -0
- package/dist/esm/utils/units.js +152 -0
- package/dist/esm/utils/units.js.map +1 -0
- package/dist/esm/utils/validation.js +285 -0
- package/dist/esm/utils/validation.js.map +1 -0
- package/dist/esm/utils/xmlSanitization.js +54 -0
- package/dist/esm/utils/xmlSanitization.js.map +1 -0
- package/dist/esm/validation/RevisionAutoFixer.js +340 -0
- package/dist/esm/validation/RevisionAutoFixer.js.map +1 -0
- package/dist/esm/validation/RevisionValidator.js +240 -0
- package/dist/esm/validation/RevisionValidator.js.map +1 -0
- package/dist/esm/validation/ValidationRuleRegistry.js +40 -0
- package/dist/esm/validation/ValidationRuleRegistry.js.map +1 -0
- package/dist/esm/validation/ValidationRules.js +92 -0
- package/dist/esm/validation/ValidationRules.js.map +1 -0
- package/dist/esm/validation/index.js +4 -0
- package/dist/esm/validation/index.js.map +1 -0
- package/dist/esm/xml/XMLBuilder.js +434 -0
- package/dist/esm/xml/XMLBuilder.js.map +1 -0
- package/dist/esm/xml/XMLParser.js +486 -0
- package/dist/esm/xml/XMLParser.js.map +1 -0
- package/dist/esm/zip/ZipHandler.js +298 -0
- package/dist/esm/zip/ZipHandler.js.map +1 -0
- package/dist/esm/zip/ZipReader.js +147 -0
- package/dist/esm/zip/ZipReader.js.map +1 -0
- package/dist/esm/zip/ZipWriter.js +199 -0
- package/dist/esm/zip/ZipWriter.js.map +1 -0
- package/dist/esm/zip/errors.js +43 -0
- package/dist/esm/zip/errors.js.map +1 -0
- package/dist/esm/zip/types.js +31 -0
- package/dist/esm/zip/types.js.map +1 -0
- package/dist/formatting/AbstractNumbering.d.ts +2 -2
- package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
- package/dist/formatting/AbstractNumbering.js +33 -33
- package/dist/formatting/AbstractNumbering.js.map +1 -1
- package/dist/formatting/NumberingInstance.d.ts +2 -2
- package/dist/formatting/NumberingInstance.d.ts.map +1 -1
- package/dist/formatting/NumberingInstance.js +7 -7
- package/dist/formatting/NumberingInstance.js.map +1 -1
- package/dist/formatting/NumberingLevel.d.ts +11 -2
- package/dist/formatting/NumberingLevel.d.ts.map +1 -1
- package/dist/formatting/NumberingLevel.js +111 -25
- package/dist/formatting/NumberingLevel.js.map +1 -1
- package/dist/formatting/NumberingManager.d.ts +4 -4
- package/dist/formatting/NumberingManager.d.ts.map +1 -1
- package/dist/formatting/NumberingManager.js +28 -28
- package/dist/formatting/NumberingManager.js.map +1 -1
- package/dist/formatting/Style.d.ts +14 -7
- package/dist/formatting/Style.d.ts.map +1 -1
- package/dist/formatting/Style.js +309 -112
- package/dist/formatting/Style.js.map +1 -1
- package/dist/formatting/StylesManager.d.ts +2 -2
- package/dist/formatting/StylesManager.d.ts.map +1 -1
- package/dist/formatting/StylesManager.js +52 -52
- package/dist/formatting/StylesManager.js.map +1 -1
- package/dist/helpers/CleanupHelper.d.ts +1 -1
- package/dist/helpers/CleanupHelper.d.ts.map +1 -1
- package/dist/helpers/CleanupHelper.js +15 -15
- package/dist/helpers/CleanupHelper.js.map +1 -1
- package/dist/index.d.ts +81 -90
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +286 -317
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +16 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +42 -0
- package/dist/internal.js.map +1 -0
- package/dist/managers/DrawingManager.d.ts +3 -3
- package/dist/managers/DrawingManager.d.ts.map +1 -1
- package/dist/managers/DrawingManager.js +12 -12
- package/dist/managers/DrawingManager.js.map +1 -1
- package/dist/{utils → processors}/ChangelogGenerator.d.ts +2 -2
- package/dist/processors/ChangelogGenerator.d.ts.map +1 -0
- package/dist/{utils → processors}/ChangelogGenerator.js +2 -2
- package/dist/processors/ChangelogGenerator.js.map +1 -0
- package/dist/processors/CompatibilityUpgrader.d.ts.map +1 -0
- package/dist/{utils → processors}/CompatibilityUpgrader.js +10 -10
- package/dist/processors/CompatibilityUpgrader.js.map +1 -0
- package/dist/{utils → processors}/InMemoryRevisionAcceptor.d.ts +3 -3
- package/dist/processors/InMemoryRevisionAcceptor.d.ts.map +1 -0
- package/dist/{utils → processors}/InMemoryRevisionAcceptor.js +84 -27
- package/dist/processors/InMemoryRevisionAcceptor.js.map +1 -0
- package/dist/{utils → processors}/MoveOperationHelper.d.ts +4 -4
- package/dist/processors/MoveOperationHelper.d.ts.map +1 -0
- package/dist/{utils → processors}/MoveOperationHelper.js +10 -10
- package/dist/processors/MoveOperationHelper.js.map +1 -0
- package/dist/{utils → processors}/RevisionAwareProcessor.d.ts +3 -3
- package/dist/processors/RevisionAwareProcessor.d.ts.map +1 -0
- package/dist/{utils → processors}/RevisionAwareProcessor.js +2 -2
- package/dist/processors/RevisionAwareProcessor.js.map +1 -0
- package/dist/{utils → processors}/RevisionWalker.d.ts +2 -1
- package/dist/processors/RevisionWalker.d.ts.map +1 -0
- package/dist/{utils → processors}/RevisionWalker.js +28 -0
- package/dist/processors/RevisionWalker.js.map +1 -0
- package/dist/{utils → processors}/SelectiveRevisionAcceptor.d.ts +4 -3
- package/dist/processors/SelectiveRevisionAcceptor.d.ts.map +1 -0
- package/dist/processors/SelectiveRevisionAcceptor.js +402 -0
- package/dist/processors/SelectiveRevisionAcceptor.js.map +1 -0
- package/dist/processors/ShadingResolver.d.ts +6 -0
- package/dist/processors/ShadingResolver.d.ts.map +1 -0
- package/dist/{utils → processors}/ShadingResolver.js +2 -2
- package/dist/processors/ShadingResolver.js.map +1 -0
- package/dist/{utils → processors}/acceptRevisions.d.ts +1 -1
- package/dist/processors/acceptRevisions.d.ts.map +1 -0
- package/dist/{utils → processors}/acceptRevisions.js +24 -4
- package/dist/processors/acceptRevisions.js.map +1 -0
- package/dist/{utils → processors}/cnfStyleDecoder.d.ts +1 -1
- package/dist/processors/cnfStyleDecoder.d.ts.map +1 -0
- package/dist/processors/cnfStyleDecoder.js.map +1 -0
- package/dist/processors/stripTrackedChanges.d.ts +3 -0
- package/dist/processors/stripTrackedChanges.d.ts.map +1 -0
- package/dist/{utils → processors}/stripTrackedChanges.js +16 -6
- package/dist/processors/stripTrackedChanges.js.map +1 -0
- package/dist/tracking/DocumentTrackingContext.d.ts +4 -4
- package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
- package/dist/tracking/DocumentTrackingContext.js +38 -43
- package/dist/tracking/DocumentTrackingContext.js.map +1 -1
- package/dist/tracking/TrackingContext.d.ts +8 -8
- package/dist/tracking/TrackingContext.d.ts.map +1 -1
- package/dist/tracking/TrackingContext.js.map +1 -1
- package/dist/types/document-types.d.ts +28 -0
- package/dist/types/document-types.d.ts.map +1 -0
- package/dist/types/document-types.js +3 -0
- package/dist/types/document-types.js.map +1 -0
- package/dist/types/formatting.d.ts +4 -4
- package/dist/types/formatting.d.ts.map +1 -1
- package/dist/types/formatting.js.map +1 -1
- package/dist/types/settings-types.d.ts +6 -0
- package/dist/types/settings-types.d.ts.map +1 -1
- package/dist/types/settings-types.js.map +1 -1
- package/dist/utils/KeyedRegistry.d.ts +13 -0
- package/dist/utils/KeyedRegistry.d.ts.map +1 -0
- package/dist/utils/KeyedRegistry.js +36 -0
- package/dist/utils/KeyedRegistry.js.map +1 -0
- package/dist/utils/corruptionDetection.d.ts +1 -1
- package/dist/utils/corruptionDetection.d.ts.map +1 -1
- package/dist/utils/corruptionDetection.js +4 -4
- package/dist/utils/corruptionDetection.js.map +1 -1
- package/dist/utils/deepEqual.d.ts +2 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +50 -0
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/list-detection.d.ts +2 -2
- package/dist/utils/list-detection.d.ts.map +1 -1
- package/dist/utils/list-detection.js.map +1 -1
- package/dist/utils/parsingHelpers.d.ts +1 -1
- package/dist/utils/parsingHelpers.d.ts.map +1 -1
- package/dist/utils/parsingHelpers.js +2 -2
- package/dist/utils/parsingHelpers.js.map +1 -1
- package/dist/utils/validation.js +7 -7
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/xmlSanitization.js +2 -2
- package/dist/utils/xmlSanitization.js.map +1 -1
- package/dist/validation/RevisionAutoFixer.d.ts +4 -4
- package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
- package/dist/validation/RevisionAutoFixer.js +11 -11
- package/dist/validation/RevisionAutoFixer.js.map +1 -1
- package/dist/validation/RevisionValidator.d.ts +5 -4
- package/dist/validation/RevisionValidator.d.ts.map +1 -1
- package/dist/validation/RevisionValidator.js +29 -30
- package/dist/validation/RevisionValidator.js.map +1 -1
- package/dist/validation/ValidationRuleRegistry.d.ts +27 -0
- package/dist/validation/ValidationRuleRegistry.d.ts.map +1 -0
- package/dist/validation/ValidationRuleRegistry.js +43 -0
- package/dist/validation/ValidationRuleRegistry.js.map +1 -0
- package/dist/validation/index.d.ts +3 -3
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +10 -10
- package/dist/validation/index.js.map +1 -1
- package/dist/xml/XMLBuilder.d.ts +6 -1
- package/dist/xml/XMLBuilder.d.ts.map +1 -1
- package/dist/xml/XMLBuilder.js +11 -6
- package/dist/xml/XMLBuilder.js.map +1 -1
- package/dist/xml/XMLParser.js +6 -6
- package/dist/xml/XMLParser.js.map +1 -1
- package/dist/zip/ZipHandler.d.ts +1 -1
- package/dist/zip/ZipHandler.d.ts.map +1 -1
- package/dist/zip/ZipHandler.js +8 -8
- package/dist/zip/ZipHandler.js.map +1 -1
- package/dist/zip/ZipReader.d.ts +1 -1
- package/dist/zip/ZipReader.d.ts.map +1 -1
- package/dist/zip/ZipReader.js +14 -14
- package/dist/zip/ZipReader.js.map +1 -1
- package/dist/zip/ZipWriter.d.ts +1 -1
- package/dist/zip/ZipWriter.d.ts.map +1 -1
- package/dist/zip/ZipWriter.js +10 -10
- package/dist/zip/ZipWriter.js.map +1 -1
- package/package.json +20 -4
- package/src/constants/legacyCompatFlags.ts +1 -1
- package/src/core/Document.ts +478 -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 +2235 -620
- package/src/core/DocumentValidator.ts +7 -7
- package/src/core/ElementRegistry.ts +69 -0
- package/src/core/Relationship.ts +1 -1
- package/src/core/RelationshipManager.ts +4 -4
- package/src/elements/AlternateContent.ts +1 -1
- package/src/elements/Bookmark.ts +52 -4
- package/src/elements/BookmarkManager.ts +2 -2
- package/src/elements/Comment.ts +3 -3
- package/src/elements/CommentManager.ts +4 -4
- package/src/elements/CommonTypes.ts +45 -7
- package/src/elements/CustomXml.ts +1 -1
- package/src/elements/Endnote.ts +2 -2
- package/src/elements/EndnoteManager.ts +3 -3
- package/src/elements/Field.ts +44 -10
- package/src/elements/FieldHelpers.ts +2 -2
- package/src/elements/Footer.ts +4 -4
- package/src/elements/Footnote.ts +2 -2
- package/src/elements/FootnoteManager.ts +3 -3
- package/src/elements/Header.ts +4 -4
- package/src/elements/HeaderFooterManager.ts +2 -2
- package/src/elements/Hyperlink.ts +16 -12
- package/src/elements/Image.ts +3 -3
- package/src/elements/ImageManager.ts +2 -2
- package/src/elements/ImageRun.ts +3 -3
- package/src/elements/MathElement.ts +1 -1
- package/src/elements/Paragraph.ts +221 -88
- package/src/elements/PreservedElement.ts +1 -1
- package/src/elements/PropertyChangeTypes.ts +2 -2
- package/src/elements/RangeMarker.ts +153 -12
- package/src/elements/RegisteredBodyElement.ts +52 -0
- package/src/elements/Revision.ts +14 -14
- package/src/elements/RevisionContent.ts +3 -3
- package/src/elements/RevisionManager.ts +3 -3
- package/src/elements/Run.ts +221 -94
- package/src/elements/Section.ts +136 -69
- package/src/elements/Shape.ts +4 -4
- package/src/elements/StructuredDocumentTag.ts +3 -3
- package/src/elements/Table.ts +91 -27
- package/src/elements/TableCell.ts +62 -34
- package/src/elements/TableGridChange.ts +1 -1
- package/src/elements/TableOfContents.ts +1 -1
- package/src/elements/TableOfContentsElement.ts +2 -2
- package/src/elements/TableRow.ts +192 -48
- package/src/elements/TextBox.ts +5 -5
- package/src/formatting/AbstractNumbering.ts +3 -3
- package/src/formatting/NumberingInstance.ts +2 -2
- package/src/formatting/NumberingLevel.ts +201 -10
- package/src/formatting/NumberingManager.ts +5 -5
- package/src/formatting/Style.ts +382 -86
- package/src/formatting/StylesManager.ts +4 -4
- package/src/helpers/CleanupHelper.ts +6 -6
- package/src/index.ts +118 -127
- package/src/internal.ts +79 -0
- package/src/managers/DrawingManager.ts +3 -3
- package/src/{utils → processors}/ChangelogGenerator.ts +3 -3
- package/src/{utils → processors}/CompatibilityUpgrader.ts +2 -2
- package/src/{utils → processors}/InMemoryRevisionAcceptor.ts +100 -12
- package/src/{utils → processors}/MoveOperationHelper.ts +5 -5
- package/src/{utils → processors}/RevisionAwareProcessor.ts +3 -3
- package/src/{utils → processors}/RevisionWalker.ts +42 -1
- package/src/{utils → processors}/SelectiveRevisionAcceptor.ts +98 -39
- package/src/{utils → processors}/ShadingResolver.ts +5 -5
- package/src/{utils → processors}/acceptRevisions.ts +77 -9
- package/src/{utils → processors}/cnfStyleDecoder.ts +1 -1
- package/src/{utils → processors}/stripTrackedChanges.ts +35 -10
- package/src/tracking/DocumentTrackingContext.ts +12 -14
- package/src/tracking/TrackingContext.ts +8 -8
- package/src/types/document-types.ts +53 -0
- package/src/types/formatting.ts +4 -4
- package/src/types/settings-types.ts +32 -0
- package/src/utils/KeyedRegistry.ts +41 -0
- package/src/utils/corruptionDetection.ts +2 -2
- package/src/utils/deepEqual.ts +58 -0
- package/src/utils/list-detection.ts +2 -2
- package/src/utils/parsingHelpers.ts +11 -3
- package/src/utils/validation.ts +3 -3
- package/src/utils/xmlSanitization.ts +1 -1
- package/src/validation/RevisionAutoFixer.ts +5 -5
- package/src/validation/RevisionValidator.ts +39 -28
- package/src/validation/ValidationRuleRegistry.ts +86 -0
- package/src/validation/index.ts +3 -3
- package/src/xml/XMLBuilder.ts +13 -3
- package/src/xml/XMLParser.ts +2 -2
- package/src/zip/ZipHandler.ts +4 -4
- package/src/zip/ZipReader.ts +3 -3
- package/src/zip/ZipWriter.ts +3 -3
- package/dist/utils/ChangelogGenerator.d.ts.map +0 -1
- package/dist/utils/ChangelogGenerator.js.map +0 -1
- package/dist/utils/CompatibilityUpgrader.d.ts.map +0 -1
- package/dist/utils/CompatibilityUpgrader.js.map +0 -1
- package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +0 -1
- package/dist/utils/InMemoryRevisionAcceptor.js.map +0 -1
- package/dist/utils/MoveOperationHelper.d.ts.map +0 -1
- package/dist/utils/MoveOperationHelper.js.map +0 -1
- package/dist/utils/RevisionAwareProcessor.d.ts.map +0 -1
- package/dist/utils/RevisionAwareProcessor.js.map +0 -1
- package/dist/utils/RevisionWalker.d.ts.map +0 -1
- package/dist/utils/RevisionWalker.js.map +0 -1
- package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +0 -1
- package/dist/utils/SelectiveRevisionAcceptor.js.map +0 -1
- package/dist/utils/ShadingResolver.d.ts +0 -6
- package/dist/utils/ShadingResolver.d.ts.map +0 -1
- package/dist/utils/ShadingResolver.js.map +0 -1
- package/dist/utils/acceptRevisions.d.ts.map +0 -1
- package/dist/utils/acceptRevisions.js.map +0 -1
- package/dist/utils/cnfStyleDecoder.d.ts.map +0 -1
- package/dist/utils/cnfStyleDecoder.js.map +0 -1
- package/dist/utils/stripTrackedChanges.d.ts +0 -3
- package/dist/utils/stripTrackedChanges.d.ts.map +0 -1
- package/dist/utils/stripTrackedChanges.js.map +0 -1
- package/src/__tests__/helper-methods.test.ts +0 -512
- package/src/constants/CLAUDE.md +0 -28
- package/src/core/CLAUDE.md +0 -113
- package/src/elements/CLAUDE.md +0 -142
- package/src/formatting/CLAUDE.md +0 -78
- package/src/managers/CLAUDE.md +0 -47
- package/src/tracking/CLAUDE.md +0 -30
- package/src/types/CLAUDE.md +0 -39
- package/src/utils/CLAUDE.md +0 -168
- package/src/validation/CLAUDE.md +0 -40
- package/src/xml/CLAUDE.md +0 -65
- package/src/zip/CLAUDE.md +0 -55
- /package/dist/{utils → processors}/CompatibilityUpgrader.d.ts +0 -0
- /package/dist/{utils → processors}/cnfStyleDecoder.js +0 -0
|
@@ -3,24 +3,26 @@
|
|
|
3
3
|
* Extracts content from ZIP archives and converts XML to structured data
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { AlternateContent } from '../elements/AlternateContent';
|
|
7
|
-
import { Bookmark } from '../elements/Bookmark';
|
|
8
|
-
import { Endnote, EndnoteType } from '../elements/Endnote';
|
|
9
|
-
import { Footnote, FootnoteType } from '../elements/Footnote';
|
|
10
|
-
import { BookmarkManager } from '../elements/BookmarkManager';
|
|
11
|
-
import { Comment } from '../elements/Comment';
|
|
12
|
-
import { CustomXmlBlock } from '../elements/CustomXml';
|
|
13
|
-
import { PreservedElement } from '../elements/PreservedElement';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
6
|
+
import { AlternateContent } from '../elements/AlternateContent.js';
|
|
7
|
+
import { Bookmark } from '../elements/Bookmark.js';
|
|
8
|
+
import { Endnote, EndnoteType } from '../elements/Endnote.js';
|
|
9
|
+
import { Footnote, FootnoteType } from '../elements/Footnote.js';
|
|
10
|
+
import { BookmarkManager } from '../elements/BookmarkManager.js';
|
|
11
|
+
import { Comment } from '../elements/Comment.js';
|
|
12
|
+
import { CustomXmlBlock } from '../elements/CustomXml.js';
|
|
13
|
+
import { PreservedElement } from '../elements/PreservedElement.js';
|
|
14
|
+
import { RegisteredBodyElement } from '../elements/RegisteredBodyElement.js';
|
|
15
|
+
import { ElementRegistry } from './ElementRegistry.js';
|
|
16
|
+
import { MathParagraph } from '../elements/MathElement.js';
|
|
17
|
+
import { ComplexField, Field } from '../elements/Field.js';
|
|
18
|
+
import { isHyperlinkInstruction, parseHyperlinkInstruction } from '../elements/FieldHelpers.js';
|
|
19
|
+
import { Footer } from '../elements/Footer.js';
|
|
20
|
+
import { Header } from '../elements/Header.js';
|
|
21
|
+
import { Hyperlink } from '../elements/Hyperlink.js';
|
|
22
|
+
import { ImageManager } from '../elements/ImageManager.js';
|
|
23
|
+
import { ImageRun } from '../elements/ImageRun.js';
|
|
24
|
+
import { Paragraph, ParagraphFormatting, ParagraphContent } from '../elements/Paragraph.js';
|
|
25
|
+
import { Revision } from '../elements/Revision.js';
|
|
24
26
|
import {
|
|
25
27
|
BreakType,
|
|
26
28
|
FormFieldCheckBox,
|
|
@@ -30,35 +32,40 @@ import {
|
|
|
30
32
|
Run,
|
|
31
33
|
RunContent,
|
|
32
34
|
RunFormatting,
|
|
33
|
-
} from '../elements/Run';
|
|
34
|
-
import { Section, SectionProperties, SectionType } from '../elements/Section';
|
|
35
|
-
import { StructuredDocumentTag } from '../elements/StructuredDocumentTag';
|
|
36
|
-
import { Table, TableBorder } from '../elements/Table';
|
|
37
|
-
import { TableCell } from '../elements/TableCell';
|
|
38
|
-
import { TableOfContents } from '../elements/TableOfContents';
|
|
39
|
-
import { TableOfContentsElement } from '../elements/TableOfContentsElement';
|
|
40
|
-
import { TableGridChange } from '../elements/TableGridChange';
|
|
41
|
-
import { TableRow } from '../elements/TableRow';
|
|
42
|
-
import { AbstractNumbering } from '../formatting/AbstractNumbering';
|
|
43
|
-
import { NumberingInstance } from '../formatting/NumberingInstance';
|
|
44
|
-
import { Style, StyleProperties, StyleType } from '../formatting/Style';
|
|
45
|
-
import { logParagraphContent, logParsing, logTextDirection } from '../utils/diagnostics';
|
|
46
|
-
import { getGlobalLogger, createScopedLogger, ILogger, defaultLogger } from '../utils/logger';
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
} from '../elements/Run.js';
|
|
36
|
+
import { Section, SectionProperties, SectionType } from '../elements/Section.js';
|
|
37
|
+
import { StructuredDocumentTag } from '../elements/StructuredDocumentTag.js';
|
|
38
|
+
import { Table, TableBorder } from '../elements/Table.js';
|
|
39
|
+
import { TableCell } from '../elements/TableCell.js';
|
|
40
|
+
import { TableOfContents } from '../elements/TableOfContents.js';
|
|
41
|
+
import { TableOfContentsElement } from '../elements/TableOfContentsElement.js';
|
|
42
|
+
import { TableGridChange } from '../elements/TableGridChange.js';
|
|
43
|
+
import { TableRow } from '../elements/TableRow.js';
|
|
44
|
+
import { AbstractNumbering } from '../formatting/AbstractNumbering.js';
|
|
45
|
+
import { NumberingInstance } from '../formatting/NumberingInstance.js';
|
|
46
|
+
import { Style, StyleProperties, StyleType } from '../formatting/Style.js';
|
|
47
|
+
import { logParagraphContent, logParsing, logTextDirection } from '../utils/diagnostics.js';
|
|
48
|
+
import { getGlobalLogger, createScopedLogger, ILogger, defaultLogger } from '../utils/logger.js';
|
|
49
|
+
import {
|
|
50
|
+
safeParseInt,
|
|
51
|
+
isExplicitlySet,
|
|
52
|
+
parseOoxmlBoolean,
|
|
53
|
+
parseOnOffAttribute,
|
|
54
|
+
} from '../utils/parsingHelpers.js';
|
|
55
|
+
import { halfPointsToPoints } from '../utils/units.js';
|
|
56
|
+
import type { ShadingConfig } from '../elements/CommonTypes.js';
|
|
50
57
|
|
|
51
58
|
// Create scoped logger for DocumentParser operations
|
|
52
59
|
function getLogger(): ILogger {
|
|
53
60
|
return createScopedLogger(getGlobalLogger(), 'DocumentParser');
|
|
54
61
|
}
|
|
55
|
-
import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
|
|
56
|
-
import { XMLParser } from '../xml/XMLParser';
|
|
57
|
-
import { ZipHandler } from '../zip/ZipHandler';
|
|
58
|
-
import { DOCX_PATHS } from '../zip/types';
|
|
59
|
-
import { DocumentProperties } from '
|
|
60
|
-
import { BodyElement } from './DocumentContent';
|
|
61
|
-
import { RelationshipManager } from './RelationshipManager';
|
|
62
|
+
import { XMLBuilder, XMLElement } from '../xml/XMLBuilder.js';
|
|
63
|
+
import { XMLParser } from '../xml/XMLParser.js';
|
|
64
|
+
import { ZipHandler } from '../zip/ZipHandler.js';
|
|
65
|
+
import { DOCX_PATHS } from '../zip/types.js';
|
|
66
|
+
import type { DocumentProperties } from '../types/document-types.js';
|
|
67
|
+
import { BodyElement } from './DocumentContent.js';
|
|
68
|
+
import { RelationshipManager } from './RelationshipManager.js';
|
|
62
69
|
|
|
63
70
|
/**
|
|
64
71
|
* Parse error tracking
|
|
@@ -242,7 +249,7 @@ export class DocumentParser {
|
|
|
242
249
|
const nextCXml = this.findNextTopLevelTag(bodyContent, 'w:customXml', pos);
|
|
243
250
|
const nextAltChunk = this.findNextTopLevelTag(bodyContent, 'w:altChunk', pos);
|
|
244
251
|
|
|
245
|
-
const candidates = [];
|
|
252
|
+
const candidates: { type: string; pos: number; registeredTag?: string }[] = [];
|
|
246
253
|
if (nextP !== -1) candidates.push({ type: 'p', pos: nextP });
|
|
247
254
|
if (nextTbl !== -1) candidates.push({ type: 'tbl', pos: nextTbl });
|
|
248
255
|
if (nextSdt !== -1) candidates.push({ type: 'sdt', pos: nextSdt });
|
|
@@ -251,6 +258,15 @@ export class DocumentParser {
|
|
|
251
258
|
if (nextCXml !== -1) candidates.push({ type: 'customXml', pos: nextCXml });
|
|
252
259
|
if (nextAltChunk !== -1) candidates.push({ type: 'altChunk', pos: nextAltChunk });
|
|
253
260
|
|
|
261
|
+
// ElementRegistry plugin tags — consumer-registered handlers for
|
|
262
|
+
// qualified-name body elements outside the framework's native set.
|
|
263
|
+
// We scan each registered tag at every step so the position-based
|
|
264
|
+
// dispatch picks up registered elements interleaved with native ones.
|
|
265
|
+
for (const tag of ElementRegistry.registeredTags()) {
|
|
266
|
+
const p = this.findNextTopLevelTag(bodyContent, tag, pos);
|
|
267
|
+
if (p !== -1) candidates.push({ type: 'registered', pos: p, registeredTag: tag });
|
|
268
|
+
}
|
|
269
|
+
|
|
254
270
|
if (candidates.length === 0) break;
|
|
255
271
|
|
|
256
272
|
candidates.sort((a, b) => a.pos - b.pos);
|
|
@@ -399,6 +415,29 @@ export class DocumentParser {
|
|
|
399
415
|
} else {
|
|
400
416
|
pos = next.pos + 1;
|
|
401
417
|
}
|
|
418
|
+
} else if (next.type === 'registered' && next.registeredTag) {
|
|
419
|
+
// Consumer-registered element via ElementRegistry. Hand the raw XML
|
|
420
|
+
// to the handler's parse(); on save the model is round-tripped via
|
|
421
|
+
// handler.serialize(). A throwing parse degrades gracefully to a
|
|
422
|
+
// PreservedElement so a buggy custom parser cannot fail the load.
|
|
423
|
+
const tag = next.registeredTag;
|
|
424
|
+
const elementXml = this.extractSingleElement(bodyContent, tag, next.pos);
|
|
425
|
+
if (elementXml) {
|
|
426
|
+
const handler = ElementRegistry.get(tag);
|
|
427
|
+
if (handler) {
|
|
428
|
+
try {
|
|
429
|
+
const model = handler.parse(elementXml);
|
|
430
|
+
bodyElements.push(new RegisteredBodyElement(tag, model, handler, elementXml));
|
|
431
|
+
} catch {
|
|
432
|
+
bodyElements.push(new PreservedElement(elementXml, tag, 'block'));
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
bodyElements.push(new PreservedElement(elementXml, tag, 'block'));
|
|
436
|
+
}
|
|
437
|
+
pos = next.pos + elementXml.length;
|
|
438
|
+
} else {
|
|
439
|
+
pos = next.pos + 1;
|
|
440
|
+
}
|
|
402
441
|
}
|
|
403
442
|
|
|
404
443
|
// Attach any pending body-level bookmarkStarts to the just-parsed element
|
|
@@ -507,10 +546,18 @@ export class DocumentParser {
|
|
|
507
546
|
if (idAttr) {
|
|
508
547
|
const id = parseInt(idAttr, 10);
|
|
509
548
|
if (!isNaN(id)) {
|
|
549
|
+
// CT_MarkupRange §17.13.5 — preserve w:displacedByCustomXml.
|
|
550
|
+
const displacedAttr = XMLParser.extractAttribute(
|
|
551
|
+
bookmarkEndXml,
|
|
552
|
+
'w:displacedByCustomXml'
|
|
553
|
+
);
|
|
554
|
+
const displacedByCustomXml =
|
|
555
|
+
displacedAttr === 'next' || displacedAttr === 'prev' ? displacedAttr : undefined;
|
|
510
556
|
const bookmark = new Bookmark({
|
|
511
557
|
name: `_end_${id}`,
|
|
512
558
|
id: id,
|
|
513
559
|
skipNormalization: true,
|
|
560
|
+
displacedByCustomXml,
|
|
514
561
|
});
|
|
515
562
|
bookmarks.push(bookmark);
|
|
516
563
|
}
|
|
@@ -905,14 +952,27 @@ export class DocumentParser {
|
|
|
905
952
|
}
|
|
906
953
|
}
|
|
907
954
|
|
|
908
|
-
// Parse w14:paraId and w14:textId
|
|
909
|
-
|
|
955
|
+
// Parse w14:paraId and w14:textId (Word 2010+ paragraph identifiers
|
|
956
|
+
// per MC-DOCX §2.6.19, ST_LongHexNumber — 8-char hex string). These
|
|
957
|
+
// are XML *attributes* on w:p, so XMLParser stores them under the
|
|
958
|
+
// @_-prefixed keys. The previous lookup (`pElement['w14:paraId']`)
|
|
959
|
+
// accessed an element-shaped key that never exists, silently
|
|
960
|
+
// dropping both IDs on every load → save cycle. XMLParser's
|
|
961
|
+
// numeric coercion of purely-digit hex strings (e.g. "00000001" →
|
|
962
|
+
// 1) means we normalise back to the zero-padded 8-char form so
|
|
963
|
+
// validators accept the output.
|
|
964
|
+
const normaliseHexId = (raw: unknown): string | undefined => {
|
|
965
|
+
if (raw === undefined || raw === null) return undefined;
|
|
966
|
+
const asStr = typeof raw === 'number' ? raw.toString(16) : String(raw);
|
|
967
|
+
return asStr.toUpperCase().padStart(8, '0');
|
|
968
|
+
};
|
|
969
|
+
const paraId = normaliseHexId(pElement['@_w14:paraId']);
|
|
910
970
|
if (paraId) {
|
|
911
|
-
paragraph.formatting.paraId = paraId
|
|
971
|
+
paragraph.formatting.paraId = paraId;
|
|
912
972
|
}
|
|
913
|
-
const textId = pElement['
|
|
973
|
+
const textId = normaliseHexId(pElement['@_w14:textId']);
|
|
914
974
|
if (textId) {
|
|
915
|
-
paragraph.formatting.textId = textId
|
|
975
|
+
paragraph.formatting.textId = textId;
|
|
916
976
|
}
|
|
917
977
|
|
|
918
978
|
// CRITICAL FIX: Preserve document order of paragraph children (runs, hyperlinks, fields)
|
|
@@ -1344,9 +1404,61 @@ export class DocumentParser {
|
|
|
1344
1404
|
hyperlinkObj['w:moveFrom'] ||
|
|
1345
1405
|
hyperlinkObj['w:moveTo'];
|
|
1346
1406
|
if (hasRevisionChildren) {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1407
|
+
// Flatten revisions to make hyperlink editable (setUrl/setText).
|
|
1408
|
+
// Trades revision fidelity inside the hyperlink for editability.
|
|
1409
|
+
const flattenedObj = { ...hyperlinkObj };
|
|
1410
|
+
const allRuns: any[] = [];
|
|
1411
|
+
|
|
1412
|
+
// Keep existing direct runs
|
|
1413
|
+
if (flattenedObj['w:r']) {
|
|
1414
|
+
const directRuns = Array.isArray(flattenedObj['w:r'])
|
|
1415
|
+
? flattenedObj['w:r']
|
|
1416
|
+
: [flattenedObj['w:r']];
|
|
1417
|
+
allRuns.push(...directRuns);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// Unwrap w:ins runs (inserted content — keep)
|
|
1421
|
+
if (flattenedObj['w:ins']) {
|
|
1422
|
+
const insArr = Array.isArray(flattenedObj['w:ins'])
|
|
1423
|
+
? flattenedObj['w:ins']
|
|
1424
|
+
: [flattenedObj['w:ins']];
|
|
1425
|
+
for (const ins of insArr) {
|
|
1426
|
+
if (ins['w:r']) {
|
|
1427
|
+
const insRuns = Array.isArray(ins['w:r']) ? ins['w:r'] : [ins['w:r']];
|
|
1428
|
+
allRuns.push(...insRuns);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// Unwrap w:moveTo runs (move destination — keep)
|
|
1434
|
+
if (flattenedObj['w:moveTo']) {
|
|
1435
|
+
const moveToArr = Array.isArray(flattenedObj['w:moveTo'])
|
|
1436
|
+
? flattenedObj['w:moveTo']
|
|
1437
|
+
: [flattenedObj['w:moveTo']];
|
|
1438
|
+
for (const mt of moveToArr) {
|
|
1439
|
+
if (mt['w:r']) {
|
|
1440
|
+
const mtRuns = Array.isArray(mt['w:r']) ? mt['w:r'] : [mt['w:r']];
|
|
1441
|
+
allRuns.push(...mtRuns);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// Drop w:del and w:moveFrom (deleted/moved-away content)
|
|
1447
|
+
flattenedObj['w:r'] = allRuns.length > 0 ? allRuns : undefined;
|
|
1448
|
+
delete flattenedObj['w:del'];
|
|
1449
|
+
delete flattenedObj['w:ins'];
|
|
1450
|
+
delete flattenedObj['w:moveFrom'];
|
|
1451
|
+
delete flattenedObj['w:moveTo'];
|
|
1452
|
+
|
|
1453
|
+
const result = this.parseHyperlinkFromObject(flattenedObj, relationshipManager);
|
|
1454
|
+
if (result.hyperlink) {
|
|
1455
|
+
paragraph.addHyperlink(result.hyperlink);
|
|
1456
|
+
}
|
|
1457
|
+
for (const bookmark of result.bookmarkStarts) {
|
|
1458
|
+
paragraph.addBookmarkStart(bookmark);
|
|
1459
|
+
}
|
|
1460
|
+
for (const bookmark of result.bookmarkEnds) {
|
|
1461
|
+
paragraph.addBookmarkEnd(bookmark);
|
|
1350
1462
|
}
|
|
1351
1463
|
} else {
|
|
1352
1464
|
const result = this.parseHyperlinkFromObject(hyperlinkObj, relationshipManager);
|
|
@@ -1481,7 +1593,7 @@ export class DocumentParser {
|
|
|
1481
1593
|
} = { revision: null, bookmarkStarts: [], bookmarkEnds: [] };
|
|
1482
1594
|
try {
|
|
1483
1595
|
// Map XML tag to RevisionType
|
|
1484
|
-
let revisionType: import('../elements/Revision').RevisionType;
|
|
1596
|
+
let revisionType: import('../elements/Revision.js').RevisionType;
|
|
1485
1597
|
switch (tagName) {
|
|
1486
1598
|
case 'w:ins':
|
|
1487
1599
|
revisionType = 'insert';
|
|
@@ -1529,7 +1641,7 @@ export class DocumentParser {
|
|
|
1529
1641
|
const runXmls = XMLParser.extractElements(xmlWithoutHyperlinks, 'w:r');
|
|
1530
1642
|
|
|
1531
1643
|
// Use RevisionContent to hold both Run and Hyperlink objects
|
|
1532
|
-
const content: import('../elements/RevisionContent').RevisionContent[] = [];
|
|
1644
|
+
const content: import('../elements/RevisionContent.js').RevisionContent[] = [];
|
|
1533
1645
|
|
|
1534
1646
|
// Parse standalone runs (not inside hyperlinks)
|
|
1535
1647
|
for (const runXml of runXmls) {
|
|
@@ -1675,8 +1787,8 @@ export class DocumentParser {
|
|
|
1675
1787
|
const id = parseInt(idAttr, 10);
|
|
1676
1788
|
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
1677
1789
|
const parentId = parentIdAttr ? parseInt(parentIdAttr, 10) : undefined;
|
|
1678
|
-
// Per ECMA-376, w:done
|
|
1679
|
-
const done = doneAttr
|
|
1790
|
+
// Per ECMA-376 §17.17.4, w:done is ST_OnOff — accept 1/0/true/false/on/off
|
|
1791
|
+
const done = parseOnOffAttribute(doneAttr);
|
|
1680
1792
|
|
|
1681
1793
|
// Parse content (runs from paragraphs within the comment)
|
|
1682
1794
|
const runs: Run[] = [];
|
|
@@ -1869,12 +1981,20 @@ export class DocumentParser {
|
|
|
1869
1981
|
// Parse optional column range for table bookmarks (ECMA-376 §17.16.5)
|
|
1870
1982
|
const colFirstAttr = XMLParser.extractAttribute(bookmarkXml, 'w:colFirst');
|
|
1871
1983
|
const colLastAttr = XMLParser.extractAttribute(bookmarkXml, 'w:colLast');
|
|
1984
|
+
// Parse optional w:displacedByCustomXml per CT_MarkupRange (§17.13.5).
|
|
1985
|
+
// Without this the attribute was dropped on load, so any Word document
|
|
1986
|
+
// with custom-XML-displaced bookmarks lost the disambiguator even
|
|
1987
|
+
// though the model now supports round-tripping it.
|
|
1988
|
+
const displacedAttr = XMLParser.extractAttribute(bookmarkXml, 'w:displacedByCustomXml');
|
|
1989
|
+
const displacedByCustomXml =
|
|
1990
|
+
displacedAttr === 'next' || displacedAttr === 'prev' ? displacedAttr : undefined;
|
|
1872
1991
|
const bookmark = new Bookmark({
|
|
1873
1992
|
name: nameAttr,
|
|
1874
1993
|
id: id,
|
|
1875
1994
|
skipNormalization: true,
|
|
1876
1995
|
colFirst: colFirstAttr ? parseInt(colFirstAttr, 10) : undefined,
|
|
1877
1996
|
colLast: colLastAttr ? parseInt(colLastAttr, 10) : undefined,
|
|
1997
|
+
displacedByCustomXml,
|
|
1878
1998
|
});
|
|
1879
1999
|
|
|
1880
2000
|
// Register with BookmarkManager to enable hasBookmark() checks
|
|
@@ -1917,12 +2037,22 @@ export class DocumentParser {
|
|
|
1917
2037
|
|
|
1918
2038
|
const id = parseInt(idAttr, 10);
|
|
1919
2039
|
|
|
2040
|
+
// CT_MarkupRange (§17.13.5) also permits w:displacedByCustomXml on
|
|
2041
|
+
// the end marker. Previously dropped on load, so a Word document
|
|
2042
|
+
// whose bookmark-end was displaced across a custom-XML node lost
|
|
2043
|
+
// the disambiguator even though the Bookmark model already emits
|
|
2044
|
+
// it from toEndXML().
|
|
2045
|
+
const displacedAttr = XMLParser.extractAttribute(bookmarkXml, 'w:displacedByCustomXml');
|
|
2046
|
+
const displacedByCustomXml =
|
|
2047
|
+
displacedAttr === 'next' || displacedAttr === 'prev' ? displacedAttr : undefined;
|
|
2048
|
+
|
|
1920
2049
|
// Create a placeholder bookmark for the end marker
|
|
1921
2050
|
// The name doesn't matter for bookmarkEnd as it only uses the ID
|
|
1922
2051
|
const bookmark = new Bookmark({
|
|
1923
2052
|
name: `_end_${id}`,
|
|
1924
2053
|
id: id,
|
|
1925
2054
|
skipNormalization: true,
|
|
2055
|
+
displacedByCustomXml,
|
|
1926
2056
|
});
|
|
1927
2057
|
|
|
1928
2058
|
return bookmark;
|
|
@@ -1944,12 +2074,23 @@ export class DocumentParser {
|
|
|
1944
2074
|
try {
|
|
1945
2075
|
const paragraph = new Paragraph();
|
|
1946
2076
|
|
|
1947
|
-
// Parse w14:paraId and w14:textId attributes from paragraph element
|
|
1948
|
-
|
|
2077
|
+
// Parse w14:paraId and w14:textId attributes from paragraph element
|
|
2078
|
+
// (Word 2010+, ST_LongHexNumber 8-char hex). XMLParser keys
|
|
2079
|
+
// attributes under the @_ prefix and may numeric-coerce purely-
|
|
2080
|
+
// digit hex strings like "00000001" to the number 1 — normalise
|
|
2081
|
+
// back to 8-char uppercase hex so the output passes strict
|
|
2082
|
+
// validation. The prior code used the un-prefixed element-shaped
|
|
2083
|
+
// keys and always saw `undefined`.
|
|
2084
|
+
const normaliseHexId = (raw: unknown): string | undefined => {
|
|
2085
|
+
if (raw === undefined || raw === null) return undefined;
|
|
2086
|
+
const asStr = typeof raw === 'number' ? raw.toString(16) : String(raw);
|
|
2087
|
+
return asStr.toUpperCase().padStart(8, '0');
|
|
2088
|
+
};
|
|
2089
|
+
const paraId = normaliseHexId(paraObj['@_w14:paraId']);
|
|
1949
2090
|
if (paraId) {
|
|
1950
2091
|
paragraph.formatting.paraId = paraId;
|
|
1951
2092
|
}
|
|
1952
|
-
const textId = paraObj['
|
|
2093
|
+
const textId = normaliseHexId(paraObj['@_w14:textId']);
|
|
1953
2094
|
if (textId) {
|
|
1954
2095
|
paragraph.formatting.textId = textId;
|
|
1955
2096
|
}
|
|
@@ -2119,6 +2260,22 @@ export class DocumentParser {
|
|
|
2119
2260
|
// Extract the formatting and set it as paragraph mark properties
|
|
2120
2261
|
paragraph.setParagraphMarkFormatting(tempRun.getFormatting());
|
|
2121
2262
|
|
|
2263
|
+
// Transfer w:rPrChange (CT_ParaRPrChange, §17.3.1.30) from the
|
|
2264
|
+
// temp run onto the paragraph's formatting. Without this the
|
|
2265
|
+
// paragraph-mark rPrChange is silently dropped because
|
|
2266
|
+
// `tempRun.getFormatting()` exposes RunFormatting fields only —
|
|
2267
|
+
// `propertyChangeRevision` is a separate field on Run that was
|
|
2268
|
+
// previously discarded along with the temp run.
|
|
2269
|
+
const rPrChangeRev = tempRun.getPropertyChangeRevision();
|
|
2270
|
+
if (rPrChangeRev) {
|
|
2271
|
+
paragraph.formatting.paragraphMarkRunPropertiesChange = {
|
|
2272
|
+
id: rPrChangeRev.id,
|
|
2273
|
+
author: rPrChangeRev.author,
|
|
2274
|
+
date: rPrChangeRev.date,
|
|
2275
|
+
previousProperties: rPrChangeRev.previousProperties,
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2122
2279
|
// Parse paragraph mark deletion tracking (w:del in w:pPr/w:rPr)
|
|
2123
2280
|
// Per ECMA-376 Part 1 §17.13.5.14 - indicates the paragraph mark was deleted
|
|
2124
2281
|
if (rPrObj['w:del']) {
|
|
@@ -2158,9 +2315,14 @@ export class DocumentParser {
|
|
|
2158
2315
|
paragraph.setAlignment(pPrObj['w:jc']['@_w:val']);
|
|
2159
2316
|
}
|
|
2160
2317
|
|
|
2161
|
-
// Style
|
|
2162
|
-
|
|
2163
|
-
|
|
2318
|
+
// Style (w:pStyle per ECMA-376 §17.3.1.27 — `w:val` is ST_String
|
|
2319
|
+
// referencing a style ID). Cast via String(...) so purely-numeric
|
|
2320
|
+
// style IDs that XMLParser's `parseAttributeValue: true` coerces to
|
|
2321
|
+
// JS numbers (e.g., a custom styleId of "1") survive as strings,
|
|
2322
|
+
// matching the `style?: string` field contract on
|
|
2323
|
+
// ParagraphFormatting.
|
|
2324
|
+
if (pPrObj['w:pStyle']?.['@_w:val'] !== undefined) {
|
|
2325
|
+
paragraph.setStyle(String(pPrObj['w:pStyle']['@_w:val']));
|
|
2164
2326
|
}
|
|
2165
2327
|
|
|
2166
2328
|
// Indentation
|
|
@@ -2179,6 +2341,24 @@ export class DocumentParser {
|
|
|
2179
2341
|
// Parse hanging indent per ECMA-376 Part 1 §17.3.1.17
|
|
2180
2342
|
if (isExplicitlySet(ind['@_w:hanging']))
|
|
2181
2343
|
paragraph.setHangingIndent(safeParseInt(ind['@_w:hanging']));
|
|
2344
|
+
|
|
2345
|
+
// CJK character-unit indentation attributes per ECMA-376 §17.3.1.12.
|
|
2346
|
+
// start/endChars are bidi-aware alternatives to left/rightChars; collapse
|
|
2347
|
+
// them onto the leftChars/rightChars fields the same way the twips parser
|
|
2348
|
+
// collapses w:start → left. Values are ST_DecimalNumber (hundredths of a
|
|
2349
|
+
// character unit), and 0 is a legitimate value — use isExplicitlySet so
|
|
2350
|
+
// number-0 from XMLParser.parseAttributeValue is preserved.
|
|
2351
|
+
if (!paragraph.formatting.indentation) paragraph.formatting.indentation = {};
|
|
2352
|
+
const leftCharsVal = ind['@_w:startChars'] ?? ind['@_w:leftChars'];
|
|
2353
|
+
const rightCharsVal = ind['@_w:endChars'] ?? ind['@_w:rightChars'];
|
|
2354
|
+
if (isExplicitlySet(leftCharsVal))
|
|
2355
|
+
paragraph.formatting.indentation.leftChars = safeParseInt(leftCharsVal);
|
|
2356
|
+
if (isExplicitlySet(rightCharsVal))
|
|
2357
|
+
paragraph.formatting.indentation.rightChars = safeParseInt(rightCharsVal);
|
|
2358
|
+
if (isExplicitlySet(ind['@_w:firstLineChars']))
|
|
2359
|
+
paragraph.formatting.indentation.firstLineChars = safeParseInt(ind['@_w:firstLineChars']);
|
|
2360
|
+
if (isExplicitlySet(ind['@_w:hangingChars']))
|
|
2361
|
+
paragraph.formatting.indentation.hangingChars = safeParseInt(ind['@_w:hangingChars']);
|
|
2182
2362
|
}
|
|
2183
2363
|
|
|
2184
2364
|
// Spacing (ECMA-376 §17.3.1.33 — 8 attributes)
|
|
@@ -2199,14 +2379,13 @@ export class DocumentParser {
|
|
|
2199
2379
|
paragraph.formatting.spacing.beforeLines = safeParseInt(spacing['@_w:beforeLines']);
|
|
2200
2380
|
if (isExplicitlySet(spacing['@_w:afterLines']))
|
|
2201
2381
|
paragraph.formatting.spacing.afterLines = safeParseInt(spacing['@_w:afterLines']);
|
|
2382
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
2202
2383
|
const beforeAuto = spacing['@_w:beforeAutospacing'];
|
|
2203
2384
|
if (beforeAuto !== undefined)
|
|
2204
|
-
paragraph.formatting.spacing.beforeAutospacing =
|
|
2205
|
-
String(beforeAuto) === '1' || String(beforeAuto) === 'true';
|
|
2385
|
+
paragraph.formatting.spacing.beforeAutospacing = parseOnOffAttribute(beforeAuto);
|
|
2206
2386
|
const afterAuto = spacing['@_w:afterAutospacing'];
|
|
2207
2387
|
if (afterAuto !== undefined)
|
|
2208
|
-
paragraph.formatting.spacing.afterAutospacing =
|
|
2209
|
-
String(afterAuto) === '1' || String(afterAuto) === 'true';
|
|
2388
|
+
paragraph.formatting.spacing.afterAutospacing = parseOnOffAttribute(afterAuto);
|
|
2210
2389
|
}
|
|
2211
2390
|
|
|
2212
2391
|
// Keep properties — preserve explicit val="0" to override style inheritance
|
|
@@ -2254,7 +2433,12 @@ export class DocumentParser {
|
|
|
2254
2433
|
const pBdr = pPrObj['w:pBdr'];
|
|
2255
2434
|
const borders: any = {};
|
|
2256
2435
|
|
|
2257
|
-
// Helper function to parse border definition
|
|
2436
|
+
// Helper function to parse border definition.
|
|
2437
|
+
// Covers the full CT_Border attribute set per ECMA-376 §17.18.2:
|
|
2438
|
+
// w:val, w:sz, w:color, w:space, w:themeColor, w:themeTint,
|
|
2439
|
+
// w:themeShade, w:shadow, w:frame. The last two are ST_OnOff —
|
|
2440
|
+
// route through parseOnOffAttribute so "off"/"false"/"0"/"on"
|
|
2441
|
+
// all resolve correctly even after XMLParser numeric coercion.
|
|
2258
2442
|
const parseBorder = (borderObj: any): any => {
|
|
2259
2443
|
if (!borderObj) return undefined;
|
|
2260
2444
|
const border: any = {};
|
|
@@ -2263,6 +2447,15 @@ export class DocumentParser {
|
|
|
2263
2447
|
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
2264
2448
|
if (borderObj['@_w:space'] !== undefined)
|
|
2265
2449
|
border.space = safeParseInt(borderObj['@_w:space']);
|
|
2450
|
+
if (borderObj['@_w:themeColor']) border.themeColor = String(borderObj['@_w:themeColor']);
|
|
2451
|
+
if (borderObj['@_w:themeTint']) border.themeTint = String(borderObj['@_w:themeTint']);
|
|
2452
|
+
if (borderObj['@_w:themeShade']) border.themeShade = String(borderObj['@_w:themeShade']);
|
|
2453
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
2454
|
+
border.shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
2455
|
+
}
|
|
2456
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
2457
|
+
border.frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
2458
|
+
}
|
|
2266
2459
|
return Object.keys(border).length > 0 ? border : undefined;
|
|
2267
2460
|
};
|
|
2268
2461
|
|
|
@@ -2301,7 +2494,15 @@ export class DocumentParser {
|
|
|
2301
2494
|
|
|
2302
2495
|
for (const tabObj of tabElements) {
|
|
2303
2496
|
const tab: any = {};
|
|
2304
|
-
|
|
2497
|
+
// w:pos is REQUIRED per §17.3.1.38 and is ST_SignedTwipsMeasure — 0 and
|
|
2498
|
+
// negative values are both valid. Use `!== undefined` so that XMLParser's
|
|
2499
|
+
// parseAttributeValue coercion of "0" to number 0 doesn't silently drop
|
|
2500
|
+
// tabs at the left margin (the previous `if (tabObj['@_w:pos'])` truthy
|
|
2501
|
+
// check turned pos=0 into an invisible tab-loss bug).
|
|
2502
|
+
if (tabObj['@_w:pos'] !== undefined) {
|
|
2503
|
+
const parsed = parseInt(String(tabObj['@_w:pos']), 10);
|
|
2504
|
+
if (!isNaN(parsed)) tab.position = parsed;
|
|
2505
|
+
}
|
|
2305
2506
|
if (tabObj['@_w:val']) tab.val = tabObj['@_w:val'];
|
|
2306
2507
|
if (tabObj['@_w:leader']) tab.leader = tabObj['@_w:leader'];
|
|
2307
2508
|
|
|
@@ -2317,19 +2518,10 @@ export class DocumentParser {
|
|
|
2317
2518
|
|
|
2318
2519
|
// Widow control per ECMA-376 Part 1 §17.3.1.40
|
|
2319
2520
|
if (pPrObj['w:widowControl'] !== undefined) {
|
|
2320
|
-
|
|
2321
|
-
//
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
widowControlVal === 'false' ||
|
|
2325
|
-
widowControlVal === false ||
|
|
2326
|
-
widowControlVal === 0
|
|
2327
|
-
) {
|
|
2328
|
-
paragraph.setWidowControl(false);
|
|
2329
|
-
} else {
|
|
2330
|
-
// If w:val is "1", "true", true, 1, or undefined (element present without val), default to true
|
|
2331
|
-
paragraph.setWidowControl(true);
|
|
2332
|
-
}
|
|
2521
|
+
// Delegate to parseOoxmlBoolean so every ST_OnOff literal — including
|
|
2522
|
+
// "off" / "on" — resolves correctly. The previous bespoke check missed
|
|
2523
|
+
// "off", silently flipping explicit-off to explicit-on.
|
|
2524
|
+
paragraph.setWidowControl(parseOoxmlBoolean(pPrObj['w:widowControl']));
|
|
2333
2525
|
}
|
|
2334
2526
|
|
|
2335
2527
|
// Outline level per ECMA-376 Part 1 §17.3.1.19
|
|
@@ -2345,15 +2537,11 @@ export class DocumentParser {
|
|
|
2345
2537
|
paragraph.setSuppressLineNumbers(parseOoxmlBoolean(pPrObj['w:suppressLineNumbers']));
|
|
2346
2538
|
}
|
|
2347
2539
|
|
|
2348
|
-
// Bidirectional layout per ECMA-376 Part 1 §17.3.1.6
|
|
2540
|
+
// Bidirectional layout per ECMA-376 Part 1 §17.3.1.6 — delegate to
|
|
2541
|
+
// parseOoxmlBoolean so "off"/"on" literals resolve correctly (the
|
|
2542
|
+
// previous bespoke check missed them).
|
|
2349
2543
|
if (pPrObj['w:bidi'] !== undefined) {
|
|
2350
|
-
|
|
2351
|
-
if (bidiVal === '0' || bidiVal === 'false' || bidiVal === false || bidiVal === 0) {
|
|
2352
|
-
paragraph.setBidi(false);
|
|
2353
|
-
} else {
|
|
2354
|
-
// Default is true when element present without val attribute or val="1"
|
|
2355
|
-
paragraph.setBidi(true);
|
|
2356
|
-
}
|
|
2544
|
+
paragraph.setBidi(parseOoxmlBoolean(pPrObj['w:bidi']));
|
|
2357
2545
|
}
|
|
2358
2546
|
|
|
2359
2547
|
// Text direction per ECMA-376 Part 1 §17.3.1.36
|
|
@@ -2371,20 +2559,10 @@ export class DocumentParser {
|
|
|
2371
2559
|
paragraph.setMirrorIndents(parseOoxmlBoolean(pPrObj['w:mirrorIndents']));
|
|
2372
2560
|
}
|
|
2373
2561
|
|
|
2374
|
-
// Auto-adjust right indent per ECMA-376 Part 1 §17.3.1.1
|
|
2562
|
+
// Auto-adjust right indent per ECMA-376 Part 1 §17.3.1.1 — delegate to
|
|
2563
|
+
// parseOoxmlBoolean so "off"/"on" literals resolve correctly.
|
|
2375
2564
|
if (pPrObj['w:adjustRightInd'] !== undefined) {
|
|
2376
|
-
|
|
2377
|
-
if (
|
|
2378
|
-
adjustRightIndVal === '0' ||
|
|
2379
|
-
adjustRightIndVal === 'false' ||
|
|
2380
|
-
adjustRightIndVal === false ||
|
|
2381
|
-
adjustRightIndVal === 0
|
|
2382
|
-
) {
|
|
2383
|
-
paragraph.setAdjustRightInd(false);
|
|
2384
|
-
} else {
|
|
2385
|
-
// Default is true when element present without val attribute or val="1"
|
|
2386
|
-
paragraph.setAdjustRightInd(true);
|
|
2387
|
-
}
|
|
2565
|
+
paragraph.setAdjustRightInd(parseOoxmlBoolean(pPrObj['w:adjustRightInd']));
|
|
2388
2566
|
}
|
|
2389
2567
|
|
|
2390
2568
|
// Text frame properties per ECMA-376 Part 1 §17.3.1.11
|
|
@@ -2458,11 +2636,16 @@ export class DocumentParser {
|
|
|
2458
2636
|
}
|
|
2459
2637
|
}
|
|
2460
2638
|
|
|
2461
|
-
// HTML div ID per ECMA-376 Part 1 §17.3.1.
|
|
2639
|
+
// HTML div ID per ECMA-376 Part 1 §17.3.1.10 (CT_DivId). `w:val` is
|
|
2640
|
+
// ST_DecimalNumber — 0 is a valid ID referencing the first div in
|
|
2641
|
+
// web settings. XMLParser coerces `"0"` to the number 0, and the
|
|
2642
|
+
// previous `if (divIdVal)` truthy check silently dropped it, breaking
|
|
2643
|
+
// the paragraph's link to div index 0 on every round-trip.
|
|
2462
2644
|
if (pPrObj['w:divId']) {
|
|
2463
2645
|
const divIdVal = pPrObj['w:divId']?.['@_w:val'];
|
|
2464
|
-
if (divIdVal) {
|
|
2465
|
-
|
|
2646
|
+
if (isExplicitlySet(divIdVal)) {
|
|
2647
|
+
const parsed = safeParseInt(divIdVal);
|
|
2648
|
+
if (!isNaN(parsed)) paragraph.setDivId(parsed);
|
|
2466
2649
|
}
|
|
2467
2650
|
}
|
|
2468
2651
|
|
|
@@ -2477,13 +2660,27 @@ export class DocumentParser {
|
|
|
2477
2660
|
}
|
|
2478
2661
|
}
|
|
2479
2662
|
|
|
2480
|
-
// Paragraph property change tracking per ECMA-376 Part 1 §17.3.1.27
|
|
2663
|
+
// Paragraph property change tracking per ECMA-376 Part 1 §17.3.1.27.
|
|
2664
|
+
// CT_TrackChange attributes — `w:id` (ST_DecimalNumber, required),
|
|
2665
|
+
// `w:author` (ST_String, required), `w:date` (ST_DateTime, optional).
|
|
2666
|
+
// XMLParser coerces `w:id="0"` to the number 0; the previous
|
|
2667
|
+
// `if (changeObj['@_w:id'])` truthy gate silently dropped id=0,
|
|
2668
|
+
// producing `<w:pPrChange w:author="…" w:date="…"/>` on emission —
|
|
2669
|
+
// missing the required `w:id` and failing strict validation. The
|
|
2670
|
+
// sibling `trPrChange` / `tblPrChange` / `tcPrChange` / `sectPrChange`
|
|
2671
|
+
// parsers already use `|| '0'` or `!== undefined` for the same reason.
|
|
2481
2672
|
if (pPrObj['w:pPrChange']) {
|
|
2482
2673
|
const changeObj = pPrObj['w:pPrChange'];
|
|
2483
2674
|
const change: any = {};
|
|
2484
|
-
if (changeObj['@_w:author']
|
|
2485
|
-
|
|
2486
|
-
|
|
2675
|
+
if (changeObj['@_w:author'] !== undefined) {
|
|
2676
|
+
change.author = String(changeObj['@_w:author']);
|
|
2677
|
+
}
|
|
2678
|
+
if (changeObj['@_w:date'] !== undefined) {
|
|
2679
|
+
change.date = String(changeObj['@_w:date']);
|
|
2680
|
+
}
|
|
2681
|
+
if (changeObj['@_w:id'] !== undefined) {
|
|
2682
|
+
change.id = String(changeObj['@_w:id']);
|
|
2683
|
+
}
|
|
2487
2684
|
|
|
2488
2685
|
// Parse child w:pPr for previousProperties to preserve tracked change history
|
|
2489
2686
|
if (changeObj['w:pPr']) {
|
|
@@ -2514,7 +2711,11 @@ export class DocumentParser {
|
|
|
2514
2711
|
}
|
|
2515
2712
|
|
|
2516
2713
|
// Parse previous indentation
|
|
2517
|
-
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to w:left/w:right
|
|
2714
|
+
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to w:left/w:right.
|
|
2715
|
+
// Also parse the six CJK character-unit variants (ST_DecimalNumber) per §17.3.1.12;
|
|
2716
|
+
// these round-trip alongside the twips so Word's rendering of the tracked "previous"
|
|
2717
|
+
// state stays locale-accurate for CJK-authored documents. Matches the iteration-21
|
|
2718
|
+
// fix on the main-path parser.
|
|
2518
2719
|
if (prevPPr['w:ind']) {
|
|
2519
2720
|
const ind = prevPPr['w:ind'];
|
|
2520
2721
|
previousProperties.indentation = {};
|
|
@@ -2526,6 +2727,18 @@ export class DocumentParser {
|
|
|
2526
2727
|
previousProperties.indentation.firstLine = parseInt(ind['@_w:firstLine'], 10);
|
|
2527
2728
|
if (ind['@_w:hanging'] !== undefined)
|
|
2528
2729
|
previousProperties.indentation.hanging = parseInt(ind['@_w:hanging'], 10);
|
|
2730
|
+
// CJK character-unit variants. startChars/endChars collapse onto
|
|
2731
|
+
// leftChars/rightChars (same pattern as the twips variants).
|
|
2732
|
+
const leftCharsVal = ind['@_w:startChars'] ?? ind['@_w:leftChars'];
|
|
2733
|
+
const rightCharsVal = ind['@_w:endChars'] ?? ind['@_w:rightChars'];
|
|
2734
|
+
if (leftCharsVal !== undefined)
|
|
2735
|
+
previousProperties.indentation.leftChars = parseInt(leftCharsVal, 10);
|
|
2736
|
+
if (rightCharsVal !== undefined)
|
|
2737
|
+
previousProperties.indentation.rightChars = parseInt(rightCharsVal, 10);
|
|
2738
|
+
if (ind['@_w:firstLineChars'] !== undefined)
|
|
2739
|
+
previousProperties.indentation.firstLineChars = parseInt(ind['@_w:firstLineChars'], 10);
|
|
2740
|
+
if (ind['@_w:hangingChars'] !== undefined)
|
|
2741
|
+
previousProperties.indentation.hangingChars = parseInt(ind['@_w:hangingChars'], 10);
|
|
2529
2742
|
}
|
|
2530
2743
|
|
|
2531
2744
|
// Parse previous alignment
|
|
@@ -2549,48 +2762,51 @@ export class DocumentParser {
|
|
|
2549
2762
|
previousProperties.spacing.beforeLines = parseInt(spacing['@_w:beforeLines'], 10);
|
|
2550
2763
|
if (spacing['@_w:afterLines'] !== undefined)
|
|
2551
2764
|
previousProperties.spacing.afterLines = parseInt(spacing['@_w:afterLines'], 10);
|
|
2765
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
2552
2766
|
const beforeAuto = spacing['@_w:beforeAutospacing'];
|
|
2553
2767
|
if (beforeAuto !== undefined)
|
|
2554
|
-
previousProperties.spacing.beforeAutospacing =
|
|
2555
|
-
String(beforeAuto) === '1' || String(beforeAuto) === 'true';
|
|
2768
|
+
previousProperties.spacing.beforeAutospacing = parseOnOffAttribute(beforeAuto);
|
|
2556
2769
|
const afterAuto = spacing['@_w:afterAutospacing'];
|
|
2557
2770
|
if (afterAuto !== undefined)
|
|
2558
|
-
previousProperties.spacing.afterAutospacing =
|
|
2559
|
-
String(afterAuto) === '1' || String(afterAuto) === 'true';
|
|
2771
|
+
previousProperties.spacing.afterAutospacing = parseOnOffAttribute(afterAuto);
|
|
2560
2772
|
}
|
|
2561
2773
|
|
|
2562
|
-
//
|
|
2774
|
+
// CT_OnOff properties per ECMA-376 §17.17.4 — accept "1"/"0"/"true"/"false"/"on"/"off"
|
|
2775
|
+
// plus the number forms produced by fast-xml-parser's parseAttributeValue. Using
|
|
2776
|
+
// parseOoxmlBoolean() keeps pPrChange round-trips consistent with the main pPr parser;
|
|
2777
|
+
// the previous `!== '0'` pattern silently flipped "false", "off", and the numeric 0.
|
|
2563
2778
|
if (prevPPr['w:keepNext']) {
|
|
2564
|
-
previousProperties.keepNext = prevPPr['w:keepNext']
|
|
2779
|
+
previousProperties.keepNext = parseOoxmlBoolean(prevPPr['w:keepNext']);
|
|
2565
2780
|
}
|
|
2566
2781
|
if (prevPPr['w:keepLines']) {
|
|
2567
|
-
previousProperties.keepLines = prevPPr['w:keepLines']
|
|
2782
|
+
previousProperties.keepLines = parseOoxmlBoolean(prevPPr['w:keepLines']);
|
|
2568
2783
|
}
|
|
2569
2784
|
if (prevPPr['w:pageBreakBefore']) {
|
|
2570
|
-
previousProperties.pageBreakBefore = prevPPr['w:pageBreakBefore']
|
|
2785
|
+
previousProperties.pageBreakBefore = parseOoxmlBoolean(prevPPr['w:pageBreakBefore']);
|
|
2571
2786
|
}
|
|
2572
2787
|
|
|
2573
2788
|
// === Extended paragraph property parsing per ECMA-376 Part 1 §17.3.1 ===
|
|
2574
2789
|
|
|
2575
2790
|
// Parse widowControl (w:widowControl) - orphan/widow control
|
|
2576
2791
|
if (prevPPr['w:widowControl']) {
|
|
2577
|
-
previousProperties.widowControl = prevPPr['w:widowControl']
|
|
2792
|
+
previousProperties.widowControl = parseOoxmlBoolean(prevPPr['w:widowControl']);
|
|
2578
2793
|
}
|
|
2579
2794
|
|
|
2580
2795
|
// Parse suppressAutoHyphens (w:suppressAutoHyphens)
|
|
2581
2796
|
if (prevPPr['w:suppressAutoHyphens']) {
|
|
2582
|
-
previousProperties.suppressAutoHyphens =
|
|
2583
|
-
prevPPr['w:suppressAutoHyphens']
|
|
2797
|
+
previousProperties.suppressAutoHyphens = parseOoxmlBoolean(
|
|
2798
|
+
prevPPr['w:suppressAutoHyphens']
|
|
2799
|
+
);
|
|
2584
2800
|
}
|
|
2585
2801
|
|
|
2586
2802
|
// Parse contextualSpacing (w:contextualSpacing)
|
|
2587
2803
|
if (prevPPr['w:contextualSpacing']) {
|
|
2588
|
-
previousProperties.contextualSpacing = prevPPr['w:contextualSpacing']
|
|
2804
|
+
previousProperties.contextualSpacing = parseOoxmlBoolean(prevPPr['w:contextualSpacing']);
|
|
2589
2805
|
}
|
|
2590
2806
|
|
|
2591
2807
|
// Parse mirrorIndents (w:mirrorIndents)
|
|
2592
2808
|
if (prevPPr['w:mirrorIndents']) {
|
|
2593
|
-
previousProperties.mirrorIndents = prevPPr['w:mirrorIndents']
|
|
2809
|
+
previousProperties.mirrorIndents = parseOoxmlBoolean(prevPPr['w:mirrorIndents']);
|
|
2594
2810
|
}
|
|
2595
2811
|
|
|
2596
2812
|
// Parse outlineLevel (w:outlineLvl @w:val)
|
|
@@ -2598,40 +2814,106 @@ export class DocumentParser {
|
|
|
2598
2814
|
previousProperties.outlineLevel = parseInt(prevPPr['w:outlineLvl']['@_w:val'], 10);
|
|
2599
2815
|
}
|
|
2600
2816
|
|
|
2817
|
+
// Parse previous text frame properties (w:framePr) per ECMA-376
|
|
2818
|
+
// Part 1 §17.3.1.11 CT_FramePr. The pPrChange emitter already
|
|
2819
|
+
// rebuilds every framePr attribute (see Paragraph.ts §3634), but
|
|
2820
|
+
// the parser never read them — so a tracked change to any
|
|
2821
|
+
// frame property (drop-cap, text-box positioning, wrap mode,
|
|
2822
|
+
// anchor lock…) silently lost the previous state on round-trip.
|
|
2823
|
+
if (prevPPr['w:framePr']) {
|
|
2824
|
+
const framePr = prevPPr['w:framePr'];
|
|
2825
|
+
const frameProps: any = {};
|
|
2826
|
+
if (isExplicitlySet(framePr['@_w:w'])) frameProps.w = safeParseInt(framePr['@_w:w']);
|
|
2827
|
+
if (isExplicitlySet(framePr['@_w:h'])) frameProps.h = safeParseInt(framePr['@_w:h']);
|
|
2828
|
+
if (framePr['@_w:hRule']) frameProps.hRule = String(framePr['@_w:hRule']);
|
|
2829
|
+
if (isExplicitlySet(framePr['@_w:x'])) frameProps.x = safeParseInt(framePr['@_w:x']);
|
|
2830
|
+
if (isExplicitlySet(framePr['@_w:y'])) frameProps.y = safeParseInt(framePr['@_w:y']);
|
|
2831
|
+
if (framePr['@_w:xAlign']) frameProps.xAlign = String(framePr['@_w:xAlign']);
|
|
2832
|
+
if (framePr['@_w:yAlign']) frameProps.yAlign = String(framePr['@_w:yAlign']);
|
|
2833
|
+
if (framePr['@_w:hAnchor']) frameProps.hAnchor = String(framePr['@_w:hAnchor']);
|
|
2834
|
+
if (framePr['@_w:vAnchor']) frameProps.vAnchor = String(framePr['@_w:vAnchor']);
|
|
2835
|
+
if (isExplicitlySet(framePr['@_w:hSpace'])) {
|
|
2836
|
+
frameProps.hSpace = safeParseInt(framePr['@_w:hSpace']);
|
|
2837
|
+
}
|
|
2838
|
+
if (isExplicitlySet(framePr['@_w:vSpace'])) {
|
|
2839
|
+
frameProps.vSpace = safeParseInt(framePr['@_w:vSpace']);
|
|
2840
|
+
}
|
|
2841
|
+
if (framePr['@_w:wrap']) frameProps.wrap = String(framePr['@_w:wrap']);
|
|
2842
|
+
if (framePr['@_w:dropCap']) frameProps.dropCap = String(framePr['@_w:dropCap']);
|
|
2843
|
+
if (isExplicitlySet(framePr['@_w:lines'])) {
|
|
2844
|
+
frameProps.lines = safeParseInt(framePr['@_w:lines']);
|
|
2845
|
+
}
|
|
2846
|
+
if (isExplicitlySet(framePr['@_w:anchorLock'])) {
|
|
2847
|
+
frameProps.anchorLock = parseOnOffAttribute(String(framePr['@_w:anchorLock']), true);
|
|
2848
|
+
}
|
|
2849
|
+
if (Object.keys(frameProps).length > 0) {
|
|
2850
|
+
previousProperties.framePr = frameProps;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2601
2854
|
// Parse bidi (w:bidi) - right-to-left paragraph
|
|
2602
2855
|
if (prevPPr['w:bidi']) {
|
|
2603
|
-
previousProperties.bidi = prevPPr['w:bidi']
|
|
2856
|
+
previousProperties.bidi = parseOoxmlBoolean(prevPPr['w:bidi']);
|
|
2604
2857
|
}
|
|
2605
2858
|
|
|
2606
2859
|
// Parse suppressLineNumbers (w:suppressLineNumbers)
|
|
2607
2860
|
if (prevPPr['w:suppressLineNumbers']) {
|
|
2608
|
-
previousProperties.suppressLineNumbers =
|
|
2609
|
-
prevPPr['w:suppressLineNumbers']
|
|
2861
|
+
previousProperties.suppressLineNumbers = parseOoxmlBoolean(
|
|
2862
|
+
prevPPr['w:suppressLineNumbers']
|
|
2863
|
+
);
|
|
2610
2864
|
}
|
|
2611
2865
|
|
|
2612
2866
|
// Parse adjustRightInd (w:adjustRightInd)
|
|
2613
2867
|
if (prevPPr['w:adjustRightInd']) {
|
|
2614
|
-
previousProperties.adjustRightInd = prevPPr['w:adjustRightInd']
|
|
2868
|
+
previousProperties.adjustRightInd = parseOoxmlBoolean(prevPPr['w:adjustRightInd']);
|
|
2615
2869
|
}
|
|
2616
2870
|
|
|
2617
2871
|
// Parse snapToGrid (w:snapToGrid)
|
|
2618
2872
|
if (prevPPr['w:snapToGrid']) {
|
|
2619
|
-
previousProperties.snapToGrid = prevPPr['w:snapToGrid']
|
|
2873
|
+
previousProperties.snapToGrid = parseOoxmlBoolean(prevPPr['w:snapToGrid']);
|
|
2620
2874
|
}
|
|
2621
2875
|
|
|
2622
2876
|
// Parse wordWrap (w:wordWrap)
|
|
2623
2877
|
if (prevPPr['w:wordWrap']) {
|
|
2624
|
-
previousProperties.wordWrap = prevPPr['w:wordWrap']
|
|
2878
|
+
previousProperties.wordWrap = parseOoxmlBoolean(prevPPr['w:wordWrap']);
|
|
2625
2879
|
}
|
|
2626
2880
|
|
|
2627
2881
|
// Parse autoSpaceDE (w:autoSpaceDE) - East Asian/numeric spacing
|
|
2628
2882
|
if (prevPPr['w:autoSpaceDE']) {
|
|
2629
|
-
previousProperties.autoSpaceDE = prevPPr['w:autoSpaceDE']
|
|
2883
|
+
previousProperties.autoSpaceDE = parseOoxmlBoolean(prevPPr['w:autoSpaceDE']);
|
|
2630
2884
|
}
|
|
2631
2885
|
|
|
2632
2886
|
// Parse autoSpaceDN (w:autoSpaceDN) - East Asian/Western spacing
|
|
2633
2887
|
if (prevPPr['w:autoSpaceDN']) {
|
|
2634
|
-
previousProperties.autoSpaceDN = prevPPr['w:autoSpaceDN']
|
|
2888
|
+
previousProperties.autoSpaceDN = parseOoxmlBoolean(prevPPr['w:autoSpaceDN']);
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
// Parse kinsoku / overflowPunct / topLinePunct / suppressOverlap —
|
|
2892
|
+
// CJK typography CT_OnOff flags. The Paragraph pPrChange generator
|
|
2893
|
+
// already emits these in the previous-properties block, but the
|
|
2894
|
+
// parser was missing the read side, so tracked paragraph-property
|
|
2895
|
+
// revisions that recorded any of these four flags were silently
|
|
2896
|
+
// dropped on load → save. Uses `parseOoxmlBoolean` to honour every
|
|
2897
|
+
// ST_OnOff literal (bare, 1/0, true/false, on/off).
|
|
2898
|
+
if (prevPPr['w:kinsoku']) {
|
|
2899
|
+
(previousProperties as { kinsoku?: boolean }).kinsoku = parseOoxmlBoolean(
|
|
2900
|
+
prevPPr['w:kinsoku']
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
if (prevPPr['w:overflowPunct']) {
|
|
2904
|
+
(previousProperties as { overflowPunct?: boolean }).overflowPunct = parseOoxmlBoolean(
|
|
2905
|
+
prevPPr['w:overflowPunct']
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2908
|
+
if (prevPPr['w:topLinePunct']) {
|
|
2909
|
+
(previousProperties as { topLinePunct?: boolean }).topLinePunct = parseOoxmlBoolean(
|
|
2910
|
+
prevPPr['w:topLinePunct']
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
if (prevPPr['w:suppressOverlap']) {
|
|
2914
|
+
(previousProperties as { suppressOverlap?: boolean }).suppressOverlap = parseOoxmlBoolean(
|
|
2915
|
+
prevPPr['w:suppressOverlap']
|
|
2916
|
+
);
|
|
2635
2917
|
}
|
|
2636
2918
|
|
|
2637
2919
|
// Parse textDirection (w:textDirection @w:val)
|
|
@@ -2644,23 +2926,68 @@ export class DocumentParser {
|
|
|
2644
2926
|
previousProperties.textAlignment = String(prevPPr['w:textAlignment']['@_w:val']);
|
|
2645
2927
|
}
|
|
2646
2928
|
|
|
2929
|
+
// Parse previous divId (w:divId) per ECMA-376 §17.3.1.10 —
|
|
2930
|
+
// ST_DecimalNumber referencing a web-settings div. Zero is a
|
|
2931
|
+
// legal ID (first div). XMLParser coerces `"0"` to number 0, so
|
|
2932
|
+
// gate via `isExplicitlySet` to preserve divId=0 on tracked
|
|
2933
|
+
// previous state. The pPrChange emitter (Paragraph.ts §3915)
|
|
2934
|
+
// re-emits prev.divId via `!== undefined`.
|
|
2935
|
+
if (prevPPr['w:divId']?.['@_w:val'] !== undefined) {
|
|
2936
|
+
const rawDivId = prevPPr['w:divId']['@_w:val'];
|
|
2937
|
+
const parsedDivId = safeParseInt(rawDivId);
|
|
2938
|
+
if (!isNaN(parsedDivId)) {
|
|
2939
|
+
previousProperties.divId = parsedDivId;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
// Parse previous cnfStyle (w:cnfStyle) per ECMA-376 §17.3.1.8 —
|
|
2944
|
+
// 12-character bitmask identifying which conditional-formatting
|
|
2945
|
+
// flags from the parent table style apply. XMLParser coerces
|
|
2946
|
+
// purely-numeric hex strings, but the custom parseValue keeps
|
|
2947
|
+
// 7+-digit strings as-is (so 12-char bitmasks survive); use
|
|
2948
|
+
// String + padStart to defensively normalise any shorter form.
|
|
2949
|
+
if (prevPPr['w:cnfStyle']?.['@_w:val'] !== undefined) {
|
|
2950
|
+
previousProperties.cnfStyle = String(prevPPr['w:cnfStyle']['@_w:val']).padStart(12, '0');
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2647
2953
|
// Parse paragraph borders (w:pBdr) per ECMA-376 Part 1 §17.3.1.24
|
|
2954
|
+
// Previous versions stored the attribute values under the wrong
|
|
2955
|
+
// field names (`val`/`sz` instead of `style`/`size`) — the
|
|
2956
|
+
// paragraph emitter reads `style`/`size`, so every tracked
|
|
2957
|
+
// previous border collapsed to `<w:top w:val="nil"/>` on
|
|
2958
|
+
// round-trip. The CT_Border attribute coverage here now matches
|
|
2959
|
+
// the main parser (§17.18.2): all nine attrs, with shadow/frame
|
|
2960
|
+
// routed through parseOnOffAttribute so ST_OnOff literals
|
|
2961
|
+
// ("on"/"off"/"true"/"false") resolve correctly.
|
|
2648
2962
|
if (prevPPr['w:pBdr']) {
|
|
2649
2963
|
const pBdr = prevPPr['w:pBdr'];
|
|
2650
2964
|
previousProperties.borders = {};
|
|
2651
2965
|
|
|
2652
2966
|
const parseBorder = (borderObj: any) => {
|
|
2653
2967
|
if (!borderObj) return undefined;
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
themeColor
|
|
2663
|
-
}
|
|
2968
|
+
const border: any = {};
|
|
2969
|
+
if (borderObj['@_w:val']) border.style = borderObj['@_w:val'];
|
|
2970
|
+
if (borderObj['@_w:sz'] !== undefined) border.size = safeParseInt(borderObj['@_w:sz']);
|
|
2971
|
+
if (borderObj['@_w:space'] !== undefined) {
|
|
2972
|
+
border.space = safeParseInt(borderObj['@_w:space']);
|
|
2973
|
+
}
|
|
2974
|
+
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
2975
|
+
if (borderObj['@_w:themeColor']) {
|
|
2976
|
+
border.themeColor = String(borderObj['@_w:themeColor']);
|
|
2977
|
+
}
|
|
2978
|
+
if (borderObj['@_w:themeTint']) {
|
|
2979
|
+
border.themeTint = String(borderObj['@_w:themeTint']);
|
|
2980
|
+
}
|
|
2981
|
+
if (borderObj['@_w:themeShade']) {
|
|
2982
|
+
border.themeShade = String(borderObj['@_w:themeShade']);
|
|
2983
|
+
}
|
|
2984
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
2985
|
+
border.shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
2986
|
+
}
|
|
2987
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
2988
|
+
border.frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
2989
|
+
}
|
|
2990
|
+
return Object.keys(border).length > 0 ? border : undefined;
|
|
2664
2991
|
};
|
|
2665
2992
|
|
|
2666
2993
|
if (pBdr['w:top']) previousProperties.borders.top = parseBorder(pBdr['w:top']);
|
|
@@ -3821,11 +4148,15 @@ export class DocumentParser {
|
|
|
3821
4148
|
return XMLBuilder.unescapeXml(String(node));
|
|
3822
4149
|
};
|
|
3823
4150
|
|
|
3824
|
-
|
|
4151
|
+
// Field-character attributes (w:dirty, w:fldLock, w:lock on w:fldChar) are
|
|
4152
|
+
// ST_OnOff per ECMA-376 §17.16.18. Delegate to parseOnOffAttribute so every
|
|
4153
|
+
// literal is honoured — the previous inline check missed "on" (silently
|
|
4154
|
+
// coerced to false) and was tighter than the spec requires.
|
|
4155
|
+
const parseBooleanAttr = (value: unknown): boolean | undefined => {
|
|
3825
4156
|
if (value === undefined || value === null) {
|
|
3826
4157
|
return undefined;
|
|
3827
4158
|
}
|
|
3828
|
-
return value
|
|
4159
|
+
return parseOnOffAttribute(value);
|
|
3829
4160
|
};
|
|
3830
4161
|
|
|
3831
4162
|
// Parse w:ffData from a fldChar object (form field data per ECMA-376 §17.16.17)
|
|
@@ -3840,15 +4171,13 @@ export class DocumentParser {
|
|
|
3840
4171
|
if (ffDataObj['w:name']?.['@_w:val'] !== undefined) {
|
|
3841
4172
|
ffd.name = String(ffDataObj['w:name']['@_w:val']);
|
|
3842
4173
|
}
|
|
3843
|
-
// w:enabled
|
|
4174
|
+
// w:enabled — CT_OnOff per ECMA-376 §17.16.11; presence = true, w:val honours ST_OnOff
|
|
3844
4175
|
if (ffDataObj['w:enabled'] !== undefined) {
|
|
3845
|
-
|
|
3846
|
-
ffd.enabled = enabledVal === '0' || enabledVal === 0 ? false : true;
|
|
4176
|
+
ffd.enabled = parseOoxmlBoolean(ffDataObj['w:enabled']);
|
|
3847
4177
|
}
|
|
3848
|
-
// w:calcOnExit
|
|
4178
|
+
// w:calcOnExit — CT_OnOff per ECMA-376 §17.16.4; presence = true, w:val honours ST_OnOff
|
|
3849
4179
|
if (ffDataObj['w:calcOnExit'] !== undefined) {
|
|
3850
|
-
|
|
3851
|
-
ffd.calcOnExit = calcVal === '1' || calcVal === 1 || calcVal === true;
|
|
4180
|
+
ffd.calcOnExit = parseOoxmlBoolean(ffDataObj['w:calcOnExit']);
|
|
3852
4181
|
}
|
|
3853
4182
|
// w:helpText
|
|
3854
4183
|
if (ffDataObj['w:helpText']?.['@_w:val'] !== undefined) {
|
|
@@ -3884,13 +4213,14 @@ export class DocumentParser {
|
|
|
3884
4213
|
if (ffDataObj['w:checkBox'] !== undefined) {
|
|
3885
4214
|
const cb: XmlNode = ffDataObj['w:checkBox'];
|
|
3886
4215
|
const checkBox: FormFieldCheckBox = { type: 'checkBox' };
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
4216
|
+
// w:default / w:checked are CT_OnOff per ECMA-376 §17.16.18 —
|
|
4217
|
+
// honour every ST_OnOff literal ("true"/"false"/"1"/"0"/"on"/"off")
|
|
4218
|
+
// and treat a bare self-closing element as true.
|
|
4219
|
+
if (cb['w:default'] !== undefined) {
|
|
4220
|
+
checkBox.defaultChecked = parseOoxmlBoolean(cb['w:default']);
|
|
3890
4221
|
}
|
|
3891
|
-
if (cb['w:checked']
|
|
3892
|
-
checkBox.checked =
|
|
3893
|
-
cb['w:checked']['@_w:val'] === '1' || cb['w:checked']['@_w:val'] === 1;
|
|
4222
|
+
if (cb['w:checked'] !== undefined) {
|
|
4223
|
+
checkBox.checked = parseOoxmlBoolean(cb['w:checked']);
|
|
3894
4224
|
}
|
|
3895
4225
|
if (cb['w:size']?.['@_w:val'] !== undefined) {
|
|
3896
4226
|
checkBox.size = Number(cb['w:size']['@_w:val']);
|
|
@@ -4094,30 +4424,47 @@ export class DocumentParser {
|
|
|
4094
4424
|
content.push({ type: 'annotationRef' });
|
|
4095
4425
|
break;
|
|
4096
4426
|
|
|
4097
|
-
// Footnote reference (w:footnoteReference) per ECMA-376 Part 1 §17.11.13
|
|
4427
|
+
// Footnote reference (w:footnoteReference) per ECMA-376 Part 1 §17.11.13.
|
|
4428
|
+
// w:customMarkFollows is ST_OnOff — honour every literal via parseOnOffAttribute.
|
|
4098
4429
|
case 'w:footnoteReference': {
|
|
4099
4430
|
const fnRefElements = toArray(runObj['w:footnoteReference']);
|
|
4100
4431
|
const fnRef = fnRefElements[elementIndex] || fnRefElements[0];
|
|
4101
4432
|
const fnId = fnRef?.['@_w:id'];
|
|
4433
|
+
const fnCustomMark = fnRef?.['@_w:customMarkFollows'];
|
|
4102
4434
|
content.push({
|
|
4103
4435
|
type: 'footnoteReference',
|
|
4104
4436
|
footnoteId: fnId !== undefined ? parseInt(fnId, 10) : undefined,
|
|
4437
|
+
customMarkFollows:
|
|
4438
|
+
fnCustomMark !== undefined ? parseOnOffAttribute(fnCustomMark) : undefined,
|
|
4105
4439
|
});
|
|
4106
4440
|
break;
|
|
4107
4441
|
}
|
|
4108
4442
|
|
|
4109
|
-
// Endnote reference (w:endnoteReference) per ECMA-376 Part 1 §17.11.2
|
|
4443
|
+
// Endnote reference (w:endnoteReference) per ECMA-376 Part 1 §17.11.2.
|
|
4444
|
+
// Same ST_OnOff treatment for w:customMarkFollows.
|
|
4110
4445
|
case 'w:endnoteReference': {
|
|
4111
4446
|
const enRefElements = toArray(runObj['w:endnoteReference']);
|
|
4112
4447
|
const enRef = enRefElements[elementIndex] || enRefElements[0];
|
|
4113
4448
|
const enId = enRef?.['@_w:id'];
|
|
4449
|
+
const enCustomMark = enRef?.['@_w:customMarkFollows'];
|
|
4114
4450
|
content.push({
|
|
4115
4451
|
type: 'endnoteReference',
|
|
4116
4452
|
endnoteId: enId !== undefined ? parseInt(enId, 10) : undefined,
|
|
4453
|
+
customMarkFollows:
|
|
4454
|
+
enCustomMark !== undefined ? parseOnOffAttribute(enCustomMark) : undefined,
|
|
4117
4455
|
});
|
|
4118
4456
|
break;
|
|
4119
4457
|
}
|
|
4120
4458
|
|
|
4459
|
+
// Auto-numbered marks INSIDE a footnote/endnote body per
|
|
4460
|
+
// ECMA-376 §17.11.14 / §17.11.3. Empty self-closing elements.
|
|
4461
|
+
case 'w:footnoteRef':
|
|
4462
|
+
content.push({ type: 'footnoteRef' });
|
|
4463
|
+
break;
|
|
4464
|
+
case 'w:endnoteRef':
|
|
4465
|
+
content.push({ type: 'endnoteRef' });
|
|
4466
|
+
break;
|
|
4467
|
+
|
|
4121
4468
|
case 'w:dayShort':
|
|
4122
4469
|
content.push({ type: 'dayShort' });
|
|
4123
4470
|
break;
|
|
@@ -4303,14 +4650,18 @@ export class DocumentParser {
|
|
|
4303
4650
|
if (runObj['w:annotationRef'] !== undefined) {
|
|
4304
4651
|
content.push({ type: 'annotationRef' });
|
|
4305
4652
|
}
|
|
4306
|
-
// Footnote/endnote reference fallback
|
|
4653
|
+
// Footnote/endnote reference fallback. w:customMarkFollows is ST_OnOff
|
|
4654
|
+
// per ECMA-376 §17.11.13 / §17.11.2 — honour every literal.
|
|
4307
4655
|
if (runObj['w:footnoteReference'] !== undefined) {
|
|
4308
4656
|
const fnRefElements = toArray(runObj['w:footnoteReference']);
|
|
4309
4657
|
for (const fnRef of fnRefElements) {
|
|
4310
4658
|
const fnId = fnRef?.['@_w:id'];
|
|
4659
|
+
const fnCustomMark = fnRef?.['@_w:customMarkFollows'];
|
|
4311
4660
|
content.push({
|
|
4312
4661
|
type: 'footnoteReference',
|
|
4313
4662
|
footnoteId: fnId !== undefined ? parseInt(fnId, 10) : undefined,
|
|
4663
|
+
customMarkFollows:
|
|
4664
|
+
fnCustomMark !== undefined ? parseOnOffAttribute(fnCustomMark) : undefined,
|
|
4314
4665
|
});
|
|
4315
4666
|
}
|
|
4316
4667
|
}
|
|
@@ -4318,12 +4669,22 @@ export class DocumentParser {
|
|
|
4318
4669
|
const enRefElements = toArray(runObj['w:endnoteReference']);
|
|
4319
4670
|
for (const enRef of enRefElements) {
|
|
4320
4671
|
const enId = enRef?.['@_w:id'];
|
|
4672
|
+
const enCustomMark = enRef?.['@_w:customMarkFollows'];
|
|
4321
4673
|
content.push({
|
|
4322
4674
|
type: 'endnoteReference',
|
|
4323
4675
|
endnoteId: enId !== undefined ? parseInt(enId, 10) : undefined,
|
|
4676
|
+
customMarkFollows:
|
|
4677
|
+
enCustomMark !== undefined ? parseOnOffAttribute(enCustomMark) : undefined,
|
|
4324
4678
|
});
|
|
4325
4679
|
}
|
|
4326
4680
|
}
|
|
4681
|
+
// Auto-numbered marks INSIDE a footnote/endnote body — empty elements.
|
|
4682
|
+
if (runObj['w:footnoteRef'] !== undefined) {
|
|
4683
|
+
content.push({ type: 'footnoteRef' });
|
|
4684
|
+
}
|
|
4685
|
+
if (runObj['w:endnoteRef'] !== undefined) {
|
|
4686
|
+
content.push({ type: 'endnoteRef' });
|
|
4687
|
+
}
|
|
4327
4688
|
if (runObj['w:dayShort'] !== undefined) {
|
|
4328
4689
|
content.push({ type: 'dayShort' });
|
|
4329
4690
|
}
|
|
@@ -4422,12 +4783,40 @@ export class DocumentParser {
|
|
|
4422
4783
|
: [hyperlinkObj['w:bookmarkStart']];
|
|
4423
4784
|
for (const bs of bookmarkStarts) {
|
|
4424
4785
|
const id = bs['@_w:id'];
|
|
4425
|
-
|
|
4786
|
+
// w:name is ST_String per §17.16.5 CT_Bookmark. XMLParser
|
|
4787
|
+
// coerces purely-numeric bookmark names ("12345") to JS
|
|
4788
|
+
// numbers; cast so Bookmark.name holds the declared string
|
|
4789
|
+
// type contract (parent parsers already do the same —
|
|
4790
|
+
// iter 125 toOptString helper).
|
|
4791
|
+
const rawName = bs['@_w:name'];
|
|
4792
|
+
const name =
|
|
4793
|
+
rawName === undefined || rawName === null || rawName === ''
|
|
4794
|
+
? undefined
|
|
4795
|
+
: String(rawName);
|
|
4426
4796
|
if (id !== undefined && name) {
|
|
4797
|
+
// CT_Bookmark per ECMA-376 §17.16.5: the object-form parser
|
|
4798
|
+
// must carry the same four "markup" attributes that the
|
|
4799
|
+
// XML-string bookmarkStart parser handles — colFirst/colLast
|
|
4800
|
+
// (table-column-scoped bookmarks) and displacedByCustomXml
|
|
4801
|
+
// (custom-XML boundary disambiguator). Previously dropped
|
|
4802
|
+
// whenever a hyperlink wrapped a bookmark, so inline
|
|
4803
|
+
// hyperlinks anchored to table-column bookmarks lost their
|
|
4804
|
+
// column range on round-trip.
|
|
4805
|
+
const rawColFirst = bs['@_w:colFirst'];
|
|
4806
|
+
const rawColLast = bs['@_w:colLast'];
|
|
4807
|
+
const rawDisplaced = bs['@_w:displacedByCustomXml'];
|
|
4808
|
+
const colFirst =
|
|
4809
|
+
rawColFirst === undefined ? undefined : parseInt(String(rawColFirst), 10);
|
|
4810
|
+
const colLast = rawColLast === undefined ? undefined : parseInt(String(rawColLast), 10);
|
|
4811
|
+
const displacedByCustomXml =
|
|
4812
|
+
rawDisplaced === 'next' || rawDisplaced === 'prev' ? rawDisplaced : undefined;
|
|
4427
4813
|
const bookmark = new Bookmark({
|
|
4428
4814
|
name: name,
|
|
4429
4815
|
id: typeof id === 'number' ? id : parseInt(id, 10),
|
|
4430
4816
|
skipNormalization: true,
|
|
4817
|
+
colFirst: Number.isNaN(colFirst as number) ? undefined : colFirst,
|
|
4818
|
+
colLast: Number.isNaN(colLast as number) ? undefined : colLast,
|
|
4819
|
+
displacedByCustomXml,
|
|
4431
4820
|
});
|
|
4432
4821
|
result.bookmarkStarts.push(bookmark);
|
|
4433
4822
|
// Also register with BookmarkManager
|
|
@@ -4449,23 +4838,47 @@ export class DocumentParser {
|
|
|
4449
4838
|
for (const be of bookmarkEnds) {
|
|
4450
4839
|
const id = be['@_w:id'];
|
|
4451
4840
|
if (id !== undefined) {
|
|
4841
|
+
// CT_MarkupRange per ECMA-376 §17.13.5 — preserve
|
|
4842
|
+
// w:displacedByCustomXml on bookmarkEnd when a custom-XML
|
|
4843
|
+
// boundary forced the marker to be displaced.
|
|
4844
|
+
const rawDisplaced = be['@_w:displacedByCustomXml'];
|
|
4845
|
+
const displacedByCustomXml =
|
|
4846
|
+
rawDisplaced === 'next' || rawDisplaced === 'prev' ? rawDisplaced : undefined;
|
|
4452
4847
|
const bookmark = new Bookmark({
|
|
4453
4848
|
name: `_end_${id}`,
|
|
4454
4849
|
id: typeof id === 'number' ? id : parseInt(id, 10),
|
|
4455
4850
|
skipNormalization: true,
|
|
4851
|
+
displacedByCustomXml,
|
|
4456
4852
|
});
|
|
4457
4853
|
result.bookmarkEnds.push(bookmark);
|
|
4458
4854
|
}
|
|
4459
4855
|
}
|
|
4460
4856
|
}
|
|
4461
4857
|
|
|
4462
|
-
// Extract hyperlink attributes
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4858
|
+
// Extract hyperlink attributes. Per ECMA-376 §17.16.22 CT_Hyperlink,
|
|
4859
|
+
// w:anchor / w:tooltip / w:tgtFrame / w:docLocation / r:id are all
|
|
4860
|
+
// ST_String. XMLParser's `parseAttributeValue: true` coerces
|
|
4861
|
+
// purely-numeric strings (e.g., a bookmark name like "12345") to
|
|
4862
|
+
// JS numbers — cast via String(...) so downstream `Hyperlink`
|
|
4863
|
+
// storage and string-method callers see the declared `string`
|
|
4864
|
+
// type contract.
|
|
4865
|
+
const toOptString = (v: unknown): string | undefined =>
|
|
4866
|
+
v === undefined || v === null ? undefined : String(v);
|
|
4867
|
+
const relationshipId = toOptString(hyperlinkObj['@_r:id']);
|
|
4868
|
+
const anchor = toOptString(hyperlinkObj['@_w:anchor']);
|
|
4869
|
+
const tooltip = toOptString(hyperlinkObj['@_w:tooltip']);
|
|
4870
|
+
const tgtFrame = toOptString(hyperlinkObj['@_w:tgtFrame']);
|
|
4871
|
+
// w:history is CT_OnOff per ECMA-376 §17.16.22 — honour every
|
|
4872
|
+
// ST_OnOff literal ("1"/"0"/"true"/"false"/"on"/"off") and every
|
|
4873
|
+
// XMLParser-coerced form (number 0/1, boolean). The Hyperlink
|
|
4874
|
+
// serializer accepts a string, so normalise to the canonical
|
|
4875
|
+
// "1"/"0" form. Without this, `w:history="0"` or `w:history="false"`
|
|
4876
|
+
// coerced to falsy values and the emitter's truthy check dropped
|
|
4877
|
+
// the attribute on round-trip.
|
|
4878
|
+
const rawHistory = hyperlinkObj['@_w:history'];
|
|
4879
|
+
const history =
|
|
4880
|
+
rawHistory === undefined ? undefined : parseOnOffAttribute(rawHistory) ? '1' : '0';
|
|
4881
|
+
const docLocation = toOptString(hyperlinkObj['@_w:docLocation']);
|
|
4469
4882
|
|
|
4470
4883
|
// Parse runs inside the hyperlink
|
|
4471
4884
|
const runs = hyperlinkObj['w:r'];
|
|
@@ -4763,8 +5176,20 @@ export class DocumentParser {
|
|
|
4763
5176
|
}
|
|
4764
5177
|
|
|
4765
5178
|
// Extract field type from instruction (first word)
|
|
4766
|
-
const typeMatch = instruction
|
|
4767
|
-
|
|
5179
|
+
const typeMatch = String(instruction)
|
|
5180
|
+
.trim()
|
|
5181
|
+
.match(/^(\w+)/);
|
|
5182
|
+
const type = (typeMatch?.[1] || 'PAGE') as import('../elements/Field.js').FieldType;
|
|
5183
|
+
|
|
5184
|
+
// CT_SimpleField (§17.16.16) carries two ST_OnOff attributes besides
|
|
5185
|
+
// the required w:instr — w:fldLock (update lock) and w:dirty
|
|
5186
|
+
// (cached-result staleness). Previously neither was parsed, so
|
|
5187
|
+
// Word's "update field" indicator and "lock field" flag were
|
|
5188
|
+
// silently cleared on every load → save round-trip.
|
|
5189
|
+
const fldLockRaw = fieldObj['@_w:fldLock'];
|
|
5190
|
+
const dirtyRaw = fieldObj['@_w:dirty'];
|
|
5191
|
+
const fldLock = fldLockRaw !== undefined ? parseOnOffAttribute(fldLockRaw) : undefined;
|
|
5192
|
+
const dirty = dirtyRaw !== undefined ? parseOnOffAttribute(dirtyRaw) : undefined;
|
|
4768
5193
|
|
|
4769
5194
|
// Parse run formatting from w:rPr if present
|
|
4770
5195
|
let formatting: RunFormatting | undefined;
|
|
@@ -4777,8 +5202,10 @@ export class DocumentParser {
|
|
|
4777
5202
|
// Create field with instruction
|
|
4778
5203
|
const field = Field.create({
|
|
4779
5204
|
type,
|
|
4780
|
-
instruction,
|
|
5205
|
+
instruction: String(instruction),
|
|
4781
5206
|
formatting,
|
|
5207
|
+
fldLock,
|
|
5208
|
+
dirty,
|
|
4782
5209
|
});
|
|
4783
5210
|
|
|
4784
5211
|
return field;
|
|
@@ -4796,15 +5223,25 @@ export class DocumentParser {
|
|
|
4796
5223
|
private parseRunPropertiesFromObject(rPrObj: any, run: Run): void {
|
|
4797
5224
|
if (!rPrObj) return;
|
|
4798
5225
|
|
|
4799
|
-
// Parse character style reference (w:rStyle) per ECMA-376 Part 1
|
|
5226
|
+
// Parse character style reference (w:rStyle) per ECMA-376 Part 1
|
|
5227
|
+
// §17.3.2.36 — `w:val` is ST_String referencing a style ID. Cast
|
|
5228
|
+
// via String(...) so a purely-numeric style ID (e.g., "1") that
|
|
5229
|
+
// XMLParser coerces to the number 1 survives as the string "1",
|
|
5230
|
+
// matching the `characterStyle?: string` field contract on
|
|
5231
|
+
// RunFormatting.
|
|
4800
5232
|
if (rPrObj['w:rStyle']) {
|
|
4801
5233
|
const styleId = rPrObj['w:rStyle']['@_w:val'];
|
|
4802
|
-
if (styleId) {
|
|
4803
|
-
run.setCharacterStyle(styleId);
|
|
5234
|
+
if (styleId !== undefined && styleId !== null && styleId !== '') {
|
|
5235
|
+
run.setCharacterStyle(String(styleId));
|
|
4804
5236
|
}
|
|
4805
5237
|
}
|
|
4806
5238
|
|
|
4807
|
-
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5
|
|
5239
|
+
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5 — CT_Border
|
|
5240
|
+
// §17.18.2 attribute set: val / sz / space / color / themeColor /
|
|
5241
|
+
// themeTint / themeShade / shadow / frame. Previously only the first
|
|
5242
|
+
// four were read, so themed character borders lost their theme linkage
|
|
5243
|
+
// on round-trip. The emitter (Run.generateRunPropertiesXML) handles
|
|
5244
|
+
// all nine since iteration 79.
|
|
4808
5245
|
if (rPrObj['w:bdr']) {
|
|
4809
5246
|
const bdr = rPrObj['w:bdr'];
|
|
4810
5247
|
const border: any = {};
|
|
@@ -4812,6 +5249,20 @@ export class DocumentParser {
|
|
|
4812
5249
|
if (bdr['@_w:sz']) border.size = parseInt(bdr['@_w:sz'], 10);
|
|
4813
5250
|
if (bdr['@_w:color']) border.color = bdr['@_w:color'];
|
|
4814
5251
|
if (bdr['@_w:space']) border.space = parseInt(bdr['@_w:space'], 10);
|
|
5252
|
+
// Per ECMA-376 §17.18.82 CT_Border: themeTint / themeShade are
|
|
5253
|
+
// ST_UcharHexNumber (2-char hex). XMLParser coerces purely-digit
|
|
5254
|
+
// hex strings like "80" / "50" to JS numbers; cast via String(...)
|
|
5255
|
+
// so the declared `string` contract on the model holds for any
|
|
5256
|
+
// downstream code that calls string methods (.toUpperCase(), etc.).
|
|
5257
|
+
if (bdr['@_w:themeColor']) border.themeColor = String(bdr['@_w:themeColor']);
|
|
5258
|
+
if (bdr['@_w:themeTint']) border.themeTint = String(bdr['@_w:themeTint']);
|
|
5259
|
+
if (bdr['@_w:themeShade']) border.themeShade = String(bdr['@_w:themeShade']);
|
|
5260
|
+
if (bdr['@_w:shadow'] !== undefined) {
|
|
5261
|
+
border.shadow = parseOnOffAttribute(String(bdr['@_w:shadow']), true);
|
|
5262
|
+
}
|
|
5263
|
+
if (bdr['@_w:frame'] !== undefined) {
|
|
5264
|
+
border.frame = parseOnOffAttribute(String(bdr['@_w:frame']), true);
|
|
5265
|
+
}
|
|
4815
5266
|
if (Object.keys(border).length > 0) {
|
|
4816
5267
|
run.setBorder(border);
|
|
4817
5268
|
}
|
|
@@ -4831,26 +5282,30 @@ export class DocumentParser {
|
|
|
4831
5282
|
if (val) run.setEmphasis(val);
|
|
4832
5283
|
}
|
|
4833
5284
|
|
|
4834
|
-
//
|
|
4835
|
-
//
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
if (
|
|
4840
|
-
if (
|
|
5285
|
+
// CT_OnOff text effects — presence + w:val both matter. Use `!== undefined`
|
|
5286
|
+
// to detect presence, then parseOoxmlBoolean() for the value, so an explicit
|
|
5287
|
+
// `<w:outline w:val="0"/>` override of a style-inherited true is preserved
|
|
5288
|
+
// (not silently dropped into "inherit"). Applies to all OnOffType rPr flags
|
|
5289
|
+
// per ECMA-376 §17.3.2.
|
|
5290
|
+
if (rPrObj['w:outline'] !== undefined) run.setOutline(parseOoxmlBoolean(rPrObj['w:outline']));
|
|
5291
|
+
if (rPrObj['w:shadow'] !== undefined) run.setShadow(parseOoxmlBoolean(rPrObj['w:shadow']));
|
|
5292
|
+
if (rPrObj['w:emboss'] !== undefined) run.setEmboss(parseOoxmlBoolean(rPrObj['w:emboss']));
|
|
5293
|
+
if (rPrObj['w:imprint'] !== undefined) run.setImprint(parseOoxmlBoolean(rPrObj['w:imprint']));
|
|
5294
|
+
if (rPrObj['w:noProof'] !== undefined) run.setNoProof(parseOoxmlBoolean(rPrObj['w:noProof']));
|
|
4841
5295
|
// snapToGrid: default when absent is true (§17.3.2.34), so explicit val="0" must be preserved
|
|
4842
5296
|
if (rPrObj['w:snapToGrid'] !== undefined) {
|
|
4843
5297
|
run.setSnapToGrid(parseOoxmlBoolean(rPrObj['w:snapToGrid']));
|
|
4844
5298
|
}
|
|
4845
|
-
if (
|
|
4846
|
-
if (
|
|
5299
|
+
if (rPrObj['w:vanish'] !== undefined) run.setVanish(parseOoxmlBoolean(rPrObj['w:vanish']));
|
|
5300
|
+
if (rPrObj['w:specVanish'] !== undefined)
|
|
5301
|
+
run.setSpecVanish(parseOoxmlBoolean(rPrObj['w:specVanish']));
|
|
4847
5302
|
|
|
4848
5303
|
// Boolean properties - use parseOoxmlBoolean helper
|
|
4849
5304
|
// Per ECMA-376: <w:b/> or <w:b w:val="1"/> or <w:b w:val="true"/> means true
|
|
4850
5305
|
// <w:b w:val="0"/> or <w:b w:val="false"/> means false (omit from document)
|
|
4851
5306
|
|
|
4852
5307
|
// Parse RTL text (w:rtl) per ECMA-376 Part 1 §17.3.2.30
|
|
4853
|
-
if (
|
|
5308
|
+
if (rPrObj['w:rtl'] !== undefined) run.setRTL(parseOoxmlBoolean(rPrObj['w:rtl']));
|
|
4854
5309
|
|
|
4855
5310
|
// b, bCs, i, iCs: preserve explicit val="0" to override style-inherited formatting
|
|
4856
5311
|
if (rPrObj['w:b'] !== undefined) run.setBold(parseOoxmlBoolean(rPrObj['w:b']));
|
|
@@ -4867,11 +5322,12 @@ export class DocumentParser {
|
|
|
4867
5322
|
run.setSmallCaps(parseOoxmlBoolean(rPrObj['w:smallCaps']));
|
|
4868
5323
|
if (rPrObj['w:caps'] !== undefined) run.setAllCaps(parseOoxmlBoolean(rPrObj['w:caps']));
|
|
4869
5324
|
|
|
4870
|
-
// Parse complex script flag (w:cs) per ECMA-376 Part 1 §17.3.2.7
|
|
4871
|
-
if (
|
|
5325
|
+
// Parse complex script flag (w:cs) per ECMA-376 Part 1 §17.3.2.7 — CT_OnOff
|
|
5326
|
+
if (rPrObj['w:cs'] !== undefined) run.setComplexScript(parseOoxmlBoolean(rPrObj['w:cs']));
|
|
4872
5327
|
|
|
4873
|
-
// Parse web hidden (w:webHidden) per ECMA-376 Part 1 §17.3.2.44
|
|
4874
|
-
if (
|
|
5328
|
+
// Parse web hidden (w:webHidden) per ECMA-376 Part 1 §17.3.2.44 — CT_OnOff
|
|
5329
|
+
if (rPrObj['w:webHidden'] !== undefined)
|
|
5330
|
+
run.setWebHidden(parseOoxmlBoolean(rPrObj['w:webHidden']));
|
|
4875
5331
|
|
|
4876
5332
|
if (rPrObj['w:u']) {
|
|
4877
5333
|
// XMLParser adds @_ prefix to attributes
|
|
@@ -4891,28 +5347,37 @@ export class DocumentParser {
|
|
|
4891
5347
|
);
|
|
4892
5348
|
}
|
|
4893
5349
|
|
|
4894
|
-
// Parse character spacing (w:spacing) per ECMA-376 Part 1 §17.3.2.
|
|
5350
|
+
// Parse character spacing (w:spacing) per ECMA-376 Part 1 §17.3.2.35.
|
|
5351
|
+
// ST_SignedTwipsMeasure — 0 and negative values are valid (default /
|
|
5352
|
+
// tighter spacing). XMLParser.parseAttributeValue coerces "0" to number 0,
|
|
5353
|
+
// which is falsy — so the previous `if (val)` truthy check silently dropped
|
|
5354
|
+
// explicit zero / baseline-reset formatting on every run that used it.
|
|
5355
|
+
// Matches the rPrChange parser below which already uses `!== undefined`.
|
|
4895
5356
|
if (rPrObj['w:spacing']) {
|
|
4896
5357
|
const val = rPrObj['w:spacing']['@_w:val'];
|
|
4897
|
-
if (val) run.setCharacterSpacing(parseInt(val, 10));
|
|
5358
|
+
if (val !== undefined) run.setCharacterSpacing(parseInt(String(val), 10));
|
|
4898
5359
|
}
|
|
4899
5360
|
|
|
4900
|
-
// Parse horizontal scaling (w:w) per ECMA-376 Part 1 §17.3.2.43
|
|
5361
|
+
// Parse horizontal scaling (w:w) per ECMA-376 Part 1 §17.3.2.43.
|
|
5362
|
+
// ST_TextScale — min 1 per schema, so value 0 is not spec-valid; keep
|
|
5363
|
+
// truthy check as a mild sanity guard against malformed sources.
|
|
4901
5364
|
if (rPrObj['w:w']) {
|
|
4902
5365
|
const val = rPrObj['w:w']['@_w:val'];
|
|
4903
|
-
if (val) run.setScaling(parseInt(val, 10));
|
|
5366
|
+
if (val) run.setScaling(parseInt(String(val), 10));
|
|
4904
5367
|
}
|
|
4905
5368
|
|
|
4906
|
-
// Parse vertical position (w:position) per ECMA-376 Part 1 §17.3.2.31
|
|
5369
|
+
// Parse vertical position (w:position) per ECMA-376 Part 1 §17.3.2.31.
|
|
5370
|
+
// ST_SignedHpsMeasure — 0 = baseline (default / explicit reset).
|
|
4907
5371
|
if (rPrObj['w:position']) {
|
|
4908
5372
|
const val = rPrObj['w:position']['@_w:val'];
|
|
4909
|
-
if (val) run.setPosition(parseInt(val, 10));
|
|
5373
|
+
if (val !== undefined) run.setPosition(parseInt(String(val), 10));
|
|
4910
5374
|
}
|
|
4911
5375
|
|
|
4912
|
-
// Parse kerning (w:kern) per ECMA-376 Part 1 §17.3.2.20
|
|
5376
|
+
// Parse kerning (w:kern) per ECMA-376 Part 1 §17.3.2.20.
|
|
5377
|
+
// ST_HpsMeasure — 0 means "kern at every size" (no minimum threshold).
|
|
4913
5378
|
if (rPrObj['w:kern']) {
|
|
4914
5379
|
const val = rPrObj['w:kern']['@_w:val'];
|
|
4915
|
-
if (val) run.setKerning(parseInt(val, 10));
|
|
5380
|
+
if (val !== undefined) run.setKerning(parseInt(String(val), 10));
|
|
4916
5381
|
}
|
|
4917
5382
|
|
|
4918
5383
|
// Parse language (w:lang) per ECMA-376 Part 1 §17.3.2.20 (CT_Language)
|
|
@@ -4932,14 +5397,27 @@ export class DocumentParser {
|
|
|
4932
5397
|
}
|
|
4933
5398
|
}
|
|
4934
5399
|
|
|
4935
|
-
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5400
|
+
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5401
|
+
// §17.3.2.10 CT_EastAsianLayout. `w:vert` / `w:vertCompress` /
|
|
5402
|
+
// `w:combine` are ST_OnOff attributes — route through
|
|
5403
|
+
// parseOnOffAttribute so every literal ("1"/"0"/"true"/"false"/
|
|
5404
|
+
// "on"/"off") resolves correctly. The previous truthy gate both
|
|
5405
|
+
// dropped explicit false (`w:vert="0"` → coerced 0 → undefined) AND
|
|
5406
|
+
// wrongly marked `w:vert="off"` as true (non-empty string is truthy
|
|
5407
|
+
// without parsing).
|
|
4936
5408
|
if (rPrObj['w:eastAsianLayout']) {
|
|
4937
5409
|
const layoutObj = rPrObj['w:eastAsianLayout'];
|
|
4938
5410
|
const layout: any = {};
|
|
4939
5411
|
if (layoutObj['@_w:id'] !== undefined) layout.id = Number(layoutObj['@_w:id']);
|
|
4940
|
-
if (layoutObj['@_w:vert']
|
|
4941
|
-
|
|
4942
|
-
|
|
5412
|
+
if (layoutObj['@_w:vert'] !== undefined) {
|
|
5413
|
+
layout.vert = parseOnOffAttribute(String(layoutObj['@_w:vert']), true);
|
|
5414
|
+
}
|
|
5415
|
+
if (layoutObj['@_w:vertCompress'] !== undefined) {
|
|
5416
|
+
layout.vertCompress = parseOnOffAttribute(String(layoutObj['@_w:vertCompress']), true);
|
|
5417
|
+
}
|
|
5418
|
+
if (layoutObj['@_w:combine'] !== undefined) {
|
|
5419
|
+
layout.combine = parseOnOffAttribute(String(layoutObj['@_w:combine']), true);
|
|
5420
|
+
}
|
|
4943
5421
|
if (layoutObj['@_w:combineBrackets'])
|
|
4944
5422
|
layout.combineBrackets = layoutObj['@_w:combineBrackets'];
|
|
4945
5423
|
|
|
@@ -4969,17 +5447,23 @@ export class DocumentParser {
|
|
|
4969
5447
|
|
|
4970
5448
|
if (rPrObj['w:rFonts']) {
|
|
4971
5449
|
const rFonts = rPrObj['w:rFonts'];
|
|
4972
|
-
|
|
5450
|
+
// Per ECMA-376 §17.3.2.26 CT_Fonts, all four literal-font
|
|
5451
|
+
// attributes (ascii/hAnsi/eastAsia/cs) are ST_String. XMLParser
|
|
5452
|
+
// coerces purely-numeric font names ("2010", etc.) to JS
|
|
5453
|
+
// numbers; cast through String() so RunFormatting's
|
|
5454
|
+
// declared-string font fields keep their type contract.
|
|
5455
|
+
if (rFonts['@_w:ascii'] !== undefined) run.setFont(String(rFonts['@_w:ascii']));
|
|
4973
5456
|
// Parse additional font variants per ECMA-376 Part 1 §17.3.2.26
|
|
4974
|
-
if (rFonts['@_w:hAnsi']) run.setFontHAnsi(rFonts['@_w:hAnsi']);
|
|
4975
|
-
if (rFonts['@_w:eastAsia']) run.setFontEastAsia(rFonts['@_w:eastAsia']);
|
|
4976
|
-
if (rFonts['@_w:cs']) run.setFontCs(rFonts['@_w:cs']);
|
|
4977
|
-
if (rFonts['@_w:hint']) run.setFontHint(rFonts['@_w:hint']);
|
|
5457
|
+
if (rFonts['@_w:hAnsi'] !== undefined) run.setFontHAnsi(String(rFonts['@_w:hAnsi']));
|
|
5458
|
+
if (rFonts['@_w:eastAsia'] !== undefined) run.setFontEastAsia(String(rFonts['@_w:eastAsia']));
|
|
5459
|
+
if (rFonts['@_w:cs'] !== undefined) run.setFontCs(String(rFonts['@_w:cs']));
|
|
5460
|
+
if (rFonts['@_w:hint']) run.setFontHint(String(rFonts['@_w:hint']));
|
|
4978
5461
|
// Parse theme font references per ECMA-376 Part 1 §17.3.2.26
|
|
4979
|
-
if (rFonts['@_w:asciiTheme']) run.setFontAsciiTheme(rFonts['@_w:asciiTheme']);
|
|
4980
|
-
if (rFonts['@_w:hAnsiTheme']) run.setFontHAnsiTheme(rFonts['@_w:hAnsiTheme']);
|
|
4981
|
-
if (rFonts['@_w:eastAsiaTheme'])
|
|
4982
|
-
|
|
5462
|
+
if (rFonts['@_w:asciiTheme']) run.setFontAsciiTheme(String(rFonts['@_w:asciiTheme']));
|
|
5463
|
+
if (rFonts['@_w:hAnsiTheme']) run.setFontHAnsiTheme(String(rFonts['@_w:hAnsiTheme']));
|
|
5464
|
+
if (rFonts['@_w:eastAsiaTheme'])
|
|
5465
|
+
run.setFontEastAsiaTheme(String(rFonts['@_w:eastAsiaTheme']));
|
|
5466
|
+
if (rFonts['@_w:cstheme']) run.setFontCsTheme(String(rFonts['@_w:cstheme']));
|
|
4983
5467
|
}
|
|
4984
5468
|
|
|
4985
5469
|
if (rPrObj['w:sz']) {
|
|
@@ -5047,7 +5531,7 @@ export class DocumentParser {
|
|
|
5047
5531
|
// This records what the run formatting was BEFORE a change was made
|
|
5048
5532
|
if (rPrObj['w:rPrChange']) {
|
|
5049
5533
|
const changeObj = rPrObj['w:rPrChange'];
|
|
5050
|
-
const propChange: import('../elements/PropertyChangeTypes').RunPropertyChange = {
|
|
5534
|
+
const propChange: import('../elements/PropertyChangeTypes.js').RunPropertyChange = {
|
|
5051
5535
|
id: changeObj['@_w:id'] !== undefined ? parseInt(String(changeObj['@_w:id']), 10) : 0,
|
|
5052
5536
|
author: changeObj['@_w:author'] ? String(changeObj['@_w:author']) : '',
|
|
5053
5537
|
date: changeObj['@_w:date'] ? new Date(String(changeObj['@_w:date'])) : new Date(),
|
|
@@ -5057,7 +5541,7 @@ export class DocumentParser {
|
|
|
5057
5541
|
// Parse previous run properties from child w:rPr element
|
|
5058
5542
|
if (changeObj['w:rPr']) {
|
|
5059
5543
|
const prevRPr = changeObj['w:rPr'];
|
|
5060
|
-
const prevProps: Partial<import('../elements/Run').RunFormatting> = {};
|
|
5544
|
+
const prevProps: Partial<import('../elements/Run.js').RunFormatting> = {};
|
|
5061
5545
|
|
|
5062
5546
|
// Parse previous bold
|
|
5063
5547
|
if (prevRPr['w:b']) {
|
|
@@ -5069,10 +5553,22 @@ export class DocumentParser {
|
|
|
5069
5553
|
prevProps.italic = parseOoxmlBoolean(prevRPr['w:i']);
|
|
5070
5554
|
}
|
|
5071
5555
|
|
|
5072
|
-
// Parse previous underline
|
|
5556
|
+
// Parse previous underline — CT_Underline per §17.3.2.40 has `val`
|
|
5557
|
+
// plus color / themeColor / themeTint / themeShade. Main rPr parser
|
|
5558
|
+
// reads all of them; rPrChange previously only read `val`, so
|
|
5559
|
+
// underline color metadata on tracked "previous" state was dropped.
|
|
5073
5560
|
if (prevRPr['w:u']) {
|
|
5074
|
-
const
|
|
5561
|
+
const uObj = prevRPr['w:u'];
|
|
5562
|
+
const uVal = uObj['@_w:val'];
|
|
5075
5563
|
prevProps.underline = uVal || true;
|
|
5564
|
+
if (uObj['@_w:color']) prevProps.underlineColor = uObj['@_w:color'];
|
|
5565
|
+
if (uObj['@_w:themeColor']) prevProps.underlineThemeColor = uObj['@_w:themeColor'];
|
|
5566
|
+
if (uObj['@_w:themeTint'] !== undefined) {
|
|
5567
|
+
prevProps.underlineThemeTint = parseInt(String(uObj['@_w:themeTint']), 16);
|
|
5568
|
+
}
|
|
5569
|
+
if (uObj['@_w:themeShade'] !== undefined) {
|
|
5570
|
+
prevProps.underlineThemeShade = parseInt(String(uObj['@_w:themeShade']), 16);
|
|
5571
|
+
}
|
|
5076
5572
|
}
|
|
5077
5573
|
|
|
5078
5574
|
// Parse previous strikethrough
|
|
@@ -5081,13 +5577,28 @@ export class DocumentParser {
|
|
|
5081
5577
|
}
|
|
5082
5578
|
|
|
5083
5579
|
// Parse previous font (all w:rFonts attributes per ECMA-376 Part 1 §17.3.2.26)
|
|
5580
|
+
// including theme font references (asciiTheme/hAnsiTheme/eastAsiaTheme/
|
|
5581
|
+
// cstheme). Previously only the literal-font attributes were read, so
|
|
5582
|
+
// rPrChange tracked history of theme-font changes lost the theme linkage
|
|
5583
|
+
// on round-trip — a paragraph whose "previous" font was a theme
|
|
5584
|
+
// reference (e.g. w:asciiTheme="minorHAnsi") silently dropped it.
|
|
5084
5585
|
if (prevRPr['w:rFonts']) {
|
|
5085
5586
|
const rFonts = prevRPr['w:rFonts'];
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
if (rFonts['@_w:
|
|
5587
|
+
// Mirror the main-path String() casts on rPrChange
|
|
5588
|
+
// previous-font reads — ECMA-376 §17.3.2.26 CT_Fonts declares
|
|
5589
|
+
// ascii/hAnsi/eastAsia/cs as ST_String, so purely-numeric
|
|
5590
|
+
// font names must survive round-trip as strings here too.
|
|
5591
|
+
if (rFonts['@_w:ascii'] !== undefined) prevProps.font = String(rFonts['@_w:ascii']);
|
|
5592
|
+
if (rFonts['@_w:hAnsi'] !== undefined) prevProps.fontHAnsi = String(rFonts['@_w:hAnsi']);
|
|
5593
|
+
if (rFonts['@_w:eastAsia'] !== undefined)
|
|
5594
|
+
prevProps.fontEastAsia = String(rFonts['@_w:eastAsia']);
|
|
5595
|
+
if (rFonts['@_w:cs'] !== undefined) prevProps.fontCs = String(rFonts['@_w:cs']);
|
|
5596
|
+
if (rFonts['@_w:hint']) prevProps.fontHint = String(rFonts['@_w:hint']);
|
|
5597
|
+
if (rFonts['@_w:asciiTheme']) prevProps.fontAsciiTheme = String(rFonts['@_w:asciiTheme']);
|
|
5598
|
+
if (rFonts['@_w:hAnsiTheme']) prevProps.fontHAnsiTheme = String(rFonts['@_w:hAnsiTheme']);
|
|
5599
|
+
if (rFonts['@_w:eastAsiaTheme'])
|
|
5600
|
+
prevProps.fontEastAsiaTheme = String(rFonts['@_w:eastAsiaTheme']);
|
|
5601
|
+
if (rFonts['@_w:cstheme']) prevProps.fontCsTheme = String(rFonts['@_w:cstheme']);
|
|
5091
5602
|
}
|
|
5092
5603
|
|
|
5093
5604
|
// Parse previous size (half-points to points)
|
|
@@ -5285,17 +5796,33 @@ export class DocumentParser {
|
|
|
5285
5796
|
}
|
|
5286
5797
|
}
|
|
5287
5798
|
|
|
5288
|
-
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.
|
|
5289
|
-
//
|
|
5799
|
+
// Parse text border (w:bdr) per ECMA-376 Part 1 §17.3.2.5 — full
|
|
5800
|
+
// CT_Border attribute set for rPrChange previous-properties fidelity.
|
|
5290
5801
|
if (prevRPr['w:bdr']) {
|
|
5291
5802
|
const bdrObj = prevRPr['w:bdr'];
|
|
5292
|
-
|
|
5293
|
-
style: bdrObj['@_w:val'] as import('../elements/Run').TextBorderStyle,
|
|
5803
|
+
const tb: import('../elements/Run.js').TextBorder = {
|
|
5804
|
+
style: bdrObj['@_w:val'] as import('../elements/Run.js').TextBorderStyle,
|
|
5294
5805
|
size: bdrObj['@_w:sz'] !== undefined ? safeParseInt(bdrObj['@_w:sz']) : undefined,
|
|
5295
5806
|
space:
|
|
5296
5807
|
bdrObj['@_w:space'] !== undefined ? safeParseInt(bdrObj['@_w:space']) : undefined,
|
|
5297
5808
|
color: bdrObj['@_w:color'],
|
|
5298
5809
|
};
|
|
5810
|
+
// String(...) cast: XMLParser coerces "80"/"50" hex to numbers
|
|
5811
|
+
// — preserve the declared string contract on the model.
|
|
5812
|
+
if (bdrObj['@_w:themeColor']) {
|
|
5813
|
+
tb.themeColor = String(
|
|
5814
|
+
bdrObj['@_w:themeColor']
|
|
5815
|
+
) as import('../elements/Run.js').ThemeColorValue;
|
|
5816
|
+
}
|
|
5817
|
+
if (bdrObj['@_w:themeTint']) tb.themeTint = String(bdrObj['@_w:themeTint']);
|
|
5818
|
+
if (bdrObj['@_w:themeShade']) tb.themeShade = String(bdrObj['@_w:themeShade']);
|
|
5819
|
+
if (bdrObj['@_w:shadow'] !== undefined) {
|
|
5820
|
+
tb.shadow = parseOnOffAttribute(String(bdrObj['@_w:shadow']), true);
|
|
5821
|
+
}
|
|
5822
|
+
if (bdrObj['@_w:frame'] !== undefined) {
|
|
5823
|
+
tb.frame = parseOnOffAttribute(String(bdrObj['@_w:frame']), true);
|
|
5824
|
+
}
|
|
5825
|
+
prevProps.border = tb;
|
|
5299
5826
|
}
|
|
5300
5827
|
|
|
5301
5828
|
// Parse character shading (w:shd) per ECMA-376 Part 1 §17.3.2.32
|
|
@@ -5306,24 +5833,54 @@ export class DocumentParser {
|
|
|
5306
5833
|
}
|
|
5307
5834
|
}
|
|
5308
5835
|
|
|
5309
|
-
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5836
|
+
// Parse East Asian layout (w:eastAsianLayout) per ECMA-376 Part 1
|
|
5837
|
+
// §17.3.2.10 CT_EastAsianLayout. Parity-fix with the main rPr
|
|
5838
|
+
// parser: route the three ST_OnOff attributes through
|
|
5839
|
+
// parseOnOffAttribute so every literal — including "0"
|
|
5840
|
+
// (explicit-false override) and "off" — resolves correctly. The
|
|
5841
|
+
// previous truthy gate both dropped explicit-false (XMLParser
|
|
5842
|
+
// coerces "0" to number 0 → falsy → undefined) and wrongly
|
|
5843
|
+
// coerced "off" to true.
|
|
5310
5844
|
if (prevRPr['w:eastAsianLayout']) {
|
|
5311
5845
|
const eaObj = prevRPr['w:eastAsianLayout'];
|
|
5312
5846
|
prevProps.eastAsianLayout = {
|
|
5313
5847
|
id: eaObj['@_w:id'] !== undefined ? safeParseInt(eaObj['@_w:id']) : undefined,
|
|
5314
|
-
combine:
|
|
5315
|
-
|
|
5316
|
-
|
|
5848
|
+
combine:
|
|
5849
|
+
eaObj['@_w:combine'] !== undefined
|
|
5850
|
+
? parseOnOffAttribute(String(eaObj['@_w:combine']), true)
|
|
5851
|
+
: undefined,
|
|
5317
5852
|
combineBrackets: eaObj['@_w:combineBrackets'],
|
|
5318
|
-
vert:
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
: undefined
|
|
5853
|
+
vert:
|
|
5854
|
+
eaObj['@_w:vert'] !== undefined
|
|
5855
|
+
? parseOnOffAttribute(String(eaObj['@_w:vert']), true)
|
|
5856
|
+
: undefined,
|
|
5857
|
+
vertCompress:
|
|
5858
|
+
eaObj['@_w:vertCompress'] !== undefined
|
|
5859
|
+
? parseOnOffAttribute(String(eaObj['@_w:vertCompress']), true)
|
|
5860
|
+
: undefined,
|
|
5324
5861
|
};
|
|
5325
5862
|
}
|
|
5326
5863
|
|
|
5864
|
+
// Collect w14: namespace elements from the previous rPr for
|
|
5865
|
+
// passthrough (Word 2010+ text effects: w14:textOutline,
|
|
5866
|
+
// w14:shadow, w14:reflection, w14:glow, w14:ligatures,
|
|
5867
|
+
// w14:numForm, w14:numSpacing, w14:cntxtAlts, w14:stylisticSets).
|
|
5868
|
+
// The main rPr parser already collects these and the rPrChange
|
|
5869
|
+
// emitter (via generateRunPropertiesXML line 3130) re-emits
|
|
5870
|
+
// prevProps.rawW14Properties, but the rPrChange parser never
|
|
5871
|
+
// captured them — so tracked changes to any w14 text effect
|
|
5872
|
+
// silently lost the previous state on load → save.
|
|
5873
|
+
const prevRawW14: string[] = [];
|
|
5874
|
+
for (const key of Object.keys(prevRPr)) {
|
|
5875
|
+
if (key.startsWith('w14:')) {
|
|
5876
|
+
const rawXml = this.objectToXml({ [key]: prevRPr[key] });
|
|
5877
|
+
if (rawXml) prevRawW14.push(rawXml);
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
if (prevRawW14.length > 0) {
|
|
5881
|
+
(prevProps as { rawW14Properties?: string[] }).rawW14Properties = prevRawW14;
|
|
5882
|
+
}
|
|
5883
|
+
|
|
5327
5884
|
propChange.previousProperties = prevProps;
|
|
5328
5885
|
}
|
|
5329
5886
|
|
|
@@ -5394,8 +5951,15 @@ export class DocumentParser {
|
|
|
5394
5951
|
let docPrId = 1;
|
|
5395
5952
|
let hidden = false;
|
|
5396
5953
|
if (docPrObj) {
|
|
5397
|
-
name
|
|
5398
|
-
|
|
5954
|
+
// wp:docPr @name and @descr are xsd:string per ECMA-376
|
|
5955
|
+
// §20.4.2.5 CT_NonVisualDrawingProps. XMLParser coerces
|
|
5956
|
+
// purely-numeric values ("2010") to JS numbers; cast through
|
|
5957
|
+
// String() so Image.name / Image.description keep the declared
|
|
5958
|
+
// string contract (matches the @_title handling below).
|
|
5959
|
+
const rawName = docPrObj['@_name'];
|
|
5960
|
+
name = rawName !== undefined && rawName !== null ? String(rawName) : 'image';
|
|
5961
|
+
const rawDescr = docPrObj['@_descr'];
|
|
5962
|
+
description = rawDescr !== undefined && rawDescr !== null ? String(rawDescr) : 'Image';
|
|
5399
5963
|
if (docPrObj['@_title']) {
|
|
5400
5964
|
title = String(docPrObj['@_title']);
|
|
5401
5965
|
}
|
|
@@ -5724,7 +6288,7 @@ export class DocumentParser {
|
|
|
5724
6288
|
);
|
|
5725
6289
|
|
|
5726
6290
|
// Create image from buffer with all properties
|
|
5727
|
-
const { Image: ImageClass } = await import('../elements/Image');
|
|
6291
|
+
const { Image: ImageClass } = await import('../elements/Image.js');
|
|
5728
6292
|
const image = await ImageClass.create({
|
|
5729
6293
|
source: imageData,
|
|
5730
6294
|
width,
|
|
@@ -6085,12 +6649,36 @@ export class DocumentParser {
|
|
|
6085
6649
|
*/
|
|
6086
6650
|
private parseBorderElement(borderObj: any): TableBorder | undefined {
|
|
6087
6651
|
if (!borderObj) return undefined;
|
|
6652
|
+
// Extract the full CT_Border attribute set per ECMA-376 §17.18.2:
|
|
6653
|
+
// val (required) / sz / space / color / themeColor / themeTint /
|
|
6654
|
+
// themeShade / shadow / frame. Previously the last five were silently
|
|
6655
|
+
// dropped on load, so themed borders and shadow/frame flags were lost
|
|
6656
|
+
// on every round-trip.
|
|
6088
6657
|
const border: TableBorder = {
|
|
6089
6658
|
style: (borderObj['@_w:val'] || 'single') as TableBorder['style'],
|
|
6090
6659
|
};
|
|
6091
6660
|
if (borderObj['@_w:sz'] !== undefined) border.size = safeParseInt(borderObj['@_w:sz']);
|
|
6092
6661
|
if (borderObj['@_w:space'] !== undefined) border.space = safeParseInt(borderObj['@_w:space']);
|
|
6093
|
-
if (borderObj['@_w:color']) border.color = borderObj['@_w:color'];
|
|
6662
|
+
if (borderObj['@_w:color']) border.color = String(borderObj['@_w:color']);
|
|
6663
|
+
// String(...) cast: themeTint / themeShade are ST_UcharHexNumber
|
|
6664
|
+
// (2-char hex) declared as `string` on the model. XMLParser coerces
|
|
6665
|
+
// purely-digit hex like "80"/"50" to numbers — cast to preserve
|
|
6666
|
+
// the type contract.
|
|
6667
|
+
if (borderObj['@_w:themeColor']) {
|
|
6668
|
+
(border as any).themeColor = String(borderObj['@_w:themeColor']);
|
|
6669
|
+
}
|
|
6670
|
+
if (borderObj['@_w:themeTint']) {
|
|
6671
|
+
(border as any).themeTint = String(borderObj['@_w:themeTint']);
|
|
6672
|
+
}
|
|
6673
|
+
if (borderObj['@_w:themeShade']) {
|
|
6674
|
+
(border as any).themeShade = String(borderObj['@_w:themeShade']);
|
|
6675
|
+
}
|
|
6676
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
6677
|
+
(border as any).shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
6678
|
+
}
|
|
6679
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
6680
|
+
(border as any).frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
6681
|
+
}
|
|
6094
6682
|
return border;
|
|
6095
6683
|
}
|
|
6096
6684
|
|
|
@@ -6136,8 +6724,10 @@ export class DocumentParser {
|
|
|
6136
6724
|
const gridChange = TableGridChange.create(
|
|
6137
6725
|
safeParseInt(changeObj['@_w:id'], 0),
|
|
6138
6726
|
prevWidths,
|
|
6139
|
-
changeObj['@_w:author']
|
|
6140
|
-
changeObj['@_w:date']
|
|
6727
|
+
changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : undefined,
|
|
6728
|
+
changeObj['@_w:date'] !== undefined
|
|
6729
|
+
? new Date(String(changeObj['@_w:date']))
|
|
6730
|
+
: undefined
|
|
6141
6731
|
);
|
|
6142
6732
|
table.setTblGridChange(gridChange);
|
|
6143
6733
|
}
|
|
@@ -6232,53 +6822,90 @@ export class DocumentParser {
|
|
|
6232
6822
|
private parseTablePropertiesFromObject(tblPrObj: any, table: Table): void {
|
|
6233
6823
|
if (!tblPrObj) return;
|
|
6234
6824
|
|
|
6235
|
-
// Parse table style reference (w:tblStyle)
|
|
6825
|
+
// Parse table style reference (w:tblStyle) per ECMA-376 §17.7.4.62.
|
|
6826
|
+
// w:val is ST_String — cast through String() so purely-numeric
|
|
6827
|
+
// custom style IDs ("2025", "1", …) don't leak as JS numbers
|
|
6828
|
+
// through XMLParser's parseAttributeValue coercion into the
|
|
6829
|
+
// string-typed `formatting.style` field.
|
|
6236
6830
|
if (tblPrObj['w:tblStyle']) {
|
|
6237
6831
|
const styleId = tblPrObj['w:tblStyle']['@_w:val'];
|
|
6238
|
-
if (styleId) {
|
|
6239
|
-
table.setStyle(styleId);
|
|
6240
|
-
}
|
|
6241
|
-
}
|
|
6242
|
-
|
|
6243
|
-
// Parse table look flags (w:tblLook) -
|
|
6244
|
-
//
|
|
6832
|
+
if (styleId !== undefined && styleId !== null && styleId !== '') {
|
|
6833
|
+
table.setStyle(String(styleId));
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
|
|
6837
|
+
// Parse table look flags (w:tblLook) per ECMA-376 §17.4.57 — supports both
|
|
6838
|
+
// hex-string format (w:val="04A0") AND individual ST_OnOff attributes
|
|
6839
|
+
// (firstRow/lastRow/firstColumn/lastColumn/noHBand/noVBand).
|
|
6840
|
+
//
|
|
6841
|
+
// XMLParser.parseToObject runs with `parseAttributeValue: true` by default,
|
|
6842
|
+
// so `"1"` coerces to the number `1` and `"true"` to the boolean `true`.
|
|
6843
|
+
// The previous `=== '1'` strict-string comparison missed both coerced
|
|
6844
|
+
// forms, silently flipping every individually-set flag to OFF and
|
|
6845
|
+
// producing `tblLook="0000"` for every Word-authored document whose
|
|
6846
|
+
// tblLook used the expanded attribute syntax. Route each attribute
|
|
6847
|
+
// through `parseOoxmlBoolean` (attribute form) so string/number/boolean
|
|
6848
|
+
// representations all resolve correctly.
|
|
6245
6849
|
if (tblPrObj['w:tblLook']) {
|
|
6246
6850
|
const look = tblPrObj['w:tblLook'];
|
|
6247
6851
|
if (look['@_w:val']) {
|
|
6248
6852
|
// Hex string format
|
|
6249
6853
|
table.setTblLook(look['@_w:val']);
|
|
6250
6854
|
} else {
|
|
6251
|
-
// Individual attribute format
|
|
6252
|
-
//
|
|
6855
|
+
// Individual attribute format — construct hex value.
|
|
6856
|
+
// Bits per §17.4.57: firstRow=0x0020, lastRow=0x0040, firstCol=0x0080,
|
|
6857
|
+
// lastCol=0x0100, noHBand=0x0200, noVBand=0x0400.
|
|
6858
|
+
const attrIsOn = (name: string): boolean => {
|
|
6859
|
+
const v = look[name];
|
|
6860
|
+
if (v === undefined) return false;
|
|
6861
|
+
// parseOoxmlBoolean accepts the value wrapped as `{'@_w:val': v}` —
|
|
6862
|
+
// handles string "1"/"0"/"true"/"false"/"on"/"off", number 1/0,
|
|
6863
|
+
// and boolean true/false uniformly.
|
|
6864
|
+
return parseOoxmlBoolean({ '@_w:val': v });
|
|
6865
|
+
};
|
|
6253
6866
|
let value = 0;
|
|
6254
|
-
if (
|
|
6255
|
-
if (
|
|
6256
|
-
if (
|
|
6257
|
-
if (
|
|
6258
|
-
if (
|
|
6259
|
-
if (
|
|
6867
|
+
if (attrIsOn('@_w:firstRow')) value |= 0x0020;
|
|
6868
|
+
if (attrIsOn('@_w:lastRow')) value |= 0x0040;
|
|
6869
|
+
if (attrIsOn('@_w:firstColumn')) value |= 0x0080;
|
|
6870
|
+
if (attrIsOn('@_w:lastColumn')) value |= 0x0100;
|
|
6871
|
+
if (attrIsOn('@_w:noHBand')) value |= 0x0200;
|
|
6872
|
+
if (attrIsOn('@_w:noVBand')) value |= 0x0400;
|
|
6260
6873
|
table.setTblLook(value.toString(16).toUpperCase().padStart(4, '0'));
|
|
6261
6874
|
}
|
|
6262
6875
|
}
|
|
6263
6876
|
|
|
6264
|
-
// Parse table positioning (tblpPr) - for floating tables
|
|
6877
|
+
// Parse table positioning (tblpPr) - for floating tables.
|
|
6878
|
+
// Per ECMA-376 §17.4.52 CT_TblPPr, the six numeric attributes
|
|
6879
|
+
// (tblpX/tblpY/leftFromText/rightFromText/topFromText/bottomFromText)
|
|
6880
|
+
// are ST_SignedTwipsMeasure / ST_TwipsMeasure where 0 is a valid
|
|
6881
|
+
// value (e.g. float table anchored exactly at the anchor point).
|
|
6882
|
+
// XMLParser coerces "0" to the number 0 (falsy), so the previous
|
|
6883
|
+
// truthy gate silently dropped zero-offset positions. Table's
|
|
6884
|
+
// emitter uses `!== undefined`, so the asymmetry lost zeroes on
|
|
6885
|
+
// round-trip. Route each numeric read through isExplicitlySet +
|
|
6886
|
+
// safeParseInt.
|
|
6265
6887
|
if (tblPrObj['w:tblpPr']) {
|
|
6266
6888
|
const tblpPr = tblPrObj['w:tblpPr'];
|
|
6267
6889
|
const position: any = {};
|
|
6268
6890
|
|
|
6269
|
-
if (tblpPr['@_w:tblpX']) position.x =
|
|
6270
|
-
if (tblpPr['@_w:tblpY']) position.y =
|
|
6891
|
+
if (isExplicitlySet(tblpPr['@_w:tblpX'])) position.x = safeParseInt(tblpPr['@_w:tblpX']);
|
|
6892
|
+
if (isExplicitlySet(tblpPr['@_w:tblpY'])) position.y = safeParseInt(tblpPr['@_w:tblpY']);
|
|
6271
6893
|
if (tblpPr['@_w:horzAnchor']) position.horizontalAnchor = tblpPr['@_w:horzAnchor'];
|
|
6272
6894
|
if (tblpPr['@_w:vertAnchor']) position.verticalAnchor = tblpPr['@_w:vertAnchor'];
|
|
6273
6895
|
if (tblpPr['@_w:tblpXSpec']) position.horizontalAlignment = tblpPr['@_w:tblpXSpec'];
|
|
6274
6896
|
if (tblpPr['@_w:tblpYSpec']) position.verticalAlignment = tblpPr['@_w:tblpYSpec'];
|
|
6275
|
-
if (tblpPr['@_w:leftFromText'])
|
|
6276
|
-
position.leftFromText =
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6897
|
+
if (isExplicitlySet(tblpPr['@_w:leftFromText'])) {
|
|
6898
|
+
position.leftFromText = safeParseInt(tblpPr['@_w:leftFromText']);
|
|
6899
|
+
}
|
|
6900
|
+
if (isExplicitlySet(tblpPr['@_w:rightFromText'])) {
|
|
6901
|
+
position.rightFromText = safeParseInt(tblpPr['@_w:rightFromText']);
|
|
6902
|
+
}
|
|
6903
|
+
if (isExplicitlySet(tblpPr['@_w:topFromText'])) {
|
|
6904
|
+
position.topFromText = safeParseInt(tblpPr['@_w:topFromText']);
|
|
6905
|
+
}
|
|
6906
|
+
if (isExplicitlySet(tblpPr['@_w:bottomFromText'])) {
|
|
6907
|
+
position.bottomFromText = safeParseInt(tblpPr['@_w:bottomFromText']);
|
|
6908
|
+
}
|
|
6282
6909
|
|
|
6283
6910
|
if (Object.keys(position).length > 0) {
|
|
6284
6911
|
table.setPosition(position);
|
|
@@ -6291,9 +6918,9 @@ export class DocumentParser {
|
|
|
6291
6918
|
table.setOverlap(val === 'overlap');
|
|
6292
6919
|
}
|
|
6293
6920
|
|
|
6294
|
-
// Parse bidirectional visual layout
|
|
6921
|
+
// Parse bidirectional visual layout — CT_OnOff, honour w:val per ECMA-376 §17.17.4
|
|
6295
6922
|
if (tblPrObj['w:bidiVisual']) {
|
|
6296
|
-
table.setBidiVisual(
|
|
6923
|
+
table.setBidiVisual(parseOoxmlBoolean(tblPrObj['w:bidiVisual']));
|
|
6297
6924
|
}
|
|
6298
6925
|
|
|
6299
6926
|
// Parse table width — always set when w:tblW is present, including w:w="0" w:type="auto"
|
|
@@ -6306,24 +6933,37 @@ export class DocumentParser {
|
|
|
6306
6933
|
table.setWidthType(widthType);
|
|
6307
6934
|
}
|
|
6308
6935
|
|
|
6309
|
-
// Parse table caption
|
|
6936
|
+
// Parse table caption — ST_String per §17.4.62. Cast through
|
|
6937
|
+
// String() so a purely-numeric caption ("42") is preserved as a
|
|
6938
|
+
// string in `formatting.caption` rather than a JS number.
|
|
6310
6939
|
if (tblPrObj['w:tblCaption']) {
|
|
6311
6940
|
const caption = tblPrObj['w:tblCaption']['@_w:val'];
|
|
6312
|
-
if (caption
|
|
6941
|
+
if (caption !== undefined && caption !== null && caption !== '') {
|
|
6942
|
+
table.setCaption(String(caption));
|
|
6943
|
+
}
|
|
6313
6944
|
}
|
|
6314
6945
|
|
|
6315
|
-
// Parse table description
|
|
6946
|
+
// Parse table description — ST_String per §17.4.63.
|
|
6316
6947
|
if (tblPrObj['w:tblDescription']) {
|
|
6317
6948
|
const description = tblPrObj['w:tblDescription']['@_w:val'];
|
|
6318
|
-
if (description
|
|
6949
|
+
if (description !== undefined && description !== null && description !== '') {
|
|
6950
|
+
table.setDescription(String(description));
|
|
6951
|
+
}
|
|
6319
6952
|
}
|
|
6320
6953
|
|
|
6321
|
-
// Parse cell spacing
|
|
6954
|
+
// Parse table-level cell spacing (w:tblCellSpacing) per ECMA-376
|
|
6955
|
+
// §17.4.44 CT_TblCellSpacing. w:w is ST_MeasurementOrPercent; 0 is
|
|
6956
|
+
// a legal "explicit zero spacing" value (overrides any style-level
|
|
6957
|
+
// inherited tblCellSpacing). The emitter uses `!== undefined`, so
|
|
6958
|
+
// the previous `spacing > 0` gate created a parser/emitter
|
|
6959
|
+
// asymmetry: a tracked table-property change recording a *previous*
|
|
6960
|
+
// state of `<w:tblCellSpacing w:w="0" …/>` lost the override on
|
|
6961
|
+
// every round-trip.
|
|
6322
6962
|
if (tblPrObj['w:tblCellSpacing']) {
|
|
6323
|
-
const
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6963
|
+
const rawW = tblPrObj['w:tblCellSpacing']['@_w:w'];
|
|
6964
|
+
if (isExplicitlySet(rawW)) {
|
|
6965
|
+
table.setCellSpacing(safeParseInt(rawW));
|
|
6966
|
+
const spacingType = tblPrObj['w:tblCellSpacing']['@_w:type'] || 'dxa';
|
|
6327
6967
|
table.setCellSpacingType(spacingType);
|
|
6328
6968
|
}
|
|
6329
6969
|
}
|
|
@@ -6342,7 +6982,7 @@ export class DocumentParser {
|
|
|
6342
6982
|
table.setIndent(indentVal);
|
|
6343
6983
|
const indentType = tblPrObj['w:tblInd']['@_w:type'];
|
|
6344
6984
|
if (indentType) {
|
|
6345
|
-
table.setIndentType(indentType as import('../elements/Table').TableWidthType);
|
|
6985
|
+
table.setIndentType(indentType as import('../elements/Table.js').TableWidthType);
|
|
6346
6986
|
}
|
|
6347
6987
|
}
|
|
6348
6988
|
|
|
@@ -6408,15 +7048,26 @@ export class DocumentParser {
|
|
|
6408
7048
|
}
|
|
6409
7049
|
}
|
|
6410
7050
|
|
|
6411
|
-
// Parse table borders (w:tblBorders) per ECMA-376 Part 1 §17.4.40
|
|
7051
|
+
// Parse table borders (w:tblBorders) per ECMA-376 Part 1 §17.4.40.
|
|
7052
|
+
// left / right have bidi-aware aliases `w:start` / `w:end` (the
|
|
7053
|
+
// preferred spelling in modern Word-authored documents). Prefer
|
|
7054
|
+
// them when present, falling back to the legacy names — the
|
|
7055
|
+
// internal model stores under `left` / `right`, matching the
|
|
7056
|
+
// emitter. Without this fallback, any table whose side borders
|
|
7057
|
+
// were authored with the bidi-aware form silently lost those
|
|
7058
|
+
// borders on every round-trip (the emitter would replace them
|
|
7059
|
+
// with absent w:left/w:right, and the parser would never revive
|
|
7060
|
+
// the w:start/w:end it dropped).
|
|
6412
7061
|
if (tblPrObj['w:tblBorders']) {
|
|
6413
7062
|
const bordersObj = tblPrObj['w:tblBorders'];
|
|
6414
|
-
const borders: import('../elements/Table').TableBorders = {};
|
|
7063
|
+
const borders: import('../elements/Table.js').TableBorders = {};
|
|
6415
7064
|
|
|
6416
7065
|
if (bordersObj['w:top']) borders.top = this.parseBorderElement(bordersObj['w:top']);
|
|
6417
7066
|
if (bordersObj['w:bottom']) borders.bottom = this.parseBorderElement(bordersObj['w:bottom']);
|
|
6418
|
-
|
|
6419
|
-
if (
|
|
7067
|
+
const leftBorder = bordersObj['w:start'] ?? bordersObj['w:left'];
|
|
7068
|
+
if (leftBorder) borders.left = this.parseBorderElement(leftBorder);
|
|
7069
|
+
const rightBorder = bordersObj['w:end'] ?? bordersObj['w:right'];
|
|
7070
|
+
if (rightBorder) borders.right = this.parseBorderElement(rightBorder);
|
|
6420
7071
|
if (bordersObj['w:insideH'])
|
|
6421
7072
|
borders.insideH = this.parseBorderElement(bordersObj['w:insideH']);
|
|
6422
7073
|
if (bordersObj['w:insideV'])
|
|
@@ -6432,8 +7083,8 @@ export class DocumentParser {
|
|
|
6432
7083
|
const changeObj = tblPrObj['w:tblPrChange'];
|
|
6433
7084
|
table.setTblPrChange({
|
|
6434
7085
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6435
|
-
author: changeObj['@_w:author']
|
|
6436
|
-
date: changeObj['@_w:date']
|
|
7086
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7087
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6437
7088
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:tblPr']),
|
|
6438
7089
|
});
|
|
6439
7090
|
}
|
|
@@ -6510,14 +7161,20 @@ export class DocumentParser {
|
|
|
6510
7161
|
private parseTableRowPropertiesFromObject(trPrObj: any, row: TableRow): void {
|
|
6511
7162
|
if (!trPrObj) return;
|
|
6512
7163
|
|
|
6513
|
-
// Parse row height (w:trHeight) per ECMA-376 Part 1 §17.4.81
|
|
6514
|
-
//
|
|
7164
|
+
// Parse row height (w:trHeight) per ECMA-376 Part 1 §17.4.81.
|
|
7165
|
+
// w:val is ST_TwipsMeasure; zero is a valid value and, combined
|
|
7166
|
+
// with w:hRule="exact", represents a hidden / collapsed row.
|
|
7167
|
+
// XMLParser coerces "0" to the number 0 (falsy), and the previous
|
|
7168
|
+
// `heightVal > 0` gate silently dropped explicit zero-height rows
|
|
7169
|
+
// even though the emitter (TableRow.ts §914) preserves them via
|
|
7170
|
+
// `!== undefined`. Route through isExplicitlySet so zero survives.
|
|
7171
|
+
// Per §17.18.33 (ST_HeightRule), when w:hRule is absent the
|
|
7172
|
+
// default is "auto".
|
|
6515
7173
|
if (trPrObj['w:trHeight']) {
|
|
6516
|
-
const
|
|
7174
|
+
const rawVal = trPrObj['w:trHeight']['@_w:val'];
|
|
6517
7175
|
const heightRule = trPrObj['w:trHeight']['@_w:hRule'];
|
|
6518
|
-
if (
|
|
6519
|
-
|
|
6520
|
-
// so we set height first, then override the rule only if explicitly present
|
|
7176
|
+
if (isExplicitlySet(rawVal)) {
|
|
7177
|
+
const heightVal = safeParseInt(rawVal);
|
|
6521
7178
|
row.setHeight(heightVal);
|
|
6522
7179
|
if (heightRule) {
|
|
6523
7180
|
row.setHeightRule(heightRule);
|
|
@@ -6529,14 +7186,14 @@ export class DocumentParser {
|
|
|
6529
7186
|
}
|
|
6530
7187
|
}
|
|
6531
7188
|
|
|
6532
|
-
// Parse table header row (w:tblHeader) per ECMA-376 Part 1 §17.4.49
|
|
7189
|
+
// Parse table header row (w:tblHeader) per ECMA-376 Part 1 §17.4.49 — CT_OnOff
|
|
6533
7190
|
if (trPrObj['w:tblHeader']) {
|
|
6534
|
-
row.setHeader(
|
|
7191
|
+
row.setHeader(parseOoxmlBoolean(trPrObj['w:tblHeader']));
|
|
6535
7192
|
}
|
|
6536
7193
|
|
|
6537
|
-
// Parse can't split (w:cantSplit) per ECMA-376 Part 1 §17.4.5
|
|
7194
|
+
// Parse can't split (w:cantSplit) per ECMA-376 Part 1 §17.4.5 — CT_OnOff
|
|
6538
7195
|
if (trPrObj['w:cantSplit']) {
|
|
6539
|
-
row.setCantSplit(
|
|
7196
|
+
row.setCantSplit(parseOoxmlBoolean(trPrObj['w:cantSplit']));
|
|
6540
7197
|
}
|
|
6541
7198
|
|
|
6542
7199
|
// Parse row justification (w:jc) per ECMA-376 Part 1 §17.4.79
|
|
@@ -6547,9 +7204,9 @@ export class DocumentParser {
|
|
|
6547
7204
|
}
|
|
6548
7205
|
}
|
|
6549
7206
|
|
|
6550
|
-
// Parse hidden (w:hidden) per ECMA-376 Part 1 §17.4.23
|
|
7207
|
+
// Parse hidden (w:hidden) per ECMA-376 Part 1 §17.4.23 — CT_OnOff
|
|
6551
7208
|
if (trPrObj['w:hidden']) {
|
|
6552
|
-
row.setHidden(
|
|
7209
|
+
row.setHidden(parseOoxmlBoolean(trPrObj['w:hidden']));
|
|
6553
7210
|
}
|
|
6554
7211
|
|
|
6555
7212
|
// Parse grid before (w:gridBefore) per ECMA-376 Part 1 §17.4.15
|
|
@@ -6568,30 +7225,36 @@ export class DocumentParser {
|
|
|
6568
7225
|
}
|
|
6569
7226
|
}
|
|
6570
7227
|
|
|
6571
|
-
// Parse width before (w:wBefore) per ECMA-376 Part 1 §17.4.83
|
|
7228
|
+
// Parse width before (w:wBefore) per ECMA-376 Part 1 §17.4.83.
|
|
7229
|
+
// w:w is ST_TblWidth; 0 paired with w:type="auto" is the idiomatic
|
|
7230
|
+
// "no width" form, and explicit 0 in dxa twips can override an
|
|
7231
|
+
// inherited wBefore. Previous `w > 0` gate silently dropped both.
|
|
6572
7232
|
if (trPrObj['w:wBefore']) {
|
|
6573
|
-
const
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
row.setWBefore(
|
|
7233
|
+
const rawW = trPrObj['w:wBefore']['@_w:w'];
|
|
7234
|
+
if (isExplicitlySet(rawW)) {
|
|
7235
|
+
const type = (trPrObj['w:wBefore']['@_w:type'] as string | undefined) || 'dxa';
|
|
7236
|
+
row.setWBefore(safeParseInt(rawW), type);
|
|
6577
7237
|
}
|
|
6578
7238
|
}
|
|
6579
7239
|
|
|
6580
|
-
// Parse width after (w:wAfter) per ECMA-376 Part 1 §17.4.82
|
|
7240
|
+
// Parse width after (w:wAfter) per ECMA-376 Part 1 §17.4.82 — same
|
|
7241
|
+
// ST_TblWidth semantics as wBefore.
|
|
6581
7242
|
if (trPrObj['w:wAfter']) {
|
|
6582
|
-
const
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
row.setWAfter(
|
|
7243
|
+
const rawW = trPrObj['w:wAfter']['@_w:w'];
|
|
7244
|
+
if (isExplicitlySet(rawW)) {
|
|
7245
|
+
const type = (trPrObj['w:wAfter']['@_w:type'] as string | undefined) || 'dxa';
|
|
7246
|
+
row.setWAfter(safeParseInt(rawW), type);
|
|
6586
7247
|
}
|
|
6587
7248
|
}
|
|
6588
7249
|
|
|
6589
|
-
// Parse row-level cell spacing (w:tblCellSpacing)
|
|
7250
|
+
// Parse row-level cell spacing (w:tblCellSpacing). Zero is a valid
|
|
7251
|
+
// override — "explicitly no extra spacing" on a row overriding a
|
|
7252
|
+
// non-zero table-level tblCellSpacing.
|
|
6590
7253
|
if (trPrObj['w:tblCellSpacing']) {
|
|
6591
|
-
const
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
row.setRowCellSpacing(
|
|
7254
|
+
const rawW = trPrObj['w:tblCellSpacing']['@_w:w'];
|
|
7255
|
+
if (isExplicitlySet(rawW)) {
|
|
7256
|
+
const type = (trPrObj['w:tblCellSpacing']['@_w:type'] as string | undefined) || 'dxa';
|
|
7257
|
+
row.setRowCellSpacing(safeParseInt(rawW), type);
|
|
6595
7258
|
}
|
|
6596
7259
|
}
|
|
6597
7260
|
|
|
@@ -6603,11 +7266,39 @@ export class DocumentParser {
|
|
|
6603
7266
|
}
|
|
6604
7267
|
}
|
|
6605
7268
|
|
|
6606
|
-
// Parse divId (w:divId) per ECMA-376 Part 1 §17.4.9
|
|
7269
|
+
// Parse divId (w:divId) per ECMA-376 Part 1 §17.4.9. `w:val` is
|
|
7270
|
+
// ST_DecimalNumber; 0 is a valid reference to the first div in web
|
|
7271
|
+
// settings. The previous `val > 0` gate silently dropped it on load.
|
|
6607
7272
|
if (trPrObj['w:divId']) {
|
|
6608
|
-
const
|
|
6609
|
-
if (
|
|
6610
|
-
|
|
7273
|
+
const rawVal = trPrObj['w:divId']['@_w:val'];
|
|
7274
|
+
if (isExplicitlySet(rawVal)) {
|
|
7275
|
+
const parsed = safeParseInt(rawVal);
|
|
7276
|
+
if (!isNaN(parsed)) row.setDivId(parsed);
|
|
7277
|
+
}
|
|
7278
|
+
}
|
|
7279
|
+
|
|
7280
|
+
// Parse tracked row insertion / deletion (CT_TrackChange inside CT_TrPr)
|
|
7281
|
+
// per ECMA-376 Part 1 §17.13.5.19 (ins) / §17.13.5.14 (del). These mark
|
|
7282
|
+
// the entire row as a tracked revision; a previous version silently
|
|
7283
|
+
// dropped both markers on load → save because the parser skipped them.
|
|
7284
|
+
if (trPrObj['w:ins']) {
|
|
7285
|
+
const insObj = Array.isArray(trPrObj['w:ins']) ? trPrObj['w:ins'][0] : trPrObj['w:ins'];
|
|
7286
|
+
if (insObj && typeof insObj === 'object') {
|
|
7287
|
+
row.setRowInsertion({
|
|
7288
|
+
id: String(insObj['@_w:id'] ?? '0'),
|
|
7289
|
+
author: String(insObj['@_w:author'] ?? ''),
|
|
7290
|
+
date: String(insObj['@_w:date'] ?? ''),
|
|
7291
|
+
});
|
|
7292
|
+
}
|
|
7293
|
+
}
|
|
7294
|
+
if (trPrObj['w:del']) {
|
|
7295
|
+
const delObj = Array.isArray(trPrObj['w:del']) ? trPrObj['w:del'][0] : trPrObj['w:del'];
|
|
7296
|
+
if (delObj && typeof delObj === 'object') {
|
|
7297
|
+
row.setRowDeletion({
|
|
7298
|
+
id: String(delObj['@_w:id'] ?? '0'),
|
|
7299
|
+
author: String(delObj['@_w:author'] ?? ''),
|
|
7300
|
+
date: String(delObj['@_w:date'] ?? ''),
|
|
7301
|
+
});
|
|
6611
7302
|
}
|
|
6612
7303
|
}
|
|
6613
7304
|
|
|
@@ -6616,8 +7307,8 @@ export class DocumentParser {
|
|
|
6616
7307
|
const changeObj = trPrObj['w:trPrChange'];
|
|
6617
7308
|
row.setTrPrChange({
|
|
6618
7309
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6619
|
-
author: changeObj['@_w:author']
|
|
6620
|
-
date: changeObj['@_w:date']
|
|
7310
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7311
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6621
7312
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:trPr']),
|
|
6622
7313
|
});
|
|
6623
7314
|
}
|
|
@@ -6633,11 +7324,15 @@ export class DocumentParser {
|
|
|
6633
7324
|
|
|
6634
7325
|
const exceptions: any = {};
|
|
6635
7326
|
|
|
6636
|
-
// Parse table width exception (w:tblW)
|
|
7327
|
+
// Parse table width exception (w:tblW). The `val > 0` gate previously
|
|
7328
|
+
// dropped both w:w="0" (explicit zero-width override, valid when
|
|
7329
|
+
// paired with w:type="nil"/"auto") and negative overrides. Route
|
|
7330
|
+
// through isExplicitlySet + safeParseInt so zero and negative widths
|
|
7331
|
+
// round-trip.
|
|
6637
7332
|
if (tblPrExObj['w:tblW']) {
|
|
6638
|
-
const
|
|
6639
|
-
if (
|
|
6640
|
-
exceptions.width =
|
|
7333
|
+
const rawW = tblPrExObj['w:tblW']['@_w:w'];
|
|
7334
|
+
if (isExplicitlySet(rawW)) {
|
|
7335
|
+
exceptions.width = safeParseInt(rawW);
|
|
6641
7336
|
}
|
|
6642
7337
|
}
|
|
6643
7338
|
|
|
@@ -6649,19 +7344,26 @@ export class DocumentParser {
|
|
|
6649
7344
|
}
|
|
6650
7345
|
}
|
|
6651
7346
|
|
|
6652
|
-
// Parse cell spacing exception (w:tblCellSpacing)
|
|
7347
|
+
// Parse cell spacing exception (w:tblCellSpacing). Zero-value
|
|
7348
|
+
// override is valid (= "explicit no cell spacing" on a row that
|
|
7349
|
+
// would otherwise inherit non-zero spacing from the table-level
|
|
7350
|
+
// tblCellSpacing).
|
|
6653
7351
|
if (tblPrExObj['w:tblCellSpacing']) {
|
|
6654
|
-
const
|
|
6655
|
-
if (
|
|
6656
|
-
exceptions.cellSpacing =
|
|
7352
|
+
const rawW = tblPrExObj['w:tblCellSpacing']['@_w:w'];
|
|
7353
|
+
if (isExplicitlySet(rawW)) {
|
|
7354
|
+
exceptions.cellSpacing = safeParseInt(rawW);
|
|
6657
7355
|
}
|
|
6658
7356
|
}
|
|
6659
7357
|
|
|
6660
|
-
// Parse table indentation exception (w:tblInd)
|
|
7358
|
+
// Parse table indentation exception (w:tblInd). Per ECMA-376
|
|
7359
|
+
// §17.4.62 CT_TblWidth, w:w is ST_MeasurementOrPercent — 0 is a
|
|
7360
|
+
// legal "reset" value and negative values indicate an outdent (table
|
|
7361
|
+
// hanging into the page margin). The previous `val > 0` check
|
|
7362
|
+
// silently dropped both.
|
|
6661
7363
|
if (tblPrExObj['w:tblInd']) {
|
|
6662
|
-
const
|
|
6663
|
-
if (
|
|
6664
|
-
exceptions.indentation =
|
|
7364
|
+
const rawW = tblPrExObj['w:tblInd']['@_w:w'];
|
|
7365
|
+
if (isExplicitlySet(rawW)) {
|
|
7366
|
+
exceptions.indentation = safeParseInt(rawW);
|
|
6665
7367
|
}
|
|
6666
7368
|
}
|
|
6667
7369
|
|
|
@@ -6692,15 +7394,40 @@ export class DocumentParser {
|
|
|
6692
7394
|
const borderNames = ['top', 'bottom', 'left', 'right', 'insideH', 'insideV'];
|
|
6693
7395
|
|
|
6694
7396
|
for (const name of borderNames) {
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
7397
|
+
// Prefer bidi-aware `w:start`/`w:end` aliases over legacy `w:left`/
|
|
7398
|
+
// `w:right` per ECMA-376 §17.4.40 CT_TblBorders. Modern Word-
|
|
7399
|
+
// authored documents emit the bidi-aware form by default; the
|
|
7400
|
+
// internal model stores under the legacy keys to match the emitter.
|
|
7401
|
+
const aliasKey = name === 'left' ? 'w:start' : name === 'right' ? 'w:end' : undefined;
|
|
7402
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${name}`];
|
|
7403
|
+
if (borderObj) {
|
|
6698
7404
|
borders[name] = {};
|
|
6699
7405
|
|
|
7406
|
+
// Full CT_Border attribute set (§17.18.2) — previously only the four
|
|
7407
|
+
// basic attrs were read, so tblPrEx borders lost themed-color linkage
|
|
7408
|
+
// on every round-trip.
|
|
6700
7409
|
if (borderObj['@_w:val']) borders[name].style = borderObj['@_w:val'];
|
|
6701
7410
|
if (borderObj['@_w:sz']) borders[name].size = parseInt(borderObj['@_w:sz'], 10);
|
|
6702
7411
|
if (borderObj['@_w:space']) borders[name].space = parseInt(borderObj['@_w:space'], 10);
|
|
6703
|
-
if (borderObj['@_w:color']) borders[name].color = borderObj['@_w:color'];
|
|
7412
|
+
if (borderObj['@_w:color']) borders[name].color = String(borderObj['@_w:color']);
|
|
7413
|
+
// String(...) cast: themeTint / themeShade are ST_UcharHexNumber
|
|
7414
|
+
// (2-char hex). XMLParser coerces purely-digit hex to numbers —
|
|
7415
|
+
// cast so the string contract on the model is preserved.
|
|
7416
|
+
if (borderObj['@_w:themeColor']) {
|
|
7417
|
+
borders[name].themeColor = String(borderObj['@_w:themeColor']);
|
|
7418
|
+
}
|
|
7419
|
+
if (borderObj['@_w:themeTint']) {
|
|
7420
|
+
borders[name].themeTint = String(borderObj['@_w:themeTint']);
|
|
7421
|
+
}
|
|
7422
|
+
if (borderObj['@_w:themeShade']) {
|
|
7423
|
+
borders[name].themeShade = String(borderObj['@_w:themeShade']);
|
|
7424
|
+
}
|
|
7425
|
+
if (borderObj['@_w:shadow'] !== undefined) {
|
|
7426
|
+
borders[name].shadow = parseOnOffAttribute(String(borderObj['@_w:shadow']), true);
|
|
7427
|
+
}
|
|
7428
|
+
if (borderObj['@_w:frame'] !== undefined) {
|
|
7429
|
+
borders[name].frame = parseOnOffAttribute(String(borderObj['@_w:frame']), true);
|
|
7430
|
+
}
|
|
6704
7431
|
}
|
|
6705
7432
|
}
|
|
6706
7433
|
|
|
@@ -6721,12 +7448,26 @@ export class DocumentParser {
|
|
|
6721
7448
|
// Parse cell properties (w:tcPr) per ECMA-376 Part 1 §17.4.42
|
|
6722
7449
|
const tcPr = cellObj['w:tcPr'];
|
|
6723
7450
|
if (tcPr) {
|
|
6724
|
-
// Parse cell width (w:tcW) with type per ECMA-376 Part 1 §17.4.
|
|
7451
|
+
// Parse cell width (w:tcW) with type per ECMA-376 Part 1 §17.4.72
|
|
7452
|
+
// CT_TblWidth — w:w is ST_MeasurementOrPercent, w:type is
|
|
7453
|
+
// ST_TblWidth. Zero is a legal explicit override:
|
|
7454
|
+
// - `w:w="0" w:type="auto"` is the idiomatic "size to content"
|
|
7455
|
+
// form (also the default when w:tcW is absent).
|
|
7456
|
+
// - `w:w="0" w:type="dxa"` / `"pct"` / `"nil"` explicitly
|
|
7457
|
+
// override an inherited non-zero width back to zero.
|
|
7458
|
+
// The emitter at TableCell.ts:1353 uses `!== undefined`, so the
|
|
7459
|
+
// previous `widthVal > 0 || widthType === 'auto'` gate created a
|
|
7460
|
+
// parser/emitter asymmetry — any cell with an explicit zero
|
|
7461
|
+
// override in a non-auto width type silently reinherited the
|
|
7462
|
+
// style-level width on every round-trip.
|
|
6725
7463
|
if (tcPr['w:tcW']) {
|
|
6726
|
-
const
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
cell.setWidthType(
|
|
7464
|
+
const rawW = tcPr['w:tcW']['@_w:w'];
|
|
7465
|
+
if (isExplicitlySet(rawW)) {
|
|
7466
|
+
const widthType = (tcPr['w:tcW']['@_w:type'] as string | undefined) || 'dxa';
|
|
7467
|
+
cell.setWidthType(
|
|
7468
|
+
safeParseInt(rawW),
|
|
7469
|
+
widthType as import('../elements/TableCell.js').CellWidthType
|
|
7470
|
+
);
|
|
6730
7471
|
}
|
|
6731
7472
|
}
|
|
6732
7473
|
|
|
@@ -6738,7 +7479,11 @@ export class DocumentParser {
|
|
|
6738
7479
|
}
|
|
6739
7480
|
}
|
|
6740
7481
|
|
|
6741
|
-
// Parse cell borders (w:tcBorders)
|
|
7482
|
+
// Parse cell borders (w:tcBorders) per ECMA-376 Part 1 §17.4.66.
|
|
7483
|
+
// Supports both legacy LTR names (w:left / w:right) and bidi-
|
|
7484
|
+
// aware aliases (w:start / w:end). Prefer w:start / w:end when
|
|
7485
|
+
// present. Includes diagonal borders (w:tl2br / w:tr2bl) which
|
|
7486
|
+
// are cell-specific.
|
|
6742
7487
|
if (tcPr['w:tcBorders']) {
|
|
6743
7488
|
const bordersObj = tcPr['w:tcBorders'];
|
|
6744
7489
|
const borders: any = {};
|
|
@@ -6746,8 +7491,10 @@ export class DocumentParser {
|
|
|
6746
7491
|
if (bordersObj['w:top']) borders.top = this.parseBorderElement(bordersObj['w:top']);
|
|
6747
7492
|
if (bordersObj['w:bottom'])
|
|
6748
7493
|
borders.bottom = this.parseBorderElement(bordersObj['w:bottom']);
|
|
6749
|
-
|
|
6750
|
-
if (
|
|
7494
|
+
const leftBorder = bordersObj['w:start'] ?? bordersObj['w:left'];
|
|
7495
|
+
if (leftBorder) borders.left = this.parseBorderElement(leftBorder);
|
|
7496
|
+
const rightBorder = bordersObj['w:end'] ?? bordersObj['w:right'];
|
|
7497
|
+
if (rightBorder) borders.right = this.parseBorderElement(rightBorder);
|
|
6751
7498
|
if (bordersObj['w:tl2br']) borders.tl2br = this.parseBorderElement(bordersObj['w:tl2br']);
|
|
6752
7499
|
if (bordersObj['w:tr2bl']) borders.tr2bl = this.parseBorderElement(bordersObj['w:tr2bl']);
|
|
6753
7500
|
|
|
@@ -6790,10 +7537,15 @@ export class DocumentParser {
|
|
|
6790
7537
|
}
|
|
6791
7538
|
}
|
|
6792
7539
|
|
|
6793
|
-
// Parse vertical alignment (w:vAlign)
|
|
7540
|
+
// Parse vertical alignment (w:vAlign) per ECMA-376 §17.4.83.
|
|
7541
|
+
// ST_VerticalJc has four values (§17.18.101): top, center, both,
|
|
7542
|
+
// bottom. The previous whitelist dropped "both" silently — the
|
|
7543
|
+
// style-level parser accepts it, so the asymmetry truncated cell
|
|
7544
|
+
// vertical alignment on cells using the "both" (justified)
|
|
7545
|
+
// vertical alignment on load.
|
|
6794
7546
|
if (tcPr['w:vAlign']) {
|
|
6795
7547
|
const valign = tcPr['w:vAlign']['@_w:val'];
|
|
6796
|
-
if (valign
|
|
7548
|
+
if (valign === 'top' || valign === 'center' || valign === 'both' || valign === 'bottom') {
|
|
6797
7549
|
cell.setVerticalAlignment(valign);
|
|
6798
7550
|
}
|
|
6799
7551
|
}
|
|
@@ -6814,14 +7566,14 @@ export class DocumentParser {
|
|
|
6814
7566
|
}
|
|
6815
7567
|
}
|
|
6816
7568
|
|
|
6817
|
-
// Parse no wrap (w:noWrap) per ECMA-376 Part 1 §17.4.34
|
|
7569
|
+
// Parse no wrap (w:noWrap) per ECMA-376 Part 1 §17.4.34 — CT_OnOff
|
|
6818
7570
|
if (tcPr['w:noWrap']) {
|
|
6819
|
-
cell.setNoWrap(
|
|
7571
|
+
cell.setNoWrap(parseOoxmlBoolean(tcPr['w:noWrap']));
|
|
6820
7572
|
}
|
|
6821
7573
|
|
|
6822
|
-
// Parse hide mark (w:hideMark) per ECMA-376 Part 1 §17.4.24
|
|
7574
|
+
// Parse hide mark (w:hideMark) per ECMA-376 Part 1 §17.4.24 — CT_OnOff
|
|
6823
7575
|
if (tcPr['w:hideMark']) {
|
|
6824
|
-
cell.setHideMark(
|
|
7576
|
+
cell.setHideMark(parseOoxmlBoolean(tcPr['w:hideMark']));
|
|
6825
7577
|
}
|
|
6826
7578
|
|
|
6827
7579
|
// Parse headers (w:headers) per ECMA-376 Part 1 §17.4.26
|
|
@@ -6832,9 +7584,9 @@ export class DocumentParser {
|
|
|
6832
7584
|
}
|
|
6833
7585
|
}
|
|
6834
7586
|
|
|
6835
|
-
// Parse fit text (w:tcFitText) per ECMA-376 Part 1 §17.4.68
|
|
7587
|
+
// Parse fit text (w:tcFitText) per ECMA-376 Part 1 §17.4.68 — CT_OnOff
|
|
6836
7588
|
if (tcPr['w:tcFitText']) {
|
|
6837
|
-
cell.setFitText(
|
|
7589
|
+
cell.setFitText(parseOoxmlBoolean(tcPr['w:tcFitText']));
|
|
6838
7590
|
}
|
|
6839
7591
|
|
|
6840
7592
|
// Parse vertical merge (w:vMerge) per ECMA-376 Part 1 §17.4.85
|
|
@@ -6861,10 +7613,11 @@ export class DocumentParser {
|
|
|
6861
7613
|
// Parse table cell insertion marker (w:cellIns) per ECMA-376 Part 1 §17.13.5.5
|
|
6862
7614
|
if (tcPr['w:cellIns']) {
|
|
6863
7615
|
const cellIns = tcPr['w:cellIns'];
|
|
6864
|
-
const id = parseInt(cellIns['@_w:id']
|
|
6865
|
-
const author =
|
|
7616
|
+
const id = parseInt(String(cellIns['@_w:id'] ?? '0'), 10);
|
|
7617
|
+
const author =
|
|
7618
|
+
cellIns['@_w:author'] !== undefined ? String(cellIns['@_w:author']) : 'Unknown';
|
|
6866
7619
|
const dateAttr = cellIns['@_w:date'];
|
|
6867
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7620
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6868
7621
|
|
|
6869
7622
|
const revision = new Revision({
|
|
6870
7623
|
id,
|
|
@@ -6879,10 +7632,11 @@ export class DocumentParser {
|
|
|
6879
7632
|
// Parse table cell deletion marker (w:cellDel) per ECMA-376 Part 1 §17.13.5.6
|
|
6880
7633
|
if (tcPr['w:cellDel']) {
|
|
6881
7634
|
const cellDel = tcPr['w:cellDel'];
|
|
6882
|
-
const id = parseInt(cellDel['@_w:id']
|
|
6883
|
-
const author =
|
|
7635
|
+
const id = parseInt(String(cellDel['@_w:id'] ?? '0'), 10);
|
|
7636
|
+
const author =
|
|
7637
|
+
cellDel['@_w:author'] !== undefined ? String(cellDel['@_w:author']) : 'Unknown';
|
|
6884
7638
|
const dateAttr = cellDel['@_w:date'];
|
|
6885
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7639
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6886
7640
|
|
|
6887
7641
|
const revision = new Revision({
|
|
6888
7642
|
id,
|
|
@@ -6897,10 +7651,11 @@ export class DocumentParser {
|
|
|
6897
7651
|
// Parse table cell merge marker (w:cellMerge) per ECMA-376 Part 1 §17.13.5.4
|
|
6898
7652
|
if (tcPr['w:cellMerge']) {
|
|
6899
7653
|
const cellMerge = tcPr['w:cellMerge'];
|
|
6900
|
-
const id = parseInt(cellMerge['@_w:id']
|
|
6901
|
-
const author =
|
|
7654
|
+
const id = parseInt(String(cellMerge['@_w:id'] ?? '0'), 10);
|
|
7655
|
+
const author =
|
|
7656
|
+
cellMerge['@_w:author'] !== undefined ? String(cellMerge['@_w:author']) : 'Unknown';
|
|
6902
7657
|
const dateAttr = cellMerge['@_w:date'];
|
|
6903
|
-
const date = dateAttr ? new Date(dateAttr) : new Date();
|
|
7658
|
+
const date = dateAttr !== undefined ? new Date(String(dateAttr)) : new Date();
|
|
6904
7659
|
const vMergeAttr = cellMerge['@_w:vMerge'];
|
|
6905
7660
|
const vMergeOrigAttr = cellMerge['@_w:vMergeOrig'];
|
|
6906
7661
|
// ST_AnnotationVMerge uses "rest"/"cont" but API uses "restart"/"continue"
|
|
@@ -6925,8 +7680,8 @@ export class DocumentParser {
|
|
|
6925
7680
|
const changeObj = tcPr['w:tcPrChange'];
|
|
6926
7681
|
cell.setTcPrChange({
|
|
6927
7682
|
id: String(changeObj['@_w:id'] || '0'),
|
|
6928
|
-
author: changeObj['@_w:author']
|
|
6929
|
-
date: changeObj['@_w:date']
|
|
7683
|
+
author: changeObj['@_w:author'] !== undefined ? String(changeObj['@_w:author']) : '',
|
|
7684
|
+
date: changeObj['@_w:date'] !== undefined ? String(changeObj['@_w:date']) : '',
|
|
6930
7685
|
previousProperties: this.parseGenericPreviousProperties(changeObj['w:tcPr']),
|
|
6931
7686
|
});
|
|
6932
7687
|
}
|
|
@@ -7174,28 +7929,40 @@ export class DocumentParser {
|
|
|
7174
7929
|
// Parse SDT properties (sdtPr)
|
|
7175
7930
|
const sdtPr = sdtObj['w:sdtPr'];
|
|
7176
7931
|
if (sdtPr) {
|
|
7177
|
-
// Parse
|
|
7932
|
+
// Parse `<w:id w:val="…"/>` per ECMA-376 §17.5.2.18. `w:val` is
|
|
7933
|
+
// ST_DecimalNumber (xsd:integer) — 0 is legal. XMLParser coerces
|
|
7934
|
+
// `"0"` to the number `0`, so the previous truthy gate silently
|
|
7935
|
+
// dropped w:id=0 on every load → save cycle. The emitter uses
|
|
7936
|
+
// `!== undefined`, creating a parser/emitter asymmetry.
|
|
7178
7937
|
const idElement = sdtPr['w:id'];
|
|
7179
|
-
if (idElement?.['@_w:val']) {
|
|
7180
|
-
|
|
7938
|
+
if (isExplicitlySet(idElement?.['@_w:val'])) {
|
|
7939
|
+
const parsed = safeParseInt(idElement['@_w:val']);
|
|
7940
|
+
if (!isNaN(parsed)) properties.id = parsed;
|
|
7181
7941
|
}
|
|
7182
7942
|
|
|
7183
|
-
// Parse tag
|
|
7943
|
+
// Parse `<w:tag w:val="…"/>` per ECMA-376 §17.5.2.34. `w:val`
|
|
7944
|
+
// is ST_String — any string is legal, including numeric-looking
|
|
7945
|
+
// strings like "123" that XMLParser coerces to the number 123.
|
|
7946
|
+
// Cast via `String(…)` so the tag round-trips as text rather
|
|
7947
|
+
// than leaking a JS number into a `tag?: string` field.
|
|
7184
7948
|
const tagElement = sdtPr['w:tag'];
|
|
7185
|
-
if (tagElement?.['@_w:val']) {
|
|
7186
|
-
properties.tag = tagElement['@_w:val'];
|
|
7949
|
+
if (tagElement?.['@_w:val'] !== undefined) {
|
|
7950
|
+
properties.tag = String(tagElement['@_w:val']);
|
|
7187
7951
|
}
|
|
7188
7952
|
|
|
7189
|
-
// Parse lock
|
|
7953
|
+
// Parse lock — ST_Lock enum: "sdtLocked" / "contentLocked" /
|
|
7954
|
+
// "sdtContentLocked" / "unlocked". Always a non-numeric string,
|
|
7955
|
+
// so no XMLParser coercion concern; truthy check fine.
|
|
7190
7956
|
const lockElement = sdtPr['w:lock'];
|
|
7191
7957
|
if (lockElement?.['@_w:val']) {
|
|
7192
7958
|
properties.lock = lockElement['@_w:val'];
|
|
7193
7959
|
}
|
|
7194
7960
|
|
|
7195
|
-
// Parse alias
|
|
7961
|
+
// Parse alias — ST_String. Same numeric-coercion concern as
|
|
7962
|
+
// `w:tag`; cast via `String(…)`.
|
|
7196
7963
|
const aliasElement = sdtPr['w:alias'];
|
|
7197
|
-
if (aliasElement?.['@_w:val']) {
|
|
7198
|
-
properties.alias = aliasElement['@_w:val'];
|
|
7964
|
+
if (aliasElement?.['@_w:val'] !== undefined) {
|
|
7965
|
+
properties.alias = String(aliasElement['@_w:val']);
|
|
7199
7966
|
}
|
|
7200
7967
|
|
|
7201
7968
|
// Parse control type from various elements
|
|
@@ -7204,9 +7971,18 @@ export class DocumentParser {
|
|
|
7204
7971
|
} else if (sdtPr['w:text']) {
|
|
7205
7972
|
properties.controlType = 'plainText';
|
|
7206
7973
|
const textElement = sdtPr['w:text'];
|
|
7974
|
+
// w:multiLine is an OPTIONAL ST_OnOff attribute per ECMA-376
|
|
7975
|
+
// §17.5.2.33 CT_SdtText. Only record a value when the source
|
|
7976
|
+
// actually set it — otherwise leave the field undefined so
|
|
7977
|
+
// the emitter (which uses `!== undefined`) preserves the
|
|
7978
|
+
// "attribute absent" state on round-trip. Previously the
|
|
7979
|
+
// parser unconditionally stored `false` for any absent
|
|
7980
|
+
// attribute, then the emitter wrote `w:multiLine="0"` —
|
|
7981
|
+
// adding spec-noise that wasn't in the source.
|
|
7982
|
+
const rawMultiLine = textElement?.['@_w:multiLine'];
|
|
7207
7983
|
properties.plainText = {
|
|
7208
7984
|
multiLine:
|
|
7209
|
-
|
|
7985
|
+
rawMultiLine === undefined ? undefined : parseOnOffAttribute(String(rawMultiLine)),
|
|
7210
7986
|
};
|
|
7211
7987
|
} else if (sdtPr['w:comboBox']) {
|
|
7212
7988
|
properties.controlType = 'comboBox';
|
|
@@ -7234,14 +8010,11 @@ export class DocumentParser {
|
|
|
7234
8010
|
} else if (sdtPr['w14:checkbox']) {
|
|
7235
8011
|
properties.controlType = 'checkbox';
|
|
7236
8012
|
const checkboxElement = sdtPr['w14:checkbox'];
|
|
7237
|
-
//
|
|
7238
|
-
|
|
8013
|
+
// <w14:checked> is CT_OnOff in the Word 2010+ extension namespace.
|
|
8014
|
+
// Honour every ST_OnOff literal ("1"/"0"/"true"/"false"/"on"/"off")
|
|
8015
|
+
// and treat a bare self-closing `<w14:checked/>` as true.
|
|
7239
8016
|
properties.checkbox = {
|
|
7240
|
-
checked:
|
|
7241
|
-
checkedVal === 1 ||
|
|
7242
|
-
checkedVal === '1' ||
|
|
7243
|
-
checkedVal === true ||
|
|
7244
|
-
checkedVal === 'true',
|
|
8017
|
+
checked: parseOoxmlBoolean(checkboxElement?.['w14:checked'], '@_w14:val'),
|
|
7245
8018
|
checkedState: String(checkboxElement?.['w14:checkedState']?.['@_w14:val'] ?? ''),
|
|
7246
8019
|
uncheckedState: String(checkboxElement?.['w14:uncheckedState']?.['@_w14:val'] ?? ''),
|
|
7247
8020
|
};
|
|
@@ -7291,11 +8064,10 @@ export class DocumentParser {
|
|
|
7291
8064
|
};
|
|
7292
8065
|
}
|
|
7293
8066
|
|
|
7294
|
-
// Parse showing placeholder flag (w:showingPlcHdr)
|
|
8067
|
+
// Parse showing placeholder flag (w:showingPlcHdr) — CT_OnOff per ECMA-376 §17.5.2.40
|
|
7295
8068
|
const showingPlcHdr = sdtPr['w:showingPlcHdr'];
|
|
7296
8069
|
if (showingPlcHdr) {
|
|
7297
|
-
|
|
7298
|
-
properties.showingPlcHdr = val === '1' || val === 'true' || val === true;
|
|
8070
|
+
properties.showingPlcHdr = parseOoxmlBoolean(showingPlcHdr);
|
|
7299
8071
|
}
|
|
7300
8072
|
}
|
|
7301
8073
|
|
|
@@ -7835,7 +8607,25 @@ export class DocumentParser {
|
|
|
7835
8607
|
}
|
|
7836
8608
|
|
|
7837
8609
|
/**
|
|
7838
|
-
* Helper to parse list items for combo box / dropdown
|
|
8610
|
+
* Helper to parse list items for combo box / dropdown per ECMA-376
|
|
8611
|
+
* Part 1 §17.5.2.13 CT_SdtListItem. `w:value` is required; both
|
|
8612
|
+
* `w:displayText` and `w:value` are ST_String so any string
|
|
8613
|
+
* (including the empty string) is legal.
|
|
8614
|
+
*
|
|
8615
|
+
* The previous truthy gate dropped legitimate list items whenever:
|
|
8616
|
+
* - `w:value="0"` / `w:value="123"` — XMLParser coerces numeric
|
|
8617
|
+
* strings to numbers; `0` fails the truthy check entirely, and
|
|
8618
|
+
* storing a raw number instead of a string breaks the `ListItem`
|
|
8619
|
+
* `value: string` contract downstream.
|
|
8620
|
+
* - `w:displayText=""` — empty displayText is legal (e.g. a
|
|
8621
|
+
* separator / blank choice); the gate dropped it.
|
|
8622
|
+
* The fix:
|
|
8623
|
+
* - Gate on presence (`!== undefined`), not truthiness.
|
|
8624
|
+
* - Coerce both attributes to `String(…)` so numeric-coerced
|
|
8625
|
+
* attribute values serialise back to their original textual form.
|
|
8626
|
+
* - Default missing `w:displayText` to the stringified `w:value`
|
|
8627
|
+
* (the idiomatic Word fallback when authors author list items
|
|
8628
|
+
* with only a value attribute).
|
|
7839
8629
|
*/
|
|
7840
8630
|
private parseListItems(element: any): any {
|
|
7841
8631
|
const items: any[] = [];
|
|
@@ -7843,17 +8633,18 @@ export class DocumentParser {
|
|
|
7843
8633
|
const itemArray = Array.isArray(listItems) ? listItems : listItems ? [listItems] : [];
|
|
7844
8634
|
|
|
7845
8635
|
for (const item of itemArray) {
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
}
|
|
8636
|
+
const rawValue = item['@_w:value'];
|
|
8637
|
+
if (rawValue === undefined) continue; // w:value is required by the schema
|
|
8638
|
+
const value = String(rawValue);
|
|
8639
|
+
const rawDisplay = item['@_w:displayText'];
|
|
8640
|
+
const displayText = rawDisplay === undefined ? value : String(rawDisplay);
|
|
8641
|
+
items.push({ displayText, value });
|
|
7852
8642
|
}
|
|
7853
8643
|
|
|
8644
|
+
const rawLast = element?.['@_w:lastValue'];
|
|
7854
8645
|
return {
|
|
7855
8646
|
items,
|
|
7856
|
-
lastValue:
|
|
8647
|
+
lastValue: rawLast === undefined ? undefined : String(rawLast),
|
|
7857
8648
|
};
|
|
7858
8649
|
}
|
|
7859
8650
|
|
|
@@ -8329,12 +9120,21 @@ export class DocumentParser {
|
|
|
8329
9120
|
if (color) border.color = color;
|
|
8330
9121
|
const space = XMLParser.extractAttribute(sideXml, 'w:space');
|
|
8331
9122
|
if (space) border.space = parseInt(space.toString(), 10);
|
|
9123
|
+
// w:shadow and w:frame are ST_OnOff per ECMA-376 §17.17.4.
|
|
9124
|
+
// Use `!== undefined` gating so explicit-false survives round-trip
|
|
9125
|
+
// (previous code only stored `true`, silently dropping `w:shadow="0"`).
|
|
8332
9126
|
const shadow = XMLParser.extractAttribute(sideXml, 'w:shadow');
|
|
8333
|
-
if (shadow
|
|
9127
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
8334
9128
|
const frame = XMLParser.extractAttribute(sideXml, 'w:frame');
|
|
8335
|
-
if (frame
|
|
9129
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
8336
9130
|
const themeColor = XMLParser.extractAttribute(sideXml, 'w:themeColor');
|
|
8337
9131
|
if (themeColor) border.themeColor = themeColor;
|
|
9132
|
+
// Theme tint / shade per §17.18.82 — CT_TopBorder/CT_BottomBorder extend
|
|
9133
|
+
// CT_Border so inherit the full themed-color attribute set.
|
|
9134
|
+
const themeTint = XMLParser.extractAttribute(sideXml, 'w:themeTint');
|
|
9135
|
+
if (themeTint) border.themeTint = themeTint;
|
|
9136
|
+
const themeShade = XMLParser.extractAttribute(sideXml, 'w:themeShade');
|
|
9137
|
+
if (themeShade) border.themeShade = themeShade;
|
|
8338
9138
|
const artId = XMLParser.extractAttribute(sideXml, 'w:id');
|
|
8339
9139
|
if (artId) border.artId = parseInt(artId.toString(), 10);
|
|
8340
9140
|
return Object.keys(border).length > 0 ? border : undefined;
|
|
@@ -8355,7 +9155,14 @@ export class DocumentParser {
|
|
|
8355
9155
|
}
|
|
8356
9156
|
}
|
|
8357
9157
|
|
|
8358
|
-
// Parse columns
|
|
9158
|
+
// Parse columns per ECMA-376 §17.6.4 CT_Columns. Every attribute
|
|
9159
|
+
// (num / sep / space / equalWidth) is optional with spec-defined
|
|
9160
|
+
// defaults — num defaults to 1, equalWidth to true, sep to false,
|
|
9161
|
+
// space to 720 twips. The previous `if (num)` gate silently dropped
|
|
9162
|
+
// every `<w:cols>` that relied on the default num=1 (e.g. a bare
|
|
9163
|
+
// `<w:cols w:sep="1" w:space="720"/>` specifying a single column
|
|
9164
|
+
// with a separator), which is the exact form Word emits when the
|
|
9165
|
+
// user toggles the column separator without changing column count.
|
|
8359
9166
|
const colsElements = XMLParser.extractElements(sectPr, 'w:cols');
|
|
8360
9167
|
if (colsElements.length > 0) {
|
|
8361
9168
|
const cols = colsElements[0];
|
|
@@ -8384,19 +9191,23 @@ export class DocumentParser {
|
|
|
8384
9191
|
}
|
|
8385
9192
|
}
|
|
8386
9193
|
|
|
8387
|
-
//
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
9194
|
+
// Spec default for num is 1; fall back to column-count from
|
|
9195
|
+
// child `<w:col>` children when available (the expanded per-column
|
|
9196
|
+
// form), otherwise to the literal default.
|
|
9197
|
+
const count = num
|
|
9198
|
+
? parseInt(num.toString(), 10)
|
|
9199
|
+
: columnWidths.length > 0
|
|
9200
|
+
? columnWidths.length
|
|
9201
|
+
: 1;
|
|
9202
|
+
|
|
9203
|
+
sectionProps.columns = {
|
|
9204
|
+
count,
|
|
9205
|
+
space: space ? parseInt(space.toString(), 10) : undefined,
|
|
9206
|
+
equalWidth: equalWidth ? parseOnOffAttribute(equalWidth) : undefined,
|
|
9207
|
+
separator: sep ? parseOnOffAttribute(sep) : undefined,
|
|
9208
|
+
columnWidths: columnWidths.length > 0 ? columnWidths : undefined,
|
|
9209
|
+
columnSpaces: hasColumnSpaces ? columnSpaces : undefined,
|
|
9210
|
+
};
|
|
8400
9211
|
}
|
|
8401
9212
|
}
|
|
8402
9213
|
|
|
@@ -8437,9 +9248,13 @@ export class DocumentParser {
|
|
|
8437
9248
|
}
|
|
8438
9249
|
}
|
|
8439
9250
|
|
|
8440
|
-
// Parse title page flag
|
|
8441
|
-
|
|
8442
|
-
|
|
9251
|
+
// Parse title page flag (w:titlePg) — CT_OnOff per ECMA-376 §17.6.23;
|
|
9252
|
+
// honour w:val so an explicit `w:val="0"` override of an inherited
|
|
9253
|
+
// true is not silently flipped to true.
|
|
9254
|
+
const titlePgEls = XMLParser.extractElements(sectPr, 'w:titlePg');
|
|
9255
|
+
if (titlePgEls.length > 0 && titlePgEls[0]) {
|
|
9256
|
+
const v = XMLParser.extractAttribute(titlePgEls[0], 'w:val');
|
|
9257
|
+
sectionProps.titlePage = parseOnOffAttribute(v, true);
|
|
8443
9258
|
}
|
|
8444
9259
|
|
|
8445
9260
|
// Parse header references
|
|
@@ -8522,14 +9337,18 @@ export class DocumentParser {
|
|
|
8522
9337
|
}
|
|
8523
9338
|
}
|
|
8524
9339
|
|
|
8525
|
-
// Parse bidi (
|
|
8526
|
-
|
|
8527
|
-
|
|
9340
|
+
// Parse bidi (w:bidi) — CT_OnOff per ECMA-376 §17.6.1 (RTL section)
|
|
9341
|
+
const bidiEls = XMLParser.extractElements(sectPr, 'w:bidi');
|
|
9342
|
+
if (bidiEls.length > 0 && bidiEls[0]) {
|
|
9343
|
+
const v = XMLParser.extractAttribute(bidiEls[0], 'w:val');
|
|
9344
|
+
sectionProps.bidi = parseOnOffAttribute(v, true);
|
|
8528
9345
|
}
|
|
8529
9346
|
|
|
8530
|
-
// Parse RTL gutter
|
|
8531
|
-
|
|
8532
|
-
|
|
9347
|
+
// Parse RTL gutter (w:rtlGutter) — CT_OnOff per ECMA-376 §17.6.16
|
|
9348
|
+
const rtlGutterEls = XMLParser.extractElements(sectPr, 'w:rtlGutter');
|
|
9349
|
+
if (rtlGutterEls.length > 0 && rtlGutterEls[0]) {
|
|
9350
|
+
const v = XMLParser.extractAttribute(rtlGutterEls[0], 'w:val');
|
|
9351
|
+
sectionProps.rtlGutter = parseOnOffAttribute(v, true);
|
|
8533
9352
|
}
|
|
8534
9353
|
|
|
8535
9354
|
// Parse document grid (w:docGrid)
|
|
@@ -8607,14 +9426,18 @@ export class DocumentParser {
|
|
|
8607
9426
|
if (Object.keys(props).length > 0) sectionProps.endnotePr = props;
|
|
8608
9427
|
}
|
|
8609
9428
|
|
|
8610
|
-
// Parse noEndnote
|
|
8611
|
-
|
|
8612
|
-
|
|
9429
|
+
// Parse noEndnote (w:noEndnote) — CT_OnOff per ECMA-376 §17.11.14
|
|
9430
|
+
const noEndEls = XMLParser.extractElements(sectPr, 'w:noEndnote');
|
|
9431
|
+
if (noEndEls.length > 0 && noEndEls[0]) {
|
|
9432
|
+
const v = XMLParser.extractAttribute(noEndEls[0], 'w:val');
|
|
9433
|
+
sectionProps.noEndnote = parseOnOffAttribute(v, true);
|
|
8613
9434
|
}
|
|
8614
9435
|
|
|
8615
|
-
// Parse form protection
|
|
8616
|
-
|
|
8617
|
-
|
|
9436
|
+
// Parse form protection (w:formProt) — CT_OnOff per ECMA-376 §17.6.8
|
|
9437
|
+
const formProtEls = XMLParser.extractElements(sectPr, 'w:formProt');
|
|
9438
|
+
if (formProtEls.length > 0 && formProtEls[0]) {
|
|
9439
|
+
const v = XMLParser.extractAttribute(formProtEls[0], 'w:val');
|
|
9440
|
+
sectionProps.formProt = parseOnOffAttribute(v, true);
|
|
8618
9441
|
}
|
|
8619
9442
|
|
|
8620
9443
|
// Parse printer settings (w:printerSettings r:id)
|
|
@@ -8737,34 +9560,45 @@ export class DocumentParser {
|
|
|
8737
9560
|
runFormatting = this.parseRunFormattingFromXml(rPrXml);
|
|
8738
9561
|
}
|
|
8739
9562
|
|
|
8740
|
-
// Parse metadata
|
|
8741
|
-
//
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
//
|
|
8745
|
-
const
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
// locked - Prevent modification
|
|
8752
|
-
const locked = styleXml.includes('<w:locked/>') || styleXml.includes('<w:locked ');
|
|
8753
|
-
|
|
8754
|
-
// personal - User-specific style
|
|
8755
|
-
const personal = styleXml.includes('<w:personal/>') || styleXml.includes('<w:personal ');
|
|
8756
|
-
|
|
8757
|
-
// personalCompose - Style for composing new messages
|
|
8758
|
-
const personalCompose =
|
|
8759
|
-
styleXml.includes('<w:personalCompose/>') || styleXml.includes('<w:personalCompose ');
|
|
8760
|
-
|
|
8761
|
-
// personalReply - Style for replying to messages
|
|
8762
|
-
const personalReply =
|
|
8763
|
-
styleXml.includes('<w:personalReply/>') || styleXml.includes('<w:personalReply ');
|
|
9563
|
+
// Parse metadata CT_OnOff flags per ECMA-376 §17.7.4 (OnOffType bindings).
|
|
9564
|
+
// Each flag honours `w:val` so an explicit `<w:qFormat w:val="0"/>` override
|
|
9565
|
+
// of a based-on style's qFormat=true round-trips as `false`. The old code
|
|
9566
|
+
// detected presence via `styleXml.includes('<w:qFormat/>')` which ignored
|
|
9567
|
+
// w:val entirely and flipped any explicit-false to true.
|
|
9568
|
+
const parseStyleOnOffFlag = (tagName: string): boolean | undefined => {
|
|
9569
|
+
const els = XMLParser.extractElements(styleXml, tagName);
|
|
9570
|
+
if (els.length === 0 || !els[0]) return undefined;
|
|
9571
|
+
const v = XMLParser.extractAttribute(els[0], 'w:val');
|
|
9572
|
+
return parseOnOffAttribute(v, true);
|
|
9573
|
+
};
|
|
8764
9574
|
|
|
8765
|
-
|
|
8766
|
-
const
|
|
8767
|
-
|
|
9575
|
+
const qFormat = parseStyleOnOffFlag('w:qFormat');
|
|
9576
|
+
const semiHidden = parseStyleOnOffFlag('w:semiHidden');
|
|
9577
|
+
const unhideWhenUsed = parseStyleOnOffFlag('w:unhideWhenUsed');
|
|
9578
|
+
const locked = parseStyleOnOffFlag('w:locked');
|
|
9579
|
+
const personal = parseStyleOnOffFlag('w:personal');
|
|
9580
|
+
const personalCompose = parseStyleOnOffFlag('w:personalCompose');
|
|
9581
|
+
const personalReply = parseStyleOnOffFlag('w:personalReply');
|
|
9582
|
+
const autoRedefine = parseStyleOnOffFlag('w:autoRedefine');
|
|
9583
|
+
// `<w:hidden>` (CT_Style §17.7.4, OnOffOnlyType) — completely hide the
|
|
9584
|
+
// style. Previously not modeled; now round-trips as `properties.hidden`.
|
|
9585
|
+
const hidden = parseStyleOnOffFlag('w:hidden');
|
|
9586
|
+
|
|
9587
|
+
// `<w:rsid w:val="HEX"/>` (CT_Style §17.7.4, CT_LongHexNumber §17.18.50) —
|
|
9588
|
+
// revision-save ID stamp identifying the session in which this style
|
|
9589
|
+
// definition was last edited. Schema position: between `personalReply`
|
|
9590
|
+
// and `pPr`. Previously dropped entirely on parse, now preserved on
|
|
9591
|
+
// StyleProperties so round-trips stay faithful.
|
|
9592
|
+
let styleRsid: string | undefined;
|
|
9593
|
+
if (styleXml.includes('<w:rsid')) {
|
|
9594
|
+
const rsidTag = XMLParser.extractSelfClosingTag(styleXml, 'w:rsid');
|
|
9595
|
+
if (rsidTag) {
|
|
9596
|
+
const v = XMLParser.extractAttribute(`<w:rsid${rsidTag}`, 'w:val');
|
|
9597
|
+
if (v && v.length > 0) {
|
|
9598
|
+
styleRsid = v;
|
|
9599
|
+
}
|
|
9600
|
+
}
|
|
9601
|
+
}
|
|
8768
9602
|
|
|
8769
9603
|
// uiPriority - Sort order
|
|
8770
9604
|
let uiPriority: number | undefined;
|
|
@@ -8803,7 +9637,7 @@ export class DocumentParser {
|
|
|
8803
9637
|
}
|
|
8804
9638
|
|
|
8805
9639
|
// Parse table style properties (Phase 5.1)
|
|
8806
|
-
let tableStyle: import('../formatting/Style').TableStyleProperties | undefined;
|
|
9640
|
+
let tableStyle: import('../formatting/Style.js').TableStyleProperties | undefined;
|
|
8807
9641
|
if (typeAttr === 'table') {
|
|
8808
9642
|
tableStyle = this.parseTableStyleProperties(styleXml);
|
|
8809
9643
|
}
|
|
@@ -8815,21 +9649,27 @@ export class DocumentParser {
|
|
|
8815
9649
|
type: typeAttr,
|
|
8816
9650
|
basedOn,
|
|
8817
9651
|
next,
|
|
8818
|
-
|
|
8819
|
-
|
|
9652
|
+
// w:default and w:customStyle are ST_OnOff per ECMA-376 §17.17.4
|
|
9653
|
+
isDefault: parseOnOffAttribute(defaultAttr),
|
|
9654
|
+
customStyle: parseOnOffAttribute(customStyleAttr),
|
|
8820
9655
|
paragraphFormatting,
|
|
8821
9656
|
numPr: styleNumPr,
|
|
8822
9657
|
runFormatting,
|
|
8823
9658
|
tableStyle,
|
|
8824
|
-
// Metadata
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
9659
|
+
// Metadata CT_OnOff flags (ECMA-376 §17.7.4). parseStyleOnOffFlag returns
|
|
9660
|
+
// undefined when the element is absent, or the actual boolean (true/false)
|
|
9661
|
+
// when present — preserve both "explicit false" (override) and "absent"
|
|
9662
|
+
// (inherit) faithfully through the Style properties record.
|
|
9663
|
+
qFormat,
|
|
9664
|
+
semiHidden,
|
|
9665
|
+
hidden,
|
|
9666
|
+
unhideWhenUsed,
|
|
9667
|
+
locked,
|
|
9668
|
+
personal,
|
|
9669
|
+
personalCompose,
|
|
9670
|
+
personalReply,
|
|
9671
|
+
rsid: styleRsid,
|
|
9672
|
+
autoRedefine,
|
|
8833
9673
|
uiPriority,
|
|
8834
9674
|
link,
|
|
8835
9675
|
aliases,
|
|
@@ -8846,6 +9686,86 @@ export class DocumentParser {
|
|
|
8846
9686
|
private parseParagraphFormattingFromXml(pPrXml: string): ParagraphFormatting {
|
|
8847
9687
|
const formatting: ParagraphFormatting = {};
|
|
8848
9688
|
|
|
9689
|
+
// Parse framePr (text frame properties) per ECMA-376 Part 1 §17.3.1.11 —
|
|
9690
|
+
// CT_FramePr is a CT_PPrBase child (#5, between pageBreakBefore and
|
|
9691
|
+
// widowControl). Each attribute is independently optional; numeric
|
|
9692
|
+
// attributes (w/h/x/y/hSpace/vSpace/lines) may legitimately be zero
|
|
9693
|
+
// so use explicit string presence rather than truthy checks.
|
|
9694
|
+
const framePrTag = XMLParser.extractSelfClosingTag(pPrXml, 'w:framePr');
|
|
9695
|
+
if (framePrTag) {
|
|
9696
|
+
const fpStr = `<w:framePr${framePrTag}`;
|
|
9697
|
+
const frameProps: NonNullable<ParagraphFormatting['framePr']> = {};
|
|
9698
|
+
const wAttr = XMLParser.extractAttribute(fpStr, 'w:w');
|
|
9699
|
+
if (wAttr !== undefined) frameProps.w = parseInt(wAttr, 10);
|
|
9700
|
+
const hAttr = XMLParser.extractAttribute(fpStr, 'w:h');
|
|
9701
|
+
if (hAttr !== undefined) frameProps.h = parseInt(hAttr, 10);
|
|
9702
|
+
const hRule = XMLParser.extractAttribute(fpStr, 'w:hRule');
|
|
9703
|
+
if (hRule === 'auto' || hRule === 'atLeast' || hRule === 'exact') {
|
|
9704
|
+
frameProps.hRule = hRule;
|
|
9705
|
+
}
|
|
9706
|
+
const xAttr = XMLParser.extractAttribute(fpStr, 'w:x');
|
|
9707
|
+
if (xAttr !== undefined) frameProps.x = parseInt(xAttr, 10);
|
|
9708
|
+
const yAttr = XMLParser.extractAttribute(fpStr, 'w:y');
|
|
9709
|
+
if (yAttr !== undefined) frameProps.y = parseInt(yAttr, 10);
|
|
9710
|
+
const xAlign = XMLParser.extractAttribute(fpStr, 'w:xAlign');
|
|
9711
|
+
if (
|
|
9712
|
+
xAlign === 'left' ||
|
|
9713
|
+
xAlign === 'center' ||
|
|
9714
|
+
xAlign === 'right' ||
|
|
9715
|
+
xAlign === 'inside' ||
|
|
9716
|
+
xAlign === 'outside'
|
|
9717
|
+
) {
|
|
9718
|
+
frameProps.xAlign = xAlign;
|
|
9719
|
+
}
|
|
9720
|
+
const yAlign = XMLParser.extractAttribute(fpStr, 'w:yAlign');
|
|
9721
|
+
if (
|
|
9722
|
+
yAlign === 'top' ||
|
|
9723
|
+
yAlign === 'center' ||
|
|
9724
|
+
yAlign === 'bottom' ||
|
|
9725
|
+
yAlign === 'inline' ||
|
|
9726
|
+
yAlign === 'inside' ||
|
|
9727
|
+
yAlign === 'outside'
|
|
9728
|
+
) {
|
|
9729
|
+
frameProps.yAlign = yAlign;
|
|
9730
|
+
}
|
|
9731
|
+
const hAnchor = XMLParser.extractAttribute(fpStr, 'w:hAnchor');
|
|
9732
|
+
if (hAnchor === 'page' || hAnchor === 'margin' || hAnchor === 'text') {
|
|
9733
|
+
frameProps.hAnchor = hAnchor;
|
|
9734
|
+
}
|
|
9735
|
+
const vAnchor = XMLParser.extractAttribute(fpStr, 'w:vAnchor');
|
|
9736
|
+
if (vAnchor === 'page' || vAnchor === 'margin' || vAnchor === 'text') {
|
|
9737
|
+
frameProps.vAnchor = vAnchor;
|
|
9738
|
+
}
|
|
9739
|
+
const hSpace = XMLParser.extractAttribute(fpStr, 'w:hSpace');
|
|
9740
|
+
if (hSpace !== undefined) frameProps.hSpace = parseInt(hSpace, 10);
|
|
9741
|
+
const vSpace = XMLParser.extractAttribute(fpStr, 'w:vSpace');
|
|
9742
|
+
if (vSpace !== undefined) frameProps.vSpace = parseInt(vSpace, 10);
|
|
9743
|
+
const wrap = XMLParser.extractAttribute(fpStr, 'w:wrap');
|
|
9744
|
+
if (
|
|
9745
|
+
wrap === 'around' ||
|
|
9746
|
+
wrap === 'auto' ||
|
|
9747
|
+
wrap === 'none' ||
|
|
9748
|
+
wrap === 'notBeside' ||
|
|
9749
|
+
wrap === 'through' ||
|
|
9750
|
+
wrap === 'tight'
|
|
9751
|
+
) {
|
|
9752
|
+
frameProps.wrap = wrap;
|
|
9753
|
+
}
|
|
9754
|
+
const dropCap = XMLParser.extractAttribute(fpStr, 'w:dropCap');
|
|
9755
|
+
if (dropCap === 'none' || dropCap === 'drop' || dropCap === 'margin') {
|
|
9756
|
+
frameProps.dropCap = dropCap;
|
|
9757
|
+
}
|
|
9758
|
+
const lines = XMLParser.extractAttribute(fpStr, 'w:lines');
|
|
9759
|
+
if (lines !== undefined) frameProps.lines = parseInt(lines, 10);
|
|
9760
|
+
const anchorLock = XMLParser.extractAttribute(fpStr, 'w:anchorLock');
|
|
9761
|
+
if (anchorLock !== undefined) {
|
|
9762
|
+
frameProps.anchorLock = parseOnOffAttribute(anchorLock, true);
|
|
9763
|
+
}
|
|
9764
|
+
if (Object.keys(frameProps).length > 0) {
|
|
9765
|
+
formatting.framePr = frameProps;
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
|
|
8849
9769
|
// Parse alignment (w:jc)
|
|
8850
9770
|
const jcElement = XMLParser.extractSelfClosingTag(pPrXml, 'w:jc');
|
|
8851
9771
|
if (jcElement) {
|
|
@@ -8885,15 +9805,17 @@ export class DocumentParser {
|
|
|
8885
9805
|
lineRule: validatedLineRule,
|
|
8886
9806
|
beforeLines: beforeLines ? parseInt(beforeLines, 10) : undefined,
|
|
8887
9807
|
afterLines: afterLines ? parseInt(afterLines, 10) : undefined,
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
afterAutospacing: afterAutosp ? afterAutosp === '1' || afterAutosp === 'true' : undefined,
|
|
9808
|
+
// ST_OnOff per ECMA-376 §17.17.4 — accept 1/0/true/false/on/off
|
|
9809
|
+
beforeAutospacing: beforeAutosp ? parseOnOffAttribute(beforeAutosp) : undefined,
|
|
9810
|
+
afterAutospacing: afterAutosp ? parseOnOffAttribute(afterAutosp) : undefined,
|
|
8892
9811
|
};
|
|
8893
9812
|
}
|
|
8894
9813
|
|
|
8895
9814
|
// Parse indentation (w:ind)
|
|
8896
|
-
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to
|
|
9815
|
+
// Per ECMA-376 §17.3.1.15: w:start/w:end are bidi-aware alternatives to
|
|
9816
|
+
// w:left/w:right. §17.3.1.12 also defines six CJK character-unit variants
|
|
9817
|
+
// (ST_DecimalNumber) — parse those alongside so styles authored in CJK
|
|
9818
|
+
// locales preserve their character-unit indent spec through round-trip.
|
|
8897
9819
|
const indElement = XMLParser.extractSelfClosingTag(pPrXml, 'w:ind');
|
|
8898
9820
|
if (indElement) {
|
|
8899
9821
|
const indTag = `<w:ind${indElement}`;
|
|
@@ -8903,33 +9825,137 @@ export class DocumentParser {
|
|
|
8903
9825
|
const right = XMLParser.extractAttribute(indTag, 'w:right');
|
|
8904
9826
|
const firstLine = XMLParser.extractAttribute(indTag, 'w:firstLine');
|
|
8905
9827
|
const hanging = XMLParser.extractAttribute(indTag, 'w:hanging');
|
|
9828
|
+
// CJK character-unit variants. startChars/endChars collapse to
|
|
9829
|
+
// leftChars/rightChars (same bidi-aware rule as the twips pair).
|
|
9830
|
+
const startChars = XMLParser.extractAttribute(indTag, 'w:startChars');
|
|
9831
|
+
const leftChars = XMLParser.extractAttribute(indTag, 'w:leftChars');
|
|
9832
|
+
const endChars = XMLParser.extractAttribute(indTag, 'w:endChars');
|
|
9833
|
+
const rightChars = XMLParser.extractAttribute(indTag, 'w:rightChars');
|
|
9834
|
+
const firstLineChars = XMLParser.extractAttribute(indTag, 'w:firstLineChars');
|
|
9835
|
+
const hangingChars = XMLParser.extractAttribute(indTag, 'w:hangingChars');
|
|
8906
9836
|
|
|
8907
9837
|
const leftVal = start || left;
|
|
8908
9838
|
const rightVal = end || right;
|
|
9839
|
+
const leftCharsVal = startChars || leftChars;
|
|
9840
|
+
const rightCharsVal = endChars || rightChars;
|
|
8909
9841
|
|
|
8910
9842
|
formatting.indentation = {
|
|
8911
9843
|
left: leftVal ? parseInt(leftVal, 10) : undefined,
|
|
8912
9844
|
right: rightVal ? parseInt(rightVal, 10) : undefined,
|
|
8913
9845
|
firstLine: firstLine ? parseInt(firstLine, 10) : undefined,
|
|
8914
9846
|
hanging: hanging ? parseInt(hanging, 10) : undefined,
|
|
9847
|
+
leftChars: leftCharsVal ? parseInt(leftCharsVal, 10) : undefined,
|
|
9848
|
+
rightChars: rightCharsVal ? parseInt(rightCharsVal, 10) : undefined,
|
|
9849
|
+
firstLineChars: firstLineChars ? parseInt(firstLineChars, 10) : undefined,
|
|
9850
|
+
hangingChars: hangingChars ? parseInt(hangingChars, 10) : undefined,
|
|
8915
9851
|
};
|
|
8916
9852
|
}
|
|
8917
9853
|
|
|
8918
|
-
// Parse boolean
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
9854
|
+
// Parse CT_OnOff boolean flags per ECMA-376 §17.17.4 / §17.3.1. The previous
|
|
9855
|
+
// substring-only detection (`pPrXml.includes('<w:keepNext/>') ||
|
|
9856
|
+
// pPrXml.includes('<w:keepNext ')`) hard-coded the flag to true whenever
|
|
9857
|
+
// the element appeared at all — silently flipping `<w:keepNext w:val="0"/>`
|
|
9858
|
+
// (explicit override) into an enabled flag. Read w:val when present and
|
|
9859
|
+
// honour every ST_OnOff literal (1/0/true/false/on/off).
|
|
9860
|
+
const parseStylePPrCtOnOff = (tagName: string): boolean | undefined => {
|
|
9861
|
+
// extractSelfClosingTag returns the ATTRIBUTE STRING (possibly empty)
|
|
9862
|
+
// when found, or `undefined` when absent. Earlier this helper checked
|
|
9863
|
+
// `=== null` by mistake — that let the "absent" case fall through and
|
|
9864
|
+
// construct a garbage tag that produced `true`, silently enabling the
|
|
9865
|
+
// flag on every style that didn't set it.
|
|
9866
|
+
const el = XMLParser.extractSelfClosingTag(pPrXml, tagName);
|
|
9867
|
+
if (el === undefined) return undefined;
|
|
9868
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
9869
|
+
if (val === undefined) return true;
|
|
9870
|
+
return parseOnOffAttribute(val, true);
|
|
9871
|
+
};
|
|
9872
|
+
|
|
9873
|
+
const keepNextVal = parseStylePPrCtOnOff('w:keepNext');
|
|
9874
|
+
if (keepNextVal !== undefined) formatting.keepNext = keepNextVal;
|
|
9875
|
+
|
|
9876
|
+
const keepLinesVal = parseStylePPrCtOnOff('w:keepLines');
|
|
9877
|
+
if (keepLinesVal !== undefined) formatting.keepLines = keepLinesVal;
|
|
9878
|
+
|
|
9879
|
+
const pageBreakBeforeVal = parseStylePPrCtOnOff('w:pageBreakBefore');
|
|
9880
|
+
if (pageBreakBeforeVal !== undefined) formatting.pageBreakBefore = pageBreakBeforeVal;
|
|
9881
|
+
|
|
9882
|
+
// Contextual spacing per ECMA-376 Part 1 §17.3.1.9
|
|
9883
|
+
// "Don't add space between paragraphs of the same style"
|
|
9884
|
+
const contextualSpacingVal = parseStylePPrCtOnOff('w:contextualSpacing');
|
|
9885
|
+
if (contextualSpacingVal !== undefined) formatting.contextualSpacing = contextualSpacingVal;
|
|
9886
|
+
|
|
9887
|
+
// Remaining CT_PPrBase CT_OnOff flags per ECMA-376 Part 1 §17.3.1.
|
|
9888
|
+
// The main paragraph parser handles all of these; the style-level parser
|
|
9889
|
+
// previously dropped them (substring matches existed only for the four
|
|
9890
|
+
// flags above). Any style using the explicit-false form to override a
|
|
9891
|
+
// based-on style's enabled flag was silently losing the override.
|
|
9892
|
+
const widowControlVal = parseStylePPrCtOnOff('w:widowControl');
|
|
9893
|
+
if (widowControlVal !== undefined) formatting.widowControl = widowControlVal;
|
|
9894
|
+
|
|
9895
|
+
const suppressLineNumbersVal = parseStylePPrCtOnOff('w:suppressLineNumbers');
|
|
9896
|
+
if (suppressLineNumbersVal !== undefined)
|
|
9897
|
+
formatting.suppressLineNumbers = suppressLineNumbersVal;
|
|
9898
|
+
|
|
9899
|
+
const bidiVal = parseStylePPrCtOnOff('w:bidi');
|
|
9900
|
+
if (bidiVal !== undefined) formatting.bidi = bidiVal;
|
|
9901
|
+
|
|
9902
|
+
const mirrorIndentsVal = parseStylePPrCtOnOff('w:mirrorIndents');
|
|
9903
|
+
if (mirrorIndentsVal !== undefined) formatting.mirrorIndents = mirrorIndentsVal;
|
|
9904
|
+
|
|
9905
|
+
const adjustRightIndVal = parseStylePPrCtOnOff('w:adjustRightInd');
|
|
9906
|
+
if (adjustRightIndVal !== undefined) formatting.adjustRightInd = adjustRightIndVal;
|
|
9907
|
+
|
|
9908
|
+
const suppressAutoHyphensVal = parseStylePPrCtOnOff('w:suppressAutoHyphens');
|
|
9909
|
+
if (suppressAutoHyphensVal !== undefined)
|
|
9910
|
+
formatting.suppressAutoHyphens = suppressAutoHyphensVal;
|
|
9911
|
+
|
|
9912
|
+
const kinsokuVal = parseStylePPrCtOnOff('w:kinsoku');
|
|
9913
|
+
if (kinsokuVal !== undefined) formatting.kinsoku = kinsokuVal;
|
|
9914
|
+
|
|
9915
|
+
const wordWrapVal = parseStylePPrCtOnOff('w:wordWrap');
|
|
9916
|
+
if (wordWrapVal !== undefined) formatting.wordWrap = wordWrapVal;
|
|
9917
|
+
|
|
9918
|
+
const overflowPunctVal = parseStylePPrCtOnOff('w:overflowPunct');
|
|
9919
|
+
if (overflowPunctVal !== undefined) formatting.overflowPunct = overflowPunctVal;
|
|
9920
|
+
|
|
9921
|
+
const topLinePunctVal = parseStylePPrCtOnOff('w:topLinePunct');
|
|
9922
|
+
if (topLinePunctVal !== undefined) formatting.topLinePunct = topLinePunctVal;
|
|
9923
|
+
|
|
9924
|
+
const autoSpaceDEVal = parseStylePPrCtOnOff('w:autoSpaceDE');
|
|
9925
|
+
if (autoSpaceDEVal !== undefined) formatting.autoSpaceDE = autoSpaceDEVal;
|
|
9926
|
+
|
|
9927
|
+
const autoSpaceDNVal = parseStylePPrCtOnOff('w:autoSpaceDN');
|
|
9928
|
+
if (autoSpaceDNVal !== undefined) formatting.autoSpaceDN = autoSpaceDNVal;
|
|
9929
|
+
|
|
9930
|
+
const suppressOverlapVal = parseStylePPrCtOnOff('w:suppressOverlap');
|
|
9931
|
+
if (suppressOverlapVal !== undefined) formatting.suppressOverlap = suppressOverlapVal;
|
|
9932
|
+
|
|
9933
|
+
// Parse `w:val`-attribute string-enum children per CT_PPrBase.
|
|
9934
|
+
// Position #28 textDirection (ST_TextDirection), #29 textAlignment
|
|
9935
|
+
// (ST_TextAlignment), #30 textboxTightWrap (ST_TextboxTightWrapType).
|
|
9936
|
+
// The main paragraph parser handles these; the style-level parser
|
|
9937
|
+
// previously dropped them because the substring scan was never
|
|
9938
|
+
// extended past the iteration-25 CT_OnOff helper.
|
|
9939
|
+
const parseStylePPrValAttr = (tagName: string): string | undefined => {
|
|
9940
|
+
const el = XMLParser.extractSelfClosingTag(pPrXml, tagName);
|
|
9941
|
+
if (el === undefined) return undefined;
|
|
9942
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
9943
|
+
return val === undefined ? undefined : String(val);
|
|
9944
|
+
};
|
|
9945
|
+
|
|
9946
|
+
const textDirectionVal = parseStylePPrValAttr('w:textDirection');
|
|
9947
|
+
if (textDirectionVal !== undefined) {
|
|
9948
|
+
formatting.textDirection = textDirectionVal as ParagraphFormatting['textDirection'];
|
|
8924
9949
|
}
|
|
8925
|
-
|
|
8926
|
-
|
|
9950
|
+
|
|
9951
|
+
const textAlignmentVal = parseStylePPrValAttr('w:textAlignment');
|
|
9952
|
+
if (textAlignmentVal !== undefined) {
|
|
9953
|
+
formatting.textAlignment = textAlignmentVal as ParagraphFormatting['textAlignment'];
|
|
8927
9954
|
}
|
|
8928
9955
|
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
formatting.contextualSpacing = true;
|
|
9956
|
+
const textboxTightWrapVal = parseStylePPrValAttr('w:textboxTightWrap');
|
|
9957
|
+
if (textboxTightWrapVal !== undefined) {
|
|
9958
|
+
formatting.textboxTightWrap = textboxTightWrapVal as ParagraphFormatting['textboxTightWrap'];
|
|
8933
9959
|
}
|
|
8934
9960
|
|
|
8935
9961
|
// Parse outline level (w:outlineLvl) - used for TOC generation
|
|
@@ -8945,7 +9971,29 @@ export class DocumentParser {
|
|
|
8945
9971
|
}
|
|
8946
9972
|
}
|
|
8947
9973
|
|
|
8948
|
-
// Parse
|
|
9974
|
+
// Parse divId (CT_PPrBase #32, §17.3.1.10) — numeric HTML div
|
|
9975
|
+
// association. Previously dropped on the style parser; the main
|
|
9976
|
+
// paragraph parser reads it at the pPrObj level but the style pPr
|
|
9977
|
+
// parser used string-based extraction and skipped both divId and
|
|
9978
|
+
// cnfStyle below.
|
|
9979
|
+
const divIdVal = parseStylePPrValAttr('w:divId');
|
|
9980
|
+
if (divIdVal !== undefined) {
|
|
9981
|
+
const parsedDivId = parseInt(divIdVal, 10);
|
|
9982
|
+
if (!isNaN(parsedDivId)) formatting.divId = parsedDivId;
|
|
9983
|
+
}
|
|
9984
|
+
|
|
9985
|
+
// Parse cnfStyle (CT_PPrBase #33, §17.3.1.8) — conditional formatting
|
|
9986
|
+
// bitmask string (12-char 0/1 sequence, e.g. "100000000100").
|
|
9987
|
+
const cnfStyleVal = parseStylePPrValAttr('w:cnfStyle');
|
|
9988
|
+
if (cnfStyleVal !== undefined) formatting.cnfStyle = cnfStyleVal;
|
|
9989
|
+
|
|
9990
|
+
// Parse paragraph borders (w:pBdr) per ECMA-376 Part 1 §17.3.1.24.
|
|
9991
|
+
// Covers the full CT_Border attribute set (§17.18.2): val, sz, space,
|
|
9992
|
+
// color, themeColor, themeTint, themeShade, shadow, frame. The style
|
|
9993
|
+
// *emitter* already round-trips all nine, so any style-pBdr authored
|
|
9994
|
+
// by Word with themed or shadow/frame attributes was silently flattened
|
|
9995
|
+
// here before this fix. Shadow/frame route through parseOnOffAttribute
|
|
9996
|
+
// so ST_OnOff literals ("on"/"off"/"1"/"0"/"true"/"false") resolve.
|
|
8949
9997
|
const pBdrXml = XMLParser.extractBetweenTags(pPrXml, '<w:pBdr>', '</w:pBdr>');
|
|
8950
9998
|
if (pBdrXml) {
|
|
8951
9999
|
const borders: any = {};
|
|
@@ -8959,11 +10007,21 @@ export class DocumentParser {
|
|
|
8959
10007
|
const size = XMLParser.extractAttribute(bTag, 'w:sz');
|
|
8960
10008
|
const space = XMLParser.extractAttribute(bTag, 'w:space');
|
|
8961
10009
|
const color = XMLParser.extractAttribute(bTag, 'w:color');
|
|
10010
|
+
const themeColor = XMLParser.extractAttribute(bTag, 'w:themeColor');
|
|
10011
|
+
const themeTint = XMLParser.extractAttribute(bTag, 'w:themeTint');
|
|
10012
|
+
const themeShade = XMLParser.extractAttribute(bTag, 'w:themeShade');
|
|
10013
|
+
const shadow = XMLParser.extractAttribute(bTag, 'w:shadow');
|
|
10014
|
+
const frame = XMLParser.extractAttribute(bTag, 'w:frame');
|
|
8962
10015
|
const border: any = {};
|
|
8963
10016
|
if (style) border.style = style;
|
|
8964
10017
|
if (size) border.size = parseInt(size, 10);
|
|
8965
10018
|
if (space) border.space = parseInt(space, 10);
|
|
8966
10019
|
if (color) border.color = color;
|
|
10020
|
+
if (themeColor) border.themeColor = themeColor;
|
|
10021
|
+
if (themeTint) border.themeTint = themeTint;
|
|
10022
|
+
if (themeShade) border.themeShade = themeShade;
|
|
10023
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
10024
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
8967
10025
|
if (Object.keys(border).length > 0) borders[type] = border;
|
|
8968
10026
|
}
|
|
8969
10027
|
}
|
|
@@ -9010,37 +10068,113 @@ export class DocumentParser {
|
|
|
9010
10068
|
private parseRunFormattingFromXml(rPrXml: string): RunFormatting {
|
|
9011
10069
|
const formatting: RunFormatting = {};
|
|
9012
10070
|
|
|
9013
|
-
//
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
10071
|
+
// CT_OnOff rPr children per ECMA-376 §17.3.2. Previously detected via
|
|
10072
|
+
// substring-include which hard-coded the flag to `true` whenever the
|
|
10073
|
+
// element appeared — silently flipping `<w:b w:val="0"/>` (explicit
|
|
10074
|
+
// override of a based-on style's bold) into an enabled flag, and
|
|
10075
|
+
// never setting the field to `false` for legitimate overrides.
|
|
10076
|
+
// Mirrors the pPr `parseStylePPrCtOnOff` helper introduced in
|
|
10077
|
+
// iteration 25 / 26.
|
|
10078
|
+
const parseStyleRPrCtOnOff = (tagName: string): boolean | undefined => {
|
|
10079
|
+
const el = XMLParser.extractSelfClosingTag(rPrXml, tagName);
|
|
10080
|
+
if (el === undefined) return undefined;
|
|
10081
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
10082
|
+
if (val === undefined) return true;
|
|
10083
|
+
return parseOnOffAttribute(val, true);
|
|
10084
|
+
};
|
|
10085
|
+
|
|
10086
|
+
const boldVal = parseStyleRPrCtOnOff('w:b');
|
|
10087
|
+
if (boldVal !== undefined) formatting.bold = boldVal;
|
|
10088
|
+
|
|
10089
|
+
const italicVal = parseStyleRPrCtOnOff('w:i');
|
|
10090
|
+
if (italicVal !== undefined) formatting.italic = italicVal;
|
|
10091
|
+
|
|
10092
|
+
const strikeVal = parseStyleRPrCtOnOff('w:strike');
|
|
10093
|
+
if (strikeVal !== undefined) formatting.strike = strikeVal;
|
|
10094
|
+
|
|
10095
|
+
const smallCapsVal = parseStyleRPrCtOnOff('w:smallCaps');
|
|
10096
|
+
if (smallCapsVal !== undefined) formatting.smallCaps = smallCapsVal;
|
|
10097
|
+
|
|
10098
|
+
const allCapsVal = parseStyleRPrCtOnOff('w:caps');
|
|
10099
|
+
if (allCapsVal !== undefined) formatting.allCaps = allCapsVal;
|
|
10100
|
+
|
|
10101
|
+
// Extended CT_OnOff run children per ECMA-376 §17.3.2. The style-level
|
|
10102
|
+
// rPr parser previously dropped all of these silently, so character
|
|
10103
|
+
// styles setting dstrike, outline, shadow, emboss, imprint, rtl,
|
|
10104
|
+
// vanish, noProof, snapToGrid, specVanish, webHidden, or complex-script
|
|
10105
|
+
// variants (bCs / iCs / cs) lost their overrides on programmatic save.
|
|
10106
|
+
const boldCsVal = parseStyleRPrCtOnOff('w:bCs');
|
|
10107
|
+
if (boldCsVal !== undefined) formatting.complexScriptBold = boldCsVal;
|
|
10108
|
+
|
|
10109
|
+
const italicCsVal = parseStyleRPrCtOnOff('w:iCs');
|
|
10110
|
+
if (italicCsVal !== undefined) formatting.complexScriptItalic = italicCsVal;
|
|
10111
|
+
|
|
10112
|
+
const csVal = parseStyleRPrCtOnOff('w:cs');
|
|
10113
|
+
if (csVal !== undefined) formatting.complexScript = csVal;
|
|
10114
|
+
|
|
10115
|
+
const dstrikeVal = parseStyleRPrCtOnOff('w:dstrike');
|
|
10116
|
+
if (dstrikeVal !== undefined) formatting.dstrike = dstrikeVal;
|
|
10117
|
+
|
|
10118
|
+
const outlineVal = parseStyleRPrCtOnOff('w:outline');
|
|
10119
|
+
if (outlineVal !== undefined) formatting.outline = outlineVal;
|
|
10120
|
+
|
|
10121
|
+
const shadowVal = parseStyleRPrCtOnOff('w:shadow');
|
|
10122
|
+
if (shadowVal !== undefined) formatting.shadow = shadowVal;
|
|
10123
|
+
|
|
10124
|
+
const embossVal = parseStyleRPrCtOnOff('w:emboss');
|
|
10125
|
+
if (embossVal !== undefined) formatting.emboss = embossVal;
|
|
10126
|
+
|
|
10127
|
+
const imprintVal = parseStyleRPrCtOnOff('w:imprint');
|
|
10128
|
+
if (imprintVal !== undefined) formatting.imprint = imprintVal;
|
|
9029
10129
|
|
|
9030
|
-
|
|
10130
|
+
const rtlVal = parseStyleRPrCtOnOff('w:rtl');
|
|
10131
|
+
if (rtlVal !== undefined) formatting.rtl = rtlVal;
|
|
10132
|
+
|
|
10133
|
+
const vanishVal = parseStyleRPrCtOnOff('w:vanish');
|
|
10134
|
+
if (vanishVal !== undefined) formatting.vanish = vanishVal;
|
|
10135
|
+
|
|
10136
|
+
const noProofVal = parseStyleRPrCtOnOff('w:noProof');
|
|
10137
|
+
if (noProofVal !== undefined) formatting.noProof = noProofVal;
|
|
10138
|
+
|
|
10139
|
+
const snapToGridVal = parseStyleRPrCtOnOff('w:snapToGrid');
|
|
10140
|
+
if (snapToGridVal !== undefined) formatting.snapToGrid = snapToGridVal;
|
|
10141
|
+
|
|
10142
|
+
const specVanishVal = parseStyleRPrCtOnOff('w:specVanish');
|
|
10143
|
+
if (specVanishVal !== undefined) formatting.specVanish = specVanishVal;
|
|
10144
|
+
|
|
10145
|
+
const webHiddenVal = parseStyleRPrCtOnOff('w:webHidden');
|
|
10146
|
+
if (webHiddenVal !== undefined) formatting.webHidden = webHiddenVal;
|
|
10147
|
+
|
|
10148
|
+
// Parse underline — all attributes per ECMA-376 §17.3.2.40.
|
|
10149
|
+
// Whitelist covers the full ST_Underline enumeration (18 values);
|
|
10150
|
+
// unknown / out-of-spec values fall through to `underline = true`
|
|
10151
|
+
// (underline enabled with default style) to match the main parser.
|
|
9031
10152
|
const uElement = XMLParser.extractSelfClosingTag(rPrXml, 'w:u');
|
|
9032
10153
|
if (uElement) {
|
|
9033
10154
|
const uTag = `<w:u${uElement}`;
|
|
9034
10155
|
const uVal = XMLParser.extractAttribute(uTag, 'w:val');
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
10156
|
+
const ST_UNDERLINE = new Set<string>([
|
|
10157
|
+
'single',
|
|
10158
|
+
'words',
|
|
10159
|
+
'double',
|
|
10160
|
+
'thick',
|
|
10161
|
+
'dotted',
|
|
10162
|
+
'dottedHeavy',
|
|
10163
|
+
'dash',
|
|
10164
|
+
'dashedHeavy',
|
|
10165
|
+
'dashLong',
|
|
10166
|
+
'dashLongHeavy',
|
|
10167
|
+
'dotDash',
|
|
10168
|
+
'dashDotHeavy',
|
|
10169
|
+
'dotDotDash',
|
|
10170
|
+
'dashDotDotHeavy',
|
|
10171
|
+
'wave',
|
|
10172
|
+
'wavyHeavy',
|
|
10173
|
+
'wavyDouble',
|
|
10174
|
+
'none',
|
|
10175
|
+
]);
|
|
10176
|
+
if (uVal !== undefined && ST_UNDERLINE.has(String(uVal))) {
|
|
10177
|
+
formatting.underline = String(uVal) as RunFormatting['underline'];
|
|
9044
10178
|
} else {
|
|
9045
10179
|
formatting.underline = true;
|
|
9046
10180
|
}
|
|
@@ -9048,7 +10182,8 @@ export class DocumentParser {
|
|
|
9048
10182
|
if (uColor) formatting.underlineColor = uColor;
|
|
9049
10183
|
const uThemeColor = XMLParser.extractAttribute(uTag, 'w:themeColor');
|
|
9050
10184
|
if (uThemeColor) {
|
|
9051
|
-
formatting.underlineThemeColor =
|
|
10185
|
+
formatting.underlineThemeColor =
|
|
10186
|
+
uThemeColor as import('../elements/Run.js').ThemeColorValue;
|
|
9052
10187
|
}
|
|
9053
10188
|
const uThemeTint = XMLParser.extractAttribute(uTag, 'w:themeTint');
|
|
9054
10189
|
if (uThemeTint) formatting.underlineThemeTint = parseInt(uThemeTint, 16);
|
|
@@ -9115,17 +10250,25 @@ export class DocumentParser {
|
|
|
9115
10250
|
}
|
|
9116
10251
|
}
|
|
9117
10252
|
|
|
9118
|
-
// Parse color (w:color) — all attributes per ECMA-376 §17.3.2.6
|
|
10253
|
+
// Parse color (w:color) — all attributes per ECMA-376 §17.3.2.6 / ST_HexColor
|
|
10254
|
+
// per §17.18.38. `w:val="auto"` is a valid ST_HexColorAuto sentinel that
|
|
10255
|
+
// tells Word to use the automatic/window text color; the previous parser
|
|
10256
|
+
// dropped it (only storing non-auto hex values), so a style-level rPr with
|
|
10257
|
+
// `<w:color w:val="auto"/>` silently lost that marker on round-trip and
|
|
10258
|
+
// the emitter defaulted to `"000000"` — changing the rendering of any
|
|
10259
|
+
// style that relied on the auto fallback. Preserve the literal "auto" so
|
|
10260
|
+
// it survives through emission. (Matches the object-format parser path
|
|
10261
|
+
// for direct-run rPr at parseRunFromObject line ~5210.)
|
|
9119
10262
|
const colorElement = XMLParser.extractSelfClosingTag(rPrXml, 'w:color');
|
|
9120
10263
|
if (colorElement) {
|
|
9121
10264
|
const colorTag = `<w:color${colorElement}`;
|
|
9122
10265
|
const val = XMLParser.extractAttribute(colorTag, 'w:val');
|
|
9123
|
-
if (val
|
|
10266
|
+
if (val) {
|
|
9124
10267
|
formatting.color = val;
|
|
9125
10268
|
}
|
|
9126
10269
|
const themeColor = XMLParser.extractAttribute(colorTag, 'w:themeColor');
|
|
9127
10270
|
if (themeColor) {
|
|
9128
|
-
formatting.themeColor = themeColor as import('../elements/Run').ThemeColorValue;
|
|
10271
|
+
formatting.themeColor = themeColor as import('../elements/Run.js').ThemeColorValue;
|
|
9129
10272
|
}
|
|
9130
10273
|
const themeTint = XMLParser.extractAttribute(colorTag, 'w:themeTint');
|
|
9131
10274
|
if (themeTint) {
|
|
@@ -9190,6 +10333,186 @@ export class DocumentParser {
|
|
|
9190
10333
|
formatting.shading = shading;
|
|
9191
10334
|
}
|
|
9192
10335
|
|
|
10336
|
+
// Character spacing (w:spacing §17.3.2.35, ST_SignedTwipsMeasure) —
|
|
10337
|
+
// previously dropped on the style parser; 0 and negative values are
|
|
10338
|
+
// valid per spec.
|
|
10339
|
+
const spacingEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:spacing');
|
|
10340
|
+
if (spacingEl !== undefined) {
|
|
10341
|
+
const val = XMLParser.extractAttribute(`<w:spacing${spacingEl}`, 'w:val');
|
|
10342
|
+
if (val !== undefined) {
|
|
10343
|
+
const n = parseInt(String(val), 10);
|
|
10344
|
+
if (!isNaN(n)) formatting.characterSpacing = n;
|
|
10345
|
+
}
|
|
10346
|
+
}
|
|
10347
|
+
|
|
10348
|
+
// Vertical position (w:position §17.3.2.31, ST_SignedHpsMeasure).
|
|
10349
|
+
// 0 = baseline (explicit reset); negative = lowered; positive = raised.
|
|
10350
|
+
const positionEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:position');
|
|
10351
|
+
if (positionEl !== undefined) {
|
|
10352
|
+
const val = XMLParser.extractAttribute(`<w:position${positionEl}`, 'w:val');
|
|
10353
|
+
if (val !== undefined) {
|
|
10354
|
+
const n = parseInt(String(val), 10);
|
|
10355
|
+
if (!isNaN(n)) formatting.position = n;
|
|
10356
|
+
}
|
|
10357
|
+
}
|
|
10358
|
+
|
|
10359
|
+
// Kerning threshold (w:kern §17.3.2.20, ST_HpsMeasure). 0 = kern at
|
|
10360
|
+
// every size (no minimum font-size threshold).
|
|
10361
|
+
const kernEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:kern');
|
|
10362
|
+
if (kernEl !== undefined) {
|
|
10363
|
+
const val = XMLParser.extractAttribute(`<w:kern${kernEl}`, 'w:val');
|
|
10364
|
+
if (val !== undefined) {
|
|
10365
|
+
const n = parseInt(String(val), 10);
|
|
10366
|
+
if (!isNaN(n)) formatting.kerning = n;
|
|
10367
|
+
}
|
|
10368
|
+
}
|
|
10369
|
+
|
|
10370
|
+
// Language (w:lang §17.3.2.20, CT_Language). Single val → plain string;
|
|
10371
|
+
// multi-script (eastAsia and/or bidi present) → LanguageConfig object.
|
|
10372
|
+
const langEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:lang');
|
|
10373
|
+
if (langEl !== undefined) {
|
|
10374
|
+
const langTag = `<w:lang${langEl}`;
|
|
10375
|
+
const val = XMLParser.extractAttribute(langTag, 'w:val');
|
|
10376
|
+
const eastAsia = XMLParser.extractAttribute(langTag, 'w:eastAsia');
|
|
10377
|
+
const bidi = XMLParser.extractAttribute(langTag, 'w:bidi');
|
|
10378
|
+
if (eastAsia || bidi) {
|
|
10379
|
+
formatting.language = {
|
|
10380
|
+
val: val ? String(val) : undefined,
|
|
10381
|
+
eastAsia: eastAsia ? String(eastAsia) : undefined,
|
|
10382
|
+
bidi: bidi ? String(bidi) : undefined,
|
|
10383
|
+
};
|
|
10384
|
+
} else if (val) {
|
|
10385
|
+
formatting.language = String(val);
|
|
10386
|
+
}
|
|
10387
|
+
}
|
|
10388
|
+
|
|
10389
|
+
// Horizontal scaling (w:w §17.3.2.43, ST_TextScale — percentage,
|
|
10390
|
+
// min 1 per spec, so 0 is not valid and we keep a truthy check).
|
|
10391
|
+
const scaleEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:w');
|
|
10392
|
+
if (scaleEl !== undefined) {
|
|
10393
|
+
const val = XMLParser.extractAttribute(`<w:w${scaleEl}`, 'w:val');
|
|
10394
|
+
if (val) {
|
|
10395
|
+
const n = parseInt(String(val), 10);
|
|
10396
|
+
if (!isNaN(n)) formatting.scaling = n;
|
|
10397
|
+
}
|
|
10398
|
+
}
|
|
10399
|
+
|
|
10400
|
+
// Emphasis mark (w:em §17.3.2.13, ST_Em — "dot"/"comma"/"circle"/
|
|
10401
|
+
// "underDot"/"none"). Commonly paired with East Asian typography.
|
|
10402
|
+
const emEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:em');
|
|
10403
|
+
if (emEl !== undefined) {
|
|
10404
|
+
const val = XMLParser.extractAttribute(`<w:em${emEl}`, 'w:val');
|
|
10405
|
+
if (val) {
|
|
10406
|
+
formatting.emphasis = String(val) as RunFormatting['emphasis'];
|
|
10407
|
+
}
|
|
10408
|
+
}
|
|
10409
|
+
|
|
10410
|
+
// Animated text effect (w:effect §17.3.2.12, ST_TextEffect —
|
|
10411
|
+
// "blinkBackground"/"lights"/"antsBlack"/"antsRed"/"shimmer"/"sparkle"/
|
|
10412
|
+
// "none"). Legacy feature but still valid per schema.
|
|
10413
|
+
const effectEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:effect');
|
|
10414
|
+
if (effectEl !== undefined) {
|
|
10415
|
+
const val = XMLParser.extractAttribute(`<w:effect${effectEl}`, 'w:val');
|
|
10416
|
+
if (val) {
|
|
10417
|
+
formatting.effect = String(val) as RunFormatting['effect'];
|
|
10418
|
+
}
|
|
10419
|
+
}
|
|
10420
|
+
|
|
10421
|
+
// Text border (w:bdr §17.3.2.5) — character/run border. Full CT_Border
|
|
10422
|
+
// attribute set (§17.18.2): val / sz / space / color / themeColor /
|
|
10423
|
+
// themeTint / themeShade / shadow / frame.
|
|
10424
|
+
const bdrEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:bdr');
|
|
10425
|
+
if (bdrEl !== undefined) {
|
|
10426
|
+
const bdrTag = `<w:bdr${bdrEl}`;
|
|
10427
|
+
const border: {
|
|
10428
|
+
style?: string;
|
|
10429
|
+
size?: number;
|
|
10430
|
+
color?: string;
|
|
10431
|
+
space?: number;
|
|
10432
|
+
themeColor?: string;
|
|
10433
|
+
themeTint?: string;
|
|
10434
|
+
themeShade?: string;
|
|
10435
|
+
shadow?: boolean;
|
|
10436
|
+
frame?: boolean;
|
|
10437
|
+
} = {};
|
|
10438
|
+
const val = XMLParser.extractAttribute(bdrTag, 'w:val');
|
|
10439
|
+
if (val) border.style = String(val);
|
|
10440
|
+
const sz = XMLParser.extractAttribute(bdrTag, 'w:sz');
|
|
10441
|
+
if (sz !== undefined) {
|
|
10442
|
+
const n = parseInt(String(sz), 10);
|
|
10443
|
+
if (!isNaN(n)) border.size = n;
|
|
10444
|
+
}
|
|
10445
|
+
const color = XMLParser.extractAttribute(bdrTag, 'w:color');
|
|
10446
|
+
if (color) border.color = String(color);
|
|
10447
|
+
const space = XMLParser.extractAttribute(bdrTag, 'w:space');
|
|
10448
|
+
if (space !== undefined) {
|
|
10449
|
+
const n = parseInt(String(space), 10);
|
|
10450
|
+
if (!isNaN(n)) border.space = n;
|
|
10451
|
+
}
|
|
10452
|
+
const themeColor = XMLParser.extractAttribute(bdrTag, 'w:themeColor');
|
|
10453
|
+
if (themeColor) border.themeColor = String(themeColor);
|
|
10454
|
+
const themeTint = XMLParser.extractAttribute(bdrTag, 'w:themeTint');
|
|
10455
|
+
if (themeTint) border.themeTint = String(themeTint);
|
|
10456
|
+
const themeShade = XMLParser.extractAttribute(bdrTag, 'w:themeShade');
|
|
10457
|
+
if (themeShade) border.themeShade = String(themeShade);
|
|
10458
|
+
const shadow = XMLParser.extractAttribute(bdrTag, 'w:shadow');
|
|
10459
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
10460
|
+
const frame = XMLParser.extractAttribute(bdrTag, 'w:frame');
|
|
10461
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
10462
|
+
if (Object.keys(border).length > 0) {
|
|
10463
|
+
formatting.border = border as RunFormatting['border'];
|
|
10464
|
+
}
|
|
10465
|
+
}
|
|
10466
|
+
|
|
10467
|
+
// Manual run width (w:fitText §17.3.2.15). Value is twips; 0 is
|
|
10468
|
+
// technically representable as "explicit zero" — use `!== undefined`.
|
|
10469
|
+
const fitTextEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:fitText');
|
|
10470
|
+
if (fitTextEl !== undefined) {
|
|
10471
|
+
const val = XMLParser.extractAttribute(`<w:fitText${fitTextEl}`, 'w:val');
|
|
10472
|
+
if (val !== undefined) {
|
|
10473
|
+
const n = parseInt(String(val), 10);
|
|
10474
|
+
if (!isNaN(n)) formatting.fitText = n;
|
|
10475
|
+
}
|
|
10476
|
+
}
|
|
10477
|
+
|
|
10478
|
+
// East Asian layout (w:eastAsianLayout §17.3.2.10) — combined
|
|
10479
|
+
// characters / vertical text / compression attributes.
|
|
10480
|
+
const ealEl = XMLParser.extractSelfClosingTag(rPrXml, 'w:eastAsianLayout');
|
|
10481
|
+
if (ealEl !== undefined) {
|
|
10482
|
+
const ealTag = `<w:eastAsianLayout${ealEl}`;
|
|
10483
|
+
const layout: Partial<{
|
|
10484
|
+
id: number;
|
|
10485
|
+
vert: boolean;
|
|
10486
|
+
vertCompress: boolean;
|
|
10487
|
+
combine: boolean;
|
|
10488
|
+
combineBrackets: 'none' | 'round' | 'square' | 'angle' | 'curly';
|
|
10489
|
+
}> = {};
|
|
10490
|
+
const id = XMLParser.extractAttribute(ealTag, 'w:id');
|
|
10491
|
+
if (id !== undefined) {
|
|
10492
|
+
const n = Number(id);
|
|
10493
|
+
if (!isNaN(n)) layout.id = n;
|
|
10494
|
+
}
|
|
10495
|
+
const vert = XMLParser.extractAttribute(ealTag, 'w:vert');
|
|
10496
|
+
if (vert !== undefined && parseOnOffAttribute(vert, true)) layout.vert = true;
|
|
10497
|
+
const vertCompress = XMLParser.extractAttribute(ealTag, 'w:vertCompress');
|
|
10498
|
+
if (vertCompress !== undefined && parseOnOffAttribute(vertCompress, true))
|
|
10499
|
+
layout.vertCompress = true;
|
|
10500
|
+
const combine = XMLParser.extractAttribute(ealTag, 'w:combine');
|
|
10501
|
+
if (combine !== undefined && parseOnOffAttribute(combine, true)) layout.combine = true;
|
|
10502
|
+
const combineBrackets = XMLParser.extractAttribute(ealTag, 'w:combineBrackets');
|
|
10503
|
+
if (combineBrackets) {
|
|
10504
|
+
layout.combineBrackets = String(combineBrackets) as
|
|
10505
|
+
| 'none'
|
|
10506
|
+
| 'round'
|
|
10507
|
+
| 'square'
|
|
10508
|
+
| 'angle'
|
|
10509
|
+
| 'curly';
|
|
10510
|
+
}
|
|
10511
|
+
if (Object.keys(layout).length > 0) {
|
|
10512
|
+
formatting.eastAsianLayout = layout as RunFormatting['eastAsianLayout'];
|
|
10513
|
+
}
|
|
10514
|
+
}
|
|
10515
|
+
|
|
9193
10516
|
return formatting;
|
|
9194
10517
|
}
|
|
9195
10518
|
|
|
@@ -9200,8 +10523,8 @@ export class DocumentParser {
|
|
|
9200
10523
|
*/
|
|
9201
10524
|
private parseTableStyleProperties(
|
|
9202
10525
|
styleXml: string
|
|
9203
|
-
): import('../formatting/Style').TableStyleProperties {
|
|
9204
|
-
const tableStyle: import('../formatting/Style').TableStyleProperties = {};
|
|
10526
|
+
): import('../formatting/Style.js').TableStyleProperties {
|
|
10527
|
+
const tableStyle: import('../formatting/Style.js').TableStyleProperties = {};
|
|
9205
10528
|
|
|
9206
10529
|
// Parse tblPr (table properties)
|
|
9207
10530
|
const tblPrXml = XMLParser.extractBetweenTags(styleXml, '<w:tblPr>', '</w:tblPr>');
|
|
@@ -9254,8 +10577,8 @@ export class DocumentParser {
|
|
|
9254
10577
|
*/
|
|
9255
10578
|
private parseTableFormattingFromXml(
|
|
9256
10579
|
tblPrXml: string
|
|
9257
|
-
): import('../formatting/Style').TableStyleFormatting {
|
|
9258
|
-
const formatting: import('../formatting/Style').TableStyleFormatting = {};
|
|
10580
|
+
): import('../formatting/Style.js').TableStyleFormatting {
|
|
10581
|
+
const formatting: import('../formatting/Style.js').TableStyleFormatting = {};
|
|
9259
10582
|
|
|
9260
10583
|
// Parse indent (w:tblInd) — preserve w:type per ECMA-376 ST_TblWidth
|
|
9261
10584
|
if (tblPrXml.includes('<w:tblInd')) {
|
|
@@ -9268,18 +10591,27 @@ export class DocumentParser {
|
|
|
9268
10591
|
}
|
|
9269
10592
|
const type = XMLParser.extractAttribute(tblIndTag, 'w:type');
|
|
9270
10593
|
if (type) {
|
|
9271
|
-
formatting.indentType = type as import('../elements/Table').TableWidthType;
|
|
10594
|
+
formatting.indentType = type as import('../elements/Table.js').TableWidthType;
|
|
9272
10595
|
}
|
|
9273
10596
|
}
|
|
9274
10597
|
}
|
|
9275
10598
|
|
|
9276
|
-
// Parse alignment
|
|
10599
|
+
// Parse alignment — ST_JcTable has 5 values (start, end, center, left,
|
|
10600
|
+
// right) per ECMA-376 §17.18.45. The whitelist previously only accepted
|
|
10601
|
+
// the three legacy LTR-centric values, silently dropping `start` / `end`
|
|
10602
|
+
// (the bidi-aware defaults a modern authoring tool emits).
|
|
9277
10603
|
if (tblPrXml.includes('<w:jc')) {
|
|
9278
10604
|
const tag = XMLParser.extractSelfClosingTag(tblPrXml, 'w:jc');
|
|
9279
10605
|
if (tag) {
|
|
9280
10606
|
const val = XMLParser.extractAttribute(`<w:jc${tag}`, 'w:val');
|
|
9281
|
-
if (
|
|
9282
|
-
|
|
10607
|
+
if (
|
|
10608
|
+
val === 'left' ||
|
|
10609
|
+
val === 'center' ||
|
|
10610
|
+
val === 'right' ||
|
|
10611
|
+
val === 'start' ||
|
|
10612
|
+
val === 'end'
|
|
10613
|
+
) {
|
|
10614
|
+
formatting.alignment = val as import('../formatting/Style.js').TableAlignment;
|
|
9283
10615
|
}
|
|
9284
10616
|
}
|
|
9285
10617
|
}
|
|
@@ -9320,8 +10652,8 @@ export class DocumentParser {
|
|
|
9320
10652
|
*/
|
|
9321
10653
|
private parseTableCellFormattingFromXml(
|
|
9322
10654
|
tcPrXml: string
|
|
9323
|
-
): import('../formatting/Style').TableCellStyleFormatting {
|
|
9324
|
-
const formatting: import('../formatting/Style').TableCellStyleFormatting = {};
|
|
10655
|
+
): import('../formatting/Style.js').TableCellStyleFormatting {
|
|
10656
|
+
const formatting: import('../formatting/Style.js').TableCellStyleFormatting = {};
|
|
9325
10657
|
|
|
9326
10658
|
// Parse borders
|
|
9327
10659
|
const bordersXml = XMLParser.extractBetweenTags(tcPrXml, '<w:tcBorders>', '</w:tcBorders>');
|
|
@@ -9329,7 +10661,7 @@ export class DocumentParser {
|
|
|
9329
10661
|
formatting.borders = this.parseBordersFromXml(
|
|
9330
10662
|
bordersXml,
|
|
9331
10663
|
true
|
|
9332
|
-
) as import('../formatting/Style').CellBorders;
|
|
10664
|
+
) as import('../formatting/Style.js').CellBorders;
|
|
9333
10665
|
}
|
|
9334
10666
|
|
|
9335
10667
|
// Parse shading
|
|
@@ -9343,12 +10675,15 @@ export class DocumentParser {
|
|
|
9343
10675
|
formatting.margins = this.parseCellMarginsFromXml(marginXml);
|
|
9344
10676
|
}
|
|
9345
10677
|
|
|
9346
|
-
// Parse vertical alignment
|
|
10678
|
+
// Parse vertical alignment — ST_VerticalJc has four values
|
|
10679
|
+
// (top / center / both / bottom) per ECMA-376 §17.18.101. Previously
|
|
10680
|
+
// the whitelist only accepted the first three, silently dropping
|
|
10681
|
+
// `<w:vAlign w:val="both"/>` on cell styles.
|
|
9347
10682
|
if (tcPrXml.includes('<w:vAlign')) {
|
|
9348
10683
|
const tag = XMLParser.extractSelfClosingTag(tcPrXml, 'w:vAlign');
|
|
9349
10684
|
if (tag) {
|
|
9350
10685
|
const val = XMLParser.extractAttribute(`<w:vAlign${tag}`, 'w:val');
|
|
9351
|
-
if (val === 'top' || val === 'center' || val === 'bottom') {
|
|
10686
|
+
if (val === 'top' || val === 'center' || val === 'both' || val === 'bottom') {
|
|
9352
10687
|
formatting.verticalAlignment = val;
|
|
9353
10688
|
}
|
|
9354
10689
|
}
|
|
@@ -9362,8 +10697,8 @@ export class DocumentParser {
|
|
|
9362
10697
|
*/
|
|
9363
10698
|
private parseTableRowFormattingFromXml(
|
|
9364
10699
|
trPrXml: string
|
|
9365
|
-
): import('../formatting/Style').TableRowStyleFormatting {
|
|
9366
|
-
const formatting: import('../formatting/Style').TableRowStyleFormatting = {};
|
|
10700
|
+
): import('../formatting/Style.js').TableRowStyleFormatting {
|
|
10701
|
+
const formatting: import('../formatting/Style.js').TableRowStyleFormatting = {};
|
|
9367
10702
|
|
|
9368
10703
|
// Parse height
|
|
9369
10704
|
if (trPrXml.includes('<w:trHeight')) {
|
|
@@ -9380,15 +10715,25 @@ export class DocumentParser {
|
|
|
9380
10715
|
}
|
|
9381
10716
|
}
|
|
9382
10717
|
|
|
9383
|
-
// Parse cantSplit
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
10718
|
+
// Parse cantSplit / tblHeader — both OnOffOnlyType (§17.4.6, §17.4.50).
|
|
10719
|
+
// Previous substring-include detection hard-coded the flag to `true`
|
|
10720
|
+
// whenever the element appeared, silently flipping an explicit-off
|
|
10721
|
+
// override (e.g., a tblStylePr conditional un-splitting a header row)
|
|
10722
|
+
// into an enabled flag. Reuse parseOnOffAttribute so bare, "on", and
|
|
10723
|
+
// "off" all map correctly, and so absent stays undefined.
|
|
10724
|
+
const parseTrPrOnOffOnly = (tagName: string): boolean | undefined => {
|
|
10725
|
+
const el = XMLParser.extractSelfClosingTag(trPrXml, tagName);
|
|
10726
|
+
if (el === undefined) return undefined;
|
|
10727
|
+
const val = XMLParser.extractAttribute(`<${tagName}${el}`, 'w:val');
|
|
10728
|
+
if (val === undefined) return true;
|
|
10729
|
+
return parseOnOffAttribute(val, true);
|
|
10730
|
+
};
|
|
9387
10731
|
|
|
9388
|
-
|
|
9389
|
-
if (
|
|
9390
|
-
|
|
9391
|
-
|
|
10732
|
+
const cantSplitVal = parseTrPrOnOffOnly('w:cantSplit');
|
|
10733
|
+
if (cantSplitVal !== undefined) formatting.cantSplit = cantSplitVal;
|
|
10734
|
+
|
|
10735
|
+
const tblHeaderVal = parseTrPrOnOffOnly('w:tblHeader');
|
|
10736
|
+
if (tblHeaderVal !== undefined) formatting.isHeader = tblHeaderVal;
|
|
9392
10737
|
|
|
9393
10738
|
return formatting;
|
|
9394
10739
|
}
|
|
@@ -9398,8 +10743,8 @@ export class DocumentParser {
|
|
|
9398
10743
|
*/
|
|
9399
10744
|
private parseConditionalFormattingFromXml(
|
|
9400
10745
|
styleXml: string
|
|
9401
|
-
): import('../formatting/Style').ConditionalTableFormatting[] | undefined {
|
|
9402
|
-
const conditionalFormatting: import('../formatting/Style').ConditionalTableFormatting[] = [];
|
|
10746
|
+
): import('../formatting/Style.js').ConditionalTableFormatting[] | undefined {
|
|
10747
|
+
const conditionalFormatting: import('../formatting/Style.js').ConditionalTableFormatting[] = [];
|
|
9403
10748
|
|
|
9404
10749
|
// Find all tblStylePr elements
|
|
9405
10750
|
let searchFrom = 0;
|
|
@@ -9415,8 +10760,8 @@ export class DocumentParser {
|
|
|
9415
10760
|
// Extract type attribute
|
|
9416
10761
|
const typeAttr = XMLParser.extractAttribute(tblStylePrXml, 'w:type');
|
|
9417
10762
|
if (typeAttr) {
|
|
9418
|
-
const conditional: import('../formatting/Style').ConditionalTableFormatting = {
|
|
9419
|
-
type: typeAttr as import('../formatting/Style').ConditionalFormattingType,
|
|
10763
|
+
const conditional: import('../formatting/Style.js').ConditionalTableFormatting = {
|
|
10764
|
+
type: typeAttr as import('../formatting/Style.js').ConditionalFormattingType,
|
|
9420
10765
|
};
|
|
9421
10766
|
|
|
9422
10767
|
// Parse pPr
|
|
@@ -9466,29 +10811,58 @@ export class DocumentParser {
|
|
|
9466
10811
|
private parseBordersFromXml(
|
|
9467
10812
|
bordersXml: string,
|
|
9468
10813
|
includeDiagonals: boolean
|
|
9469
|
-
): import('../formatting/Style').TableBorders | import('../formatting/Style').CellBorders {
|
|
10814
|
+
): import('../formatting/Style.js').TableBorders | import('../formatting/Style.js').CellBorders {
|
|
9470
10815
|
const borders: any = {};
|
|
9471
10816
|
|
|
10817
|
+
// Local helper so both the main-side loop and the diagonal loop share
|
|
10818
|
+
// the full CT_Border attribute set (§17.18.2): val / sz / space / color
|
|
10819
|
+
// / themeColor / themeTint / themeShade / shadow / frame. Previously
|
|
10820
|
+
// this parser only extracted the four "basic" attrs, so themed borders
|
|
10821
|
+
// and shadow/frame flags on page/table/cell borders were silently
|
|
10822
|
+
// dropped on every load → save round-trip.
|
|
10823
|
+
const parseBorderAttrs = (
|
|
10824
|
+
type: string
|
|
10825
|
+
): import('../formatting/Style.js').BorderProperties | null => {
|
|
10826
|
+
const tag = XMLParser.extractSelfClosingTag(bordersXml, `w:${type}`);
|
|
10827
|
+
if (!tag) return null;
|
|
10828
|
+
const ref = `<w:${type}${tag}`;
|
|
10829
|
+
const border: import('../formatting/Style.js').BorderProperties = {};
|
|
10830
|
+
const style = XMLParser.extractAttribute(ref, 'w:val');
|
|
10831
|
+
const size = XMLParser.extractAttribute(ref, 'w:sz');
|
|
10832
|
+
const space = XMLParser.extractAttribute(ref, 'w:space');
|
|
10833
|
+
const color = XMLParser.extractAttribute(ref, 'w:color');
|
|
10834
|
+
const themeColor = XMLParser.extractAttribute(ref, 'w:themeColor');
|
|
10835
|
+
const themeTint = XMLParser.extractAttribute(ref, 'w:themeTint');
|
|
10836
|
+
const themeShade = XMLParser.extractAttribute(ref, 'w:themeShade');
|
|
10837
|
+
const shadow = XMLParser.extractAttribute(ref, 'w:shadow');
|
|
10838
|
+
const frame = XMLParser.extractAttribute(ref, 'w:frame');
|
|
10839
|
+
if (style) border.style = style as any;
|
|
10840
|
+
if (size) border.size = parseInt(size, 10);
|
|
10841
|
+
if (space) border.space = parseInt(space, 10);
|
|
10842
|
+
if (color) border.color = color;
|
|
10843
|
+
if (themeColor) (border as any).themeColor = themeColor;
|
|
10844
|
+
if (themeTint) (border as any).themeTint = themeTint;
|
|
10845
|
+
if (themeShade) (border as any).themeShade = themeShade;
|
|
10846
|
+
if (shadow !== undefined) (border as any).shadow = parseOnOffAttribute(shadow, true);
|
|
10847
|
+
if (frame !== undefined) (border as any).frame = parseOnOffAttribute(frame, true);
|
|
10848
|
+
return Object.keys(border).length > 0 ? border : null;
|
|
10849
|
+
};
|
|
10850
|
+
|
|
10851
|
+
// Per ECMA-376 §17.4.40 CT_TblBorders and §17.4.66 CT_TcBorders the
|
|
10852
|
+
// left / right borders have bidi-aware aliases `w:start` / `w:end`.
|
|
10853
|
+
// Modern authoring tools (Word 2013+, Google Docs) emit the bidi-
|
|
10854
|
+
// aware form by default — prefer those over the legacy `w:left` /
|
|
10855
|
+
// `w:right` so bidi-authored tables round-trip their side borders
|
|
10856
|
+
// (the internal model stores under the left/right keys, matching
|
|
10857
|
+
// the emitter).
|
|
9472
10858
|
const borderTypes = ['top', 'bottom', 'left', 'right', 'insideH', 'insideV'];
|
|
9473
10859
|
for (const type of borderTypes) {
|
|
9474
|
-
if
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
const color = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:color');
|
|
9481
|
-
|
|
9482
|
-
const border: import('../formatting/Style').BorderProperties = {};
|
|
9483
|
-
if (style) border.style = style as any;
|
|
9484
|
-
if (size) border.size = parseInt(size, 10);
|
|
9485
|
-
if (space) border.space = parseInt(space, 10);
|
|
9486
|
-
if (color) border.color = color;
|
|
9487
|
-
|
|
9488
|
-
if (Object.keys(border).length > 0) {
|
|
9489
|
-
borders[type] = border;
|
|
9490
|
-
}
|
|
9491
|
-
}
|
|
10860
|
+
// For left/right: prefer bidi-aware start/end alias if present.
|
|
10861
|
+
const alias = type === 'left' ? 'start' : type === 'right' ? 'end' : type;
|
|
10862
|
+
const tagNameToRead = bordersXml.includes(`<w:${alias}`) ? alias : type;
|
|
10863
|
+
if (bordersXml.includes(`<w:${tagNameToRead}`)) {
|
|
10864
|
+
const border = parseBorderAttrs(tagNameToRead);
|
|
10865
|
+
if (border) borders[type] = border;
|
|
9492
10866
|
}
|
|
9493
10867
|
}
|
|
9494
10868
|
|
|
@@ -9497,23 +10871,8 @@ export class DocumentParser {
|
|
|
9497
10871
|
const diagonalTypes = ['tl2br', 'tr2bl'];
|
|
9498
10872
|
for (const type of diagonalTypes) {
|
|
9499
10873
|
if (bordersXml.includes(`<w:${type}`)) {
|
|
9500
|
-
const
|
|
9501
|
-
if (
|
|
9502
|
-
const style = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:val');
|
|
9503
|
-
const size = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:sz');
|
|
9504
|
-
const space = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:space');
|
|
9505
|
-
const color = XMLParser.extractAttribute(`<w:${type}${tag}`, 'w:color');
|
|
9506
|
-
|
|
9507
|
-
const border: import('../formatting/Style').BorderProperties = {};
|
|
9508
|
-
if (style) border.style = style as any;
|
|
9509
|
-
if (size) border.size = parseInt(size, 10);
|
|
9510
|
-
if (space) border.space = parseInt(space, 10);
|
|
9511
|
-
if (color) border.color = color;
|
|
9512
|
-
|
|
9513
|
-
if (Object.keys(border).length > 0) {
|
|
9514
|
-
borders[type] = border;
|
|
9515
|
-
}
|
|
9516
|
-
}
|
|
10874
|
+
const border = parseBorderAttrs(type);
|
|
10875
|
+
if (border) borders[type] = border;
|
|
9517
10876
|
}
|
|
9518
10877
|
}
|
|
9519
10878
|
}
|
|
@@ -9526,16 +10885,25 @@ export class DocumentParser {
|
|
|
9526
10885
|
* Extracts all 9 ECMA-376 shading attributes including theme colors.
|
|
9527
10886
|
*/
|
|
9528
10887
|
private parseShadingFromObj(shd: any): ShadingConfig | undefined {
|
|
10888
|
+
// Per ECMA-376 §17.3.1.32 CT_Shd, every string-typed attribute
|
|
10889
|
+
// (ST_UcharHexNumber tint/shade, ST_ThemeColor theme refs,
|
|
10890
|
+
// ST_HexColor fill/color, ST_Shd pattern) can be purely numeric in
|
|
10891
|
+
// hex form — "80", "00", "FF", "80000000", etc. XMLParser's
|
|
10892
|
+
// `parseAttributeValue: true` coerces purely-digit hex strings like
|
|
10893
|
+
// "80" to the JS number 80, violating the `string` type on
|
|
10894
|
+
// ShadingConfig. Previously stored values leaked downstream as
|
|
10895
|
+
// numbers (e.g. `.toUpperCase()` would throw); cast every attribute
|
|
10896
|
+
// through `String(...)` so the declared-type contract holds.
|
|
9529
10897
|
const shading: ShadingConfig = {};
|
|
9530
|
-
if (shd['@_w:val']) shading.pattern = shd['@_w:val'];
|
|
9531
|
-
if (shd['@_w:fill']) shading.fill = shd['@_w:fill'];
|
|
9532
|
-
if (shd['@_w:color']) shading.color = shd['@_w:color'];
|
|
9533
|
-
if (shd['@_w:themeFill']) shading.themeFill = shd['@_w:themeFill'];
|
|
9534
|
-
if (shd['@_w:themeColor']) shading.themeColor = shd['@_w:themeColor'];
|
|
9535
|
-
if (shd['@_w:themeFillTint']) shading.themeFillTint = shd['@_w:themeFillTint'];
|
|
9536
|
-
if (shd['@_w:themeFillShade']) shading.themeFillShade = shd['@_w:themeFillShade'];
|
|
9537
|
-
if (shd['@_w:themeTint']) shading.themeTint = shd['@_w:themeTint'];
|
|
9538
|
-
if (shd['@_w:themeShade']) shading.themeShade = shd['@_w:themeShade'];
|
|
10898
|
+
if (shd['@_w:val']) shading.pattern = String(shd['@_w:val']) as ShadingConfig['pattern'];
|
|
10899
|
+
if (shd['@_w:fill']) shading.fill = String(shd['@_w:fill']);
|
|
10900
|
+
if (shd['@_w:color']) shading.color = String(shd['@_w:color']);
|
|
10901
|
+
if (shd['@_w:themeFill']) shading.themeFill = String(shd['@_w:themeFill']);
|
|
10902
|
+
if (shd['@_w:themeColor']) shading.themeColor = String(shd['@_w:themeColor']);
|
|
10903
|
+
if (shd['@_w:themeFillTint']) shading.themeFillTint = String(shd['@_w:themeFillTint']);
|
|
10904
|
+
if (shd['@_w:themeFillShade']) shading.themeFillShade = String(shd['@_w:themeFillShade']);
|
|
10905
|
+
if (shd['@_w:themeTint']) shading.themeTint = String(shd['@_w:themeTint']);
|
|
10906
|
+
if (shd['@_w:themeShade']) shading.themeShade = String(shd['@_w:themeShade']);
|
|
9539
10907
|
return Object.keys(shading).length > 0 ? shading : undefined;
|
|
9540
10908
|
}
|
|
9541
10909
|
|
|
@@ -9576,8 +10944,8 @@ export class DocumentParser {
|
|
|
9576
10944
|
*/
|
|
9577
10945
|
private parseCellMarginsFromXml(
|
|
9578
10946
|
marginXml: string
|
|
9579
|
-
): import('../formatting/Style').CellMargins | undefined {
|
|
9580
|
-
const margins: import('../formatting/Style').CellMargins = {};
|
|
10947
|
+
): import('../formatting/Style.js').CellMargins | undefined {
|
|
10948
|
+
const margins: import('../formatting/Style.js').CellMargins = {};
|
|
9581
10949
|
|
|
9582
10950
|
// Parse top and bottom directly
|
|
9583
10951
|
for (const type of ['top', 'bottom'] as const) {
|
|
@@ -9842,23 +11210,23 @@ export class DocumentParser {
|
|
|
9842
11210
|
imageManager: ImageManager
|
|
9843
11211
|
): Promise<{
|
|
9844
11212
|
headers: {
|
|
9845
|
-
header: import('../elements/Header').Header;
|
|
11213
|
+
header: import('../elements/Header.js').Header;
|
|
9846
11214
|
relationshipId: string;
|
|
9847
11215
|
filename: string;
|
|
9848
11216
|
}[];
|
|
9849
11217
|
footers: {
|
|
9850
|
-
footer: import('../elements/Footer').Footer;
|
|
11218
|
+
footer: import('../elements/Footer.js').Footer;
|
|
9851
11219
|
relationshipId: string;
|
|
9852
11220
|
filename: string;
|
|
9853
11221
|
}[];
|
|
9854
11222
|
}> {
|
|
9855
11223
|
const headers: {
|
|
9856
|
-
header: import('../elements/Header').Header;
|
|
11224
|
+
header: import('../elements/Header.js').Header;
|
|
9857
11225
|
relationshipId: string;
|
|
9858
11226
|
filename: string;
|
|
9859
11227
|
}[] = [];
|
|
9860
11228
|
const footers: {
|
|
9861
|
-
footer: import('../elements/Footer').Footer;
|
|
11229
|
+
footer: import('../elements/Footer.js').Footer;
|
|
9862
11230
|
relationshipId: string;
|
|
9863
11231
|
filename: string;
|
|
9864
11232
|
}[] = [];
|
|
@@ -9872,7 +11240,7 @@ export class DocumentParser {
|
|
|
9872
11240
|
// Parse headers
|
|
9873
11241
|
// Track already-parsed headers by rId to avoid creating duplicates
|
|
9874
11242
|
// when multiple section property types (default, first, even) reference the same header file
|
|
9875
|
-
const parsedHeadersByRId = new Map<string, import('../elements/Header').Header>();
|
|
11243
|
+
const parsedHeadersByRId = new Map<string, import('../elements/Header.js').Header>();
|
|
9876
11244
|
|
|
9877
11245
|
if (sectionProps.headers) {
|
|
9878
11246
|
for (const [type, rId] of Object.entries(sectionProps.headers)) {
|
|
@@ -9935,7 +11303,7 @@ export class DocumentParser {
|
|
|
9935
11303
|
// Parse footers
|
|
9936
11304
|
// Track already-parsed footers by rId to avoid creating duplicates
|
|
9937
11305
|
// when multiple section property types (default, first, even) reference the same footer file
|
|
9938
|
-
const parsedFootersByRId = new Map<string, import('../elements/Footer').Footer>();
|
|
11306
|
+
const parsedFootersByRId = new Map<string, import('../elements/Footer.js').Footer>();
|
|
9939
11307
|
|
|
9940
11308
|
if (sectionProps.footers) {
|
|
9941
11309
|
for (const [type, rId] of Object.entries(sectionProps.footers)) {
|
|
@@ -10124,29 +11492,42 @@ export class DocumentParser {
|
|
|
10124
11492
|
|
|
10125
11493
|
// Table-level properties (w:tblPr context)
|
|
10126
11494
|
if (propsObj['w:tblStyle']) {
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
11495
|
+
// w:tblStyle w:val is ST_String (§17.7.4.62). XMLParser coerces
|
|
11496
|
+
// purely-numeric style IDs (e.g. "2025") to numbers; cast so the
|
|
11497
|
+
// declared `string` contract holds on tracked-change history.
|
|
11498
|
+
const v = propsObj['w:tblStyle']['@_w:val'];
|
|
11499
|
+
result.style = v !== undefined && v !== null ? String(v) : '';
|
|
11500
|
+
}
|
|
11501
|
+
// tblpPr (floating table position) — mirror main-path zero-value
|
|
11502
|
+
// preservation. The tblPrChange emitter re-emits position via
|
|
11503
|
+
// `!== undefined`, so dropping zero-valued tracked "previous"
|
|
11504
|
+
// positions here lost them silently on round-trip.
|
|
10130
11505
|
if (propsObj['w:tblpPr']) {
|
|
10131
11506
|
const tblpPr = propsObj['w:tblpPr'];
|
|
10132
11507
|
const pos: any = {};
|
|
10133
|
-
if (tblpPr['@_w:tblpX']) pos.x =
|
|
10134
|
-
if (tblpPr['@_w:tblpY']) pos.y =
|
|
11508
|
+
if (isExplicitlySet(tblpPr['@_w:tblpX'])) pos.x = safeParseInt(tblpPr['@_w:tblpX']);
|
|
11509
|
+
if (isExplicitlySet(tblpPr['@_w:tblpY'])) pos.y = safeParseInt(tblpPr['@_w:tblpY']);
|
|
10135
11510
|
if (tblpPr['@_w:horzAnchor']) pos.horizontalAnchor = tblpPr['@_w:horzAnchor'];
|
|
10136
11511
|
if (tblpPr['@_w:vertAnchor']) pos.verticalAnchor = tblpPr['@_w:vertAnchor'];
|
|
10137
|
-
if (tblpPr['@_w:leftFromText'])
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
if (tblpPr['@_w:
|
|
10141
|
-
|
|
10142
|
-
|
|
11512
|
+
if (isExplicitlySet(tblpPr['@_w:leftFromText'])) {
|
|
11513
|
+
pos.leftFromText = safeParseInt(tblpPr['@_w:leftFromText']);
|
|
11514
|
+
}
|
|
11515
|
+
if (isExplicitlySet(tblpPr['@_w:rightFromText'])) {
|
|
11516
|
+
pos.rightFromText = safeParseInt(tblpPr['@_w:rightFromText']);
|
|
11517
|
+
}
|
|
11518
|
+
if (isExplicitlySet(tblpPr['@_w:topFromText'])) {
|
|
11519
|
+
pos.topFromText = safeParseInt(tblpPr['@_w:topFromText']);
|
|
11520
|
+
}
|
|
11521
|
+
if (isExplicitlySet(tblpPr['@_w:bottomFromText'])) {
|
|
11522
|
+
pos.bottomFromText = safeParseInt(tblpPr['@_w:bottomFromText']);
|
|
11523
|
+
}
|
|
10143
11524
|
if (Object.keys(pos).length > 0) result.position = pos;
|
|
10144
11525
|
}
|
|
10145
11526
|
if (propsObj['w:tblOverlap']) {
|
|
10146
11527
|
result.overlap = propsObj['w:tblOverlap']['@_w:val'];
|
|
10147
11528
|
}
|
|
10148
11529
|
if (propsObj['w:bidiVisual']) {
|
|
10149
|
-
result.bidiVisual =
|
|
11530
|
+
result.bidiVisual = parseOoxmlBoolean(propsObj['w:bidiVisual']);
|
|
10150
11531
|
}
|
|
10151
11532
|
if (propsObj['w:tblStyleRowBandSize']) {
|
|
10152
11533
|
result.tblStyleRowBandSize = parseInt(
|
|
@@ -10192,21 +11573,55 @@ export class DocumentParser {
|
|
|
10192
11573
|
const borders: any = {};
|
|
10193
11574
|
const bordersObj = propsObj['w:tblBorders'];
|
|
10194
11575
|
for (const side of ['top', 'bottom', 'left', 'right', 'insideH', 'insideV']) {
|
|
10195
|
-
|
|
10196
|
-
|
|
11576
|
+
// Prefer bidi-aware w:start/w:end aliases over legacy w:left/
|
|
11577
|
+
// w:right (ECMA-376 §17.4.40 CT_TblBorders). Same pattern as
|
|
11578
|
+
// the main table borders parser — the bidi-aware form is the
|
|
11579
|
+
// preferred modern spelling.
|
|
11580
|
+
const aliasKey = side === 'left' ? 'w:start' : side === 'right' ? 'w:end' : undefined;
|
|
11581
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${side}`];
|
|
11582
|
+
if (borderObj) {
|
|
11583
|
+
borders[side] = this.parseBorderElement(borderObj);
|
|
10197
11584
|
}
|
|
10198
11585
|
}
|
|
10199
11586
|
if (Object.keys(borders).length > 0) result.borders = borders;
|
|
10200
11587
|
}
|
|
11588
|
+
// tblLook per ECMA-376 §17.4.57 — supports both hex-string format
|
|
11589
|
+
// (w:val="04A0") AND the expanded individual-attribute form
|
|
11590
|
+
// (firstRow/lastRow/firstColumn/lastColumn/noHBand/noVBand).
|
|
11591
|
+
// Word often emits the expanded form (no w:val) inside *PrChange
|
|
11592
|
+
// previous-properties; the hex-only read silently collapsed every
|
|
11593
|
+
// flag to "0000" on round-trip.
|
|
10201
11594
|
if (propsObj['w:tblLook']) {
|
|
10202
11595
|
const look = propsObj['w:tblLook'];
|
|
10203
|
-
|
|
11596
|
+
if (look['@_w:val']) {
|
|
11597
|
+
result.tblLook = String(look['@_w:val']);
|
|
11598
|
+
} else {
|
|
11599
|
+
const attrIsOn = (name: string): boolean => {
|
|
11600
|
+
const v = look[name];
|
|
11601
|
+
if (v === undefined) return false;
|
|
11602
|
+
return parseOoxmlBoolean({ '@_w:val': v });
|
|
11603
|
+
};
|
|
11604
|
+
let value = 0;
|
|
11605
|
+
if (attrIsOn('@_w:firstRow')) value |= 0x0020;
|
|
11606
|
+
if (attrIsOn('@_w:lastRow')) value |= 0x0040;
|
|
11607
|
+
if (attrIsOn('@_w:firstColumn')) value |= 0x0080;
|
|
11608
|
+
if (attrIsOn('@_w:lastColumn')) value |= 0x0100;
|
|
11609
|
+
if (attrIsOn('@_w:noHBand')) value |= 0x0200;
|
|
11610
|
+
if (attrIsOn('@_w:noVBand')) value |= 0x0400;
|
|
11611
|
+
result.tblLook = value.toString(16).toUpperCase().padStart(4, '0');
|
|
11612
|
+
}
|
|
10204
11613
|
}
|
|
10205
11614
|
if (propsObj['w:tblCaption']) {
|
|
10206
|
-
|
|
11615
|
+
// w:tblCaption w:val is ST_String (§17.4.62). Cast through
|
|
11616
|
+
// String() so purely-numeric caption text round-trips as a
|
|
11617
|
+
// string inside the tracked-change previousProperties.
|
|
11618
|
+
const v = propsObj['w:tblCaption']['@_w:val'];
|
|
11619
|
+
result.caption = v !== undefined && v !== null ? String(v) : undefined;
|
|
10207
11620
|
}
|
|
10208
11621
|
if (propsObj['w:tblDescription']) {
|
|
10209
|
-
|
|
11622
|
+
// w:tblDescription w:val is ST_String (§17.4.63).
|
|
11623
|
+
const v = propsObj['w:tblDescription']['@_w:val'];
|
|
11624
|
+
result.description = v !== undefined && v !== null ? String(v) : undefined;
|
|
10210
11625
|
}
|
|
10211
11626
|
|
|
10212
11627
|
// Row-level properties (w:trPr context) — all CT_TrPr elements
|
|
@@ -10237,14 +11652,15 @@ export class DocumentParser {
|
|
|
10237
11652
|
const rule = propsObj['w:trHeight']['@_w:hRule'];
|
|
10238
11653
|
if (rule) result.heightRule = rule;
|
|
10239
11654
|
}
|
|
11655
|
+
// Row CT_OnOff — honour w:val per ECMA-376 §17.17.4 (ST_OnOff)
|
|
10240
11656
|
if (propsObj['w:tblHeader']) {
|
|
10241
|
-
result.isHeader =
|
|
11657
|
+
result.isHeader = parseOoxmlBoolean(propsObj['w:tblHeader']);
|
|
10242
11658
|
}
|
|
10243
11659
|
if (propsObj['w:cantSplit']) {
|
|
10244
|
-
result.cantSplit =
|
|
11660
|
+
result.cantSplit = parseOoxmlBoolean(propsObj['w:cantSplit']);
|
|
10245
11661
|
}
|
|
10246
11662
|
if (propsObj['w:hidden']) {
|
|
10247
|
-
result.hidden =
|
|
11663
|
+
result.hidden = parseOoxmlBoolean(propsObj['w:hidden']);
|
|
10248
11664
|
}
|
|
10249
11665
|
|
|
10250
11666
|
// Cell-level properties (w:tcPr context) — all CT_TcPr elements
|
|
@@ -10265,14 +11681,19 @@ export class DocumentParser {
|
|
|
10265
11681
|
const borders: any = {};
|
|
10266
11682
|
const bordersObj = propsObj['w:tcBorders'];
|
|
10267
11683
|
for (const side of ['top', 'bottom', 'left', 'right', 'tl2br', 'tr2bl']) {
|
|
10268
|
-
|
|
10269
|
-
|
|
11684
|
+
// Prefer bidi-aware w:start/w:end aliases for left/right
|
|
11685
|
+
// (ECMA-376 §17.4.66 CT_TcBorders). Diagonals (tl2br/tr2bl)
|
|
11686
|
+
// have no bidi aliases.
|
|
11687
|
+
const aliasKey = side === 'left' ? 'w:start' : side === 'right' ? 'w:end' : undefined;
|
|
11688
|
+
const borderObj = (aliasKey && bordersObj[aliasKey]) || bordersObj[`w:${side}`];
|
|
11689
|
+
if (borderObj) {
|
|
11690
|
+
borders[side] = this.parseBorderElement(borderObj);
|
|
10270
11691
|
}
|
|
10271
11692
|
}
|
|
10272
11693
|
if (Object.keys(borders).length > 0) result.borders = borders;
|
|
10273
11694
|
}
|
|
10274
11695
|
if (propsObj['w:noWrap']) {
|
|
10275
|
-
result.noWrap =
|
|
11696
|
+
result.noWrap = parseOoxmlBoolean(propsObj['w:noWrap']);
|
|
10276
11697
|
}
|
|
10277
11698
|
if (propsObj['w:tcMar']) {
|
|
10278
11699
|
const tcMar = propsObj['w:tcMar'];
|
|
@@ -10289,13 +11710,13 @@ export class DocumentParser {
|
|
|
10289
11710
|
result.textDirection = propsObj['w:textDirection']['@_w:val'];
|
|
10290
11711
|
}
|
|
10291
11712
|
if (propsObj['w:tcFitText']) {
|
|
10292
|
-
result.fitText =
|
|
11713
|
+
result.fitText = parseOoxmlBoolean(propsObj['w:tcFitText']);
|
|
10293
11714
|
}
|
|
10294
11715
|
if (propsObj['w:vAlign']) {
|
|
10295
11716
|
result.verticalAlignment = propsObj['w:vAlign']['@_w:val'];
|
|
10296
11717
|
}
|
|
10297
11718
|
if (propsObj['w:hideMark']) {
|
|
10298
|
-
result.hideMark =
|
|
11719
|
+
result.hideMark = parseOoxmlBoolean(propsObj['w:hideMark']);
|
|
10299
11720
|
}
|
|
10300
11721
|
if (propsObj['w:cnfStyle']) {
|
|
10301
11722
|
result.cnfStyle = propsObj['w:cnfStyle']['@_w:val'];
|
|
@@ -10325,6 +11746,79 @@ export class DocumentParser {
|
|
|
10325
11746
|
if (!sectPrXml) return {};
|
|
10326
11747
|
const result: Record<string, any> = {};
|
|
10327
11748
|
|
|
11749
|
+
// Footnote properties (w:footnotePr) per §17.11.9. The main sectPr parser
|
|
11750
|
+
// reads these, and the emitter supports prev.footnotePr, but the
|
|
11751
|
+
// sectPrChange parser previously dropped them entirely — tracked history
|
|
11752
|
+
// of changes to footnote numbering format / position / start / restart
|
|
11753
|
+
// was lost on every round-trip.
|
|
11754
|
+
const footnotePrElements = XMLParser.extractElements(sectPrXml, 'w:footnotePr');
|
|
11755
|
+
if (footnotePrElements.length > 0 && footnotePrElements[0]) {
|
|
11756
|
+
const fnPr = footnotePrElements[0];
|
|
11757
|
+
const fnObj: any = {};
|
|
11758
|
+
const posElements = XMLParser.extractElements(fnPr, 'w:pos');
|
|
11759
|
+
if (posElements[0]) {
|
|
11760
|
+
const pos = XMLParser.extractAttribute(posElements[0], 'w:val');
|
|
11761
|
+
if (pos) fnObj.position = pos;
|
|
11762
|
+
}
|
|
11763
|
+
const numFmtElements = XMLParser.extractElements(fnPr, 'w:numFmt');
|
|
11764
|
+
if (numFmtElements[0]) {
|
|
11765
|
+
const fmt = XMLParser.extractAttribute(numFmtElements[0], 'w:val');
|
|
11766
|
+
if (fmt) fnObj.numberFormat = fmt;
|
|
11767
|
+
}
|
|
11768
|
+
const numStartElements = XMLParser.extractElements(fnPr, 'w:numStart');
|
|
11769
|
+
if (numStartElements[0]) {
|
|
11770
|
+
const start = XMLParser.extractAttribute(numStartElements[0], 'w:val');
|
|
11771
|
+
if (start !== undefined) fnObj.startNumber = parseInt(String(start), 10);
|
|
11772
|
+
}
|
|
11773
|
+
const numRestartElements = XMLParser.extractElements(fnPr, 'w:numRestart');
|
|
11774
|
+
if (numRestartElements[0]) {
|
|
11775
|
+
const restart = XMLParser.extractAttribute(numRestartElements[0], 'w:val');
|
|
11776
|
+
if (restart) fnObj.restart = restart;
|
|
11777
|
+
}
|
|
11778
|
+
if (Object.keys(fnObj).length > 0) result.footnotePr = fnObj;
|
|
11779
|
+
}
|
|
11780
|
+
|
|
11781
|
+
// Endnote properties (w:endnotePr) per §17.11.5 — mirror of footnotePr.
|
|
11782
|
+
const endnotePrElements = XMLParser.extractElements(sectPrXml, 'w:endnotePr');
|
|
11783
|
+
if (endnotePrElements.length > 0 && endnotePrElements[0]) {
|
|
11784
|
+
const enPr = endnotePrElements[0];
|
|
11785
|
+
const enObj: any = {};
|
|
11786
|
+
const posElements = XMLParser.extractElements(enPr, 'w:pos');
|
|
11787
|
+
if (posElements[0]) {
|
|
11788
|
+
const pos = XMLParser.extractAttribute(posElements[0], 'w:val');
|
|
11789
|
+
if (pos) enObj.position = pos;
|
|
11790
|
+
}
|
|
11791
|
+
const numFmtElements = XMLParser.extractElements(enPr, 'w:numFmt');
|
|
11792
|
+
if (numFmtElements[0]) {
|
|
11793
|
+
const fmt = XMLParser.extractAttribute(numFmtElements[0], 'w:val');
|
|
11794
|
+
if (fmt) enObj.numberFormat = fmt;
|
|
11795
|
+
}
|
|
11796
|
+
const numStartElements = XMLParser.extractElements(enPr, 'w:numStart');
|
|
11797
|
+
if (numStartElements[0]) {
|
|
11798
|
+
const start = XMLParser.extractAttribute(numStartElements[0], 'w:val');
|
|
11799
|
+
if (start !== undefined) enObj.startNumber = parseInt(String(start), 10);
|
|
11800
|
+
}
|
|
11801
|
+
const numRestartElements = XMLParser.extractElements(enPr, 'w:numRestart');
|
|
11802
|
+
if (numRestartElements[0]) {
|
|
11803
|
+
const restart = XMLParser.extractAttribute(numRestartElements[0], 'w:val');
|
|
11804
|
+
if (restart) enObj.restart = restart;
|
|
11805
|
+
}
|
|
11806
|
+
if (Object.keys(enObj).length > 0) result.endnotePr = enObj;
|
|
11807
|
+
}
|
|
11808
|
+
|
|
11809
|
+
// Paper source (w:paperSrc) per §17.6.12 CT_PaperSource — first-page / other
|
|
11810
|
+
// paper tray selection. Both attributes optional per schema.
|
|
11811
|
+
const paperSrcElements = XMLParser.extractElements(sectPrXml, 'w:paperSrc');
|
|
11812
|
+
if (paperSrcElements.length > 0 && paperSrcElements[0]) {
|
|
11813
|
+
const ps = paperSrcElements[0];
|
|
11814
|
+
const psObj: any = {};
|
|
11815
|
+
const first = XMLParser.extractAttribute(ps, 'w:first');
|
|
11816
|
+
if (first !== undefined) psObj.first = parseInt(String(first), 10);
|
|
11817
|
+
const other = XMLParser.extractAttribute(ps, 'w:other');
|
|
11818
|
+
if (other !== undefined) psObj.other = parseInt(String(other), 10);
|
|
11819
|
+
if (Object.keys(psObj).length > 0) result.paperSource = psObj;
|
|
11820
|
+
}
|
|
11821
|
+
|
|
10328
11822
|
// Page size
|
|
10329
11823
|
const pgSzElements = XMLParser.extractElements(sectPrXml, 'w:pgSz');
|
|
10330
11824
|
if (pgSzElements.length > 0 && pgSzElements[0]) {
|
|
@@ -10343,7 +11837,10 @@ export class DocumentParser {
|
|
|
10343
11837
|
}
|
|
10344
11838
|
}
|
|
10345
11839
|
|
|
10346
|
-
// Margins
|
|
11840
|
+
// Margins — full CT_PageMar attribute set (§17.6.11) including w:gutter
|
|
11841
|
+
// (the book-binding margin). Previously gutter was dropped on sectPrChange
|
|
11842
|
+
// history, so any tracked change to a binding-gutter value lost the
|
|
11843
|
+
// previous value on round-trip.
|
|
10347
11844
|
const pgMarElements = XMLParser.extractElements(sectPrXml, 'w:pgMar');
|
|
10348
11845
|
if (pgMarElements.length > 0 && pgMarElements[0]) {
|
|
10349
11846
|
const pgMar = pgMarElements[0];
|
|
@@ -10360,6 +11857,8 @@ export class DocumentParser {
|
|
|
10360
11857
|
if (header) margins.header = parseInt(header, 10);
|
|
10361
11858
|
const footer = XMLParser.extractAttribute(pgMar, 'w:footer');
|
|
10362
11859
|
if (footer) margins.footer = parseInt(footer, 10);
|
|
11860
|
+
const gutter = XMLParser.extractAttribute(pgMar, 'w:gutter');
|
|
11861
|
+
if (gutter) margins.gutter = parseInt(gutter, 10);
|
|
10363
11862
|
if (Object.keys(margins).length > 0) result.margins = margins;
|
|
10364
11863
|
}
|
|
10365
11864
|
|
|
@@ -10386,7 +11885,13 @@ export class DocumentParser {
|
|
|
10386
11885
|
if (Object.keys(lnObj).length > 0) result.lineNumbering = lnObj;
|
|
10387
11886
|
}
|
|
10388
11887
|
|
|
10389
|
-
// Page numbering
|
|
11888
|
+
// Page numbering — full CT_PageNumber attribute set (§17.6.12):
|
|
11889
|
+
// fmt / start / chapStyle / chapSep. Previously only fmt+start were read,
|
|
11890
|
+
// so tracked-change history of chapter-numbering edits (e.g. switching
|
|
11891
|
+
// from "Heading 1" to "Heading 2" as the chapter marker, or changing the
|
|
11892
|
+
// chapter separator from hyphen to emDash) lost the previous values.
|
|
11893
|
+
// The Section.ts emitter stores chapStyle / chapSep as top-level
|
|
11894
|
+
// properties rather than on pageNumbering, so expose them the same way.
|
|
10390
11895
|
const pgNumElements = XMLParser.extractElements(sectPrXml, 'w:pgNumType');
|
|
10391
11896
|
if (pgNumElements.length > 0 && pgNumElements[0]) {
|
|
10392
11897
|
const pn = pgNumElements[0];
|
|
@@ -10396,6 +11901,12 @@ export class DocumentParser {
|
|
|
10396
11901
|
const fmt = XMLParser.extractAttribute(pn, 'w:fmt');
|
|
10397
11902
|
if (fmt) pnObj.format = fmt;
|
|
10398
11903
|
if (Object.keys(pnObj).length > 0) result.pageNumbering = pnObj;
|
|
11904
|
+
// Mirror the main-sectPr parser: chapStyle / chapSep live at the root
|
|
11905
|
+
// of the section properties, not inside pageNumbering.
|
|
11906
|
+
const chapStyle = XMLParser.extractAttribute(pn, 'w:chapStyle');
|
|
11907
|
+
if (chapStyle !== undefined) result.chapStyle = parseInt(String(chapStyle), 10);
|
|
11908
|
+
const chapSep = XMLParser.extractAttribute(pn, 'w:chapSep');
|
|
11909
|
+
if (chapSep) result.chapSep = chapSep;
|
|
10399
11910
|
}
|
|
10400
11911
|
|
|
10401
11912
|
// Columns
|
|
@@ -10404,16 +11915,58 @@ export class DocumentParser {
|
|
|
10404
11915
|
const cols = colsElements[0];
|
|
10405
11916
|
const num = XMLParser.extractAttribute(cols, 'w:num');
|
|
10406
11917
|
const space = XMLParser.extractAttribute(cols, 'w:space');
|
|
11918
|
+
// Full CT_Columns attribute set (§17.6.4): num / space / equalWidth / sep
|
|
11919
|
+
// plus the child <w:col w:w="..." w:space="..."/> entries for per-column
|
|
11920
|
+
// widths. Previously only num+space were read, so sectPrChange history of
|
|
11921
|
+
// a columns-layout change dropped equalWidth, the separator line, and
|
|
11922
|
+
// the entire custom column-width / per-column-space configuration.
|
|
11923
|
+
const equalWidth = XMLParser.extractAttribute(cols, 'w:equalWidth');
|
|
11924
|
+
const sep = XMLParser.extractAttribute(cols, 'w:sep');
|
|
11925
|
+
|
|
11926
|
+
// Extract individual <w:col> children for non-equal-width layouts.
|
|
11927
|
+
const colChildElements = XMLParser.extractElements(cols, 'w:col');
|
|
11928
|
+
const columnWidths: number[] = [];
|
|
11929
|
+
const columnSpaces: number[] = [];
|
|
11930
|
+
let hasColumnSpaces = false;
|
|
11931
|
+
for (const col of colChildElements) {
|
|
11932
|
+
const width = XMLParser.extractAttribute(col, 'w:w');
|
|
11933
|
+
if (width) columnWidths.push(parseInt(width, 10));
|
|
11934
|
+
const colSpace = XMLParser.extractAttribute(col, 'w:space');
|
|
11935
|
+
if (colSpace) {
|
|
11936
|
+
columnSpaces.push(parseInt(colSpace, 10));
|
|
11937
|
+
hasColumnSpaces = true;
|
|
11938
|
+
} else {
|
|
11939
|
+
columnSpaces.push(0);
|
|
11940
|
+
}
|
|
11941
|
+
}
|
|
11942
|
+
|
|
10407
11943
|
if (num) {
|
|
10408
11944
|
result.columns = {
|
|
10409
11945
|
count: parseInt(num, 10),
|
|
10410
11946
|
space: space ? parseInt(space, 10) : undefined,
|
|
11947
|
+
equalWidth: equalWidth ? parseOnOffAttribute(equalWidth) : undefined,
|
|
11948
|
+
separator: sep ? parseOnOffAttribute(sep) : undefined,
|
|
11949
|
+
columnWidths: columnWidths.length > 0 ? columnWidths : undefined,
|
|
11950
|
+
columnSpaces: hasColumnSpaces ? columnSpaces : undefined,
|
|
10411
11951
|
};
|
|
10412
11952
|
}
|
|
10413
11953
|
}
|
|
10414
11954
|
|
|
10415
|
-
//
|
|
10416
|
-
|
|
11955
|
+
// CT_OnOff sectPr flags — honour w:val per ECMA-376 §17.17.4 (ST_OnOff).
|
|
11956
|
+
// Previously these used substring `.includes()`, which both ignored w:val
|
|
11957
|
+
// (flipping explicit false to true) and could false-positive on prefix
|
|
11958
|
+
// matches (e.g. "<w:bidi" inside "<w:bidiVisual"). Use extractElements +
|
|
11959
|
+
// extractAttribute + parseOnOffAttribute instead.
|
|
11960
|
+
const parseSectCtOnOff = (tagName: string): boolean | undefined => {
|
|
11961
|
+
const els = XMLParser.extractElements(sectPrXml, tagName);
|
|
11962
|
+
if (els.length === 0 || !els[0]) return undefined;
|
|
11963
|
+
const v = XMLParser.extractAttribute(els[0], 'w:val');
|
|
11964
|
+
return parseOnOffAttribute(v, true);
|
|
11965
|
+
};
|
|
11966
|
+
|
|
11967
|
+
// Form protection (w:formProt) — CT_OnOff
|
|
11968
|
+
const formProtVal = parseSectCtOnOff('w:formProt');
|
|
11969
|
+
if (formProtVal !== undefined) result.formProt = formProtVal;
|
|
10417
11970
|
|
|
10418
11971
|
// Vertical alignment
|
|
10419
11972
|
const vAlignElements = XMLParser.extractElements(sectPrXml, 'w:vAlign');
|
|
@@ -10422,11 +11975,13 @@ export class DocumentParser {
|
|
|
10422
11975
|
if (val) result.verticalAlignment = val;
|
|
10423
11976
|
}
|
|
10424
11977
|
|
|
10425
|
-
// Suppress endnotes
|
|
10426
|
-
|
|
11978
|
+
// Suppress endnotes (w:noEndnote) — CT_OnOff
|
|
11979
|
+
const noEndnoteVal = parseSectCtOnOff('w:noEndnote');
|
|
11980
|
+
if (noEndnoteVal !== undefined) result.noEndnote = noEndnoteVal;
|
|
10427
11981
|
|
|
10428
|
-
// Title page
|
|
10429
|
-
|
|
11982
|
+
// Title page (w:titlePg) — CT_OnOff
|
|
11983
|
+
const titlePgVal = parseSectCtOnOff('w:titlePg');
|
|
11984
|
+
if (titlePgVal !== undefined) result.titlePage = titlePgVal;
|
|
10430
11985
|
|
|
10431
11986
|
// Text direction
|
|
10432
11987
|
const textDirElements = XMLParser.extractElements(sectPrXml, 'w:textDirection');
|
|
@@ -10435,11 +11990,13 @@ export class DocumentParser {
|
|
|
10435
11990
|
if (val) result.textDirection = val;
|
|
10436
11991
|
}
|
|
10437
11992
|
|
|
10438
|
-
// Bidi section
|
|
10439
|
-
|
|
11993
|
+
// Bidi section (w:bidi) — CT_OnOff
|
|
11994
|
+
const bidiVal = parseSectCtOnOff('w:bidi');
|
|
11995
|
+
if (bidiVal !== undefined) result.bidi = bidiVal;
|
|
10440
11996
|
|
|
10441
|
-
// RTL gutter
|
|
10442
|
-
|
|
11997
|
+
// RTL gutter (w:rtlGutter) — CT_OnOff
|
|
11998
|
+
const rtlGutterVal = parseSectCtOnOff('w:rtlGutter');
|
|
11999
|
+
if (rtlGutterVal !== undefined) result.rtlGutter = rtlGutterVal;
|
|
10443
12000
|
|
|
10444
12001
|
// Document grid
|
|
10445
12002
|
const docGridElements = XMLParser.extractElements(sectPrXml, 'w:docGrid');
|
|
@@ -10455,6 +12012,64 @@ export class DocumentParser {
|
|
|
10455
12012
|
if (Object.keys(dgObj).length > 0) result.docGrid = dgObj;
|
|
10456
12013
|
}
|
|
10457
12014
|
|
|
12015
|
+
// Page borders (w:pgBorders) per ECMA-376 §17.6.10. The main sectPr parser
|
|
12016
|
+
// reads these, but the sectPrChange previous-sectPr parser previously
|
|
12017
|
+
// didn't — so a tracked-change history of page-border edits lost the
|
|
12018
|
+
// entire "previous" border configuration (style, color, themeColor,
|
|
12019
|
+
// themeTint, themeShade, shadow, frame) every round-trip. The emitter
|
|
12020
|
+
// supports prev.pageBorders already; this is the missing parser half.
|
|
12021
|
+
const pgBordersElements = XMLParser.extractElements(sectPrXml, 'w:pgBorders');
|
|
12022
|
+
if (pgBordersElements.length > 0 && pgBordersElements[0]) {
|
|
12023
|
+
const pgBordersXml = pgBordersElements[0];
|
|
12024
|
+
const pageBorders: any = {};
|
|
12025
|
+
const offsetFrom = XMLParser.extractAttribute(pgBordersXml, 'w:offsetFrom');
|
|
12026
|
+
if (offsetFrom) pageBorders.offsetFrom = offsetFrom;
|
|
12027
|
+
const display = XMLParser.extractAttribute(pgBordersXml, 'w:display');
|
|
12028
|
+
if (display) pageBorders.display = display;
|
|
12029
|
+
const zOrder = XMLParser.extractAttribute(pgBordersXml, 'w:zOrder');
|
|
12030
|
+
if (zOrder) pageBorders.zOrder = zOrder;
|
|
12031
|
+
|
|
12032
|
+
// Per-side border parser mirrors the main-sectPr logic — full CT_Border
|
|
12033
|
+
// attribute set including themed colors and shadow/frame flags.
|
|
12034
|
+
const parsePrevBorder = (sideXml: string): any | undefined => {
|
|
12035
|
+
if (!sideXml) return undefined;
|
|
12036
|
+
const border: any = {};
|
|
12037
|
+
const val = XMLParser.extractAttribute(sideXml, 'w:val');
|
|
12038
|
+
if (val) border.style = val;
|
|
12039
|
+
const sz = XMLParser.extractAttribute(sideXml, 'w:sz');
|
|
12040
|
+
if (sz) border.size = parseInt(sz, 10);
|
|
12041
|
+
const color = XMLParser.extractAttribute(sideXml, 'w:color');
|
|
12042
|
+
if (color) border.color = color;
|
|
12043
|
+
const space = XMLParser.extractAttribute(sideXml, 'w:space');
|
|
12044
|
+
if (space) border.space = parseInt(space, 10);
|
|
12045
|
+
const shadow = XMLParser.extractAttribute(sideXml, 'w:shadow');
|
|
12046
|
+
if (shadow !== undefined) border.shadow = parseOnOffAttribute(shadow, true);
|
|
12047
|
+
const frame = XMLParser.extractAttribute(sideXml, 'w:frame');
|
|
12048
|
+
if (frame !== undefined) border.frame = parseOnOffAttribute(frame, true);
|
|
12049
|
+
const themeColor = XMLParser.extractAttribute(sideXml, 'w:themeColor');
|
|
12050
|
+
if (themeColor) border.themeColor = themeColor;
|
|
12051
|
+
const themeTint = XMLParser.extractAttribute(sideXml, 'w:themeTint');
|
|
12052
|
+
if (themeTint) border.themeTint = themeTint;
|
|
12053
|
+
const themeShade = XMLParser.extractAttribute(sideXml, 'w:themeShade');
|
|
12054
|
+
if (themeShade) border.themeShade = themeShade;
|
|
12055
|
+
const artId = XMLParser.extractAttribute(sideXml, 'w:id');
|
|
12056
|
+
if (artId) border.artId = parseInt(artId, 10);
|
|
12057
|
+
return Object.keys(border).length > 0 ? border : undefined;
|
|
12058
|
+
};
|
|
12059
|
+
|
|
12060
|
+
for (const side of ['top', 'left', 'bottom', 'right']) {
|
|
12061
|
+
const sideElements = XMLParser.extractElements(pgBordersXml, `w:${side}`);
|
|
12062
|
+
if (sideElements.length > 0 && sideElements[0]) {
|
|
12063
|
+
const border = parsePrevBorder(sideElements[0]);
|
|
12064
|
+
if (border) pageBorders[side] = border;
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
12067
|
+
|
|
12068
|
+
if (Object.keys(pageBorders).length > 0) {
|
|
12069
|
+
result.pageBorders = pageBorders;
|
|
12070
|
+
}
|
|
12071
|
+
}
|
|
12072
|
+
|
|
10458
12073
|
return result;
|
|
10459
12074
|
}
|
|
10460
12075
|
}
|