sw/source/filter/ww8/docxattributeoutput.cxx | 51 +++++++++++++-------------- sw/source/filter/ww8/docxattributeoutput.hxx | 22 ++++++++++- 2 files changed, 46 insertions(+), 27 deletions(-)
New commits: commit b6e9ed9621d23b43cc29fac1b5194f91ce5cfccb Author: Justin Luth <[email protected]> AuthorDate: Sat Feb 14 20:16:10 2026 -0500 Commit: Miklos Vajna <[email protected]> CommitDate: Fri Feb 20 09:40:23 2026 +0100 tdf#170602 docx export: Revise how SdtBlockHelper is cleared No Functional Change intended ultimately, although with this scope of modification some kind of behavioural change is inevitable. This patch lays the groundwork for the rest of this bug report's patchset. SdtBlockHelper so far has been set VERY LATE in the process. All the text and runs have already finished and we EndParagraph before we find out that this paragraph is actually a grabbagged blockSdt content control. But how a grabbagged blockSdt starts is very, umm, vague. So the grabbag is cleared almost as soon as it is filled to prevent it from spilling over into tables/flies/comments etc. In order to make the information available at the beginning of the paragraph (StartParagraph), the logic of how to use and clear this grabbag cache needed to be substantially revised. In order to accomplish this, I needed to keep track of what position was used to fill this SdtBlockHelper, and only initiate a write when the currentPos matches the SdtBlockHelper pos. The idea is to be able to fill the SdtBlockHelper early, and retain the information until the <w/:sdt> end marker has been written. Change-Id: I30443822f8e09fc083daa4f0492fc4f95ad7391b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199644 Reviewed-by: Miklos Vajna <[email protected]> Reviewed-by: Justin Luth <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 08b5ff354110..50663e6b7d27 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -781,11 +781,16 @@ void SdtBlockHelper::clearGrabbagValues() m_nTabIndex = 0; } -void SdtBlockHelper::WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer, bool bRunTextIsOn, bool bParagraphHasDrawing) +void SdtBlockHelper::WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer, + const SwPosition* pStartPosition, + bool bRunTextIsOn, bool bParagraphHasDrawing) { if (!m_oSdtPrToken.has_value()) return; // not a full Sdt definition + if (pStartPosition != m_pStartPosition) + return; // Sdt grabbag data is not for the current paragraph - only used for m_aParagraphSdt + // sdt start mark pSerializer->mark(DocxAttributeOutput::Tag_WriteSdtBlock); @@ -835,9 +840,6 @@ void SdtBlockHelper::WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSeriali // write the ending tags after the paragraph m_bStartedSdt = true; - - // clear sdt status - clearGrabbagValues(); } void SdtBlockHelper::WriteExtraParams(const ::sax_fastparser::FSHelperPtr& pSerializer) @@ -896,10 +898,22 @@ void SdtBlockHelper::EndSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerialize pSerializer->endElementNS(XML_w, XML_sdtContent); pSerializer->endElementNS(XML_w, XML_sdt); m_bStartedSdt = false; + m_pStartPosition = nullptr; + clearGrabbagValues(); } -void SdtBlockHelper::GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt) +void SdtBlockHelper::GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt, + const SwPosition* pStartPosition) { + if (m_bStartedSdt) + return; // must not change grabbag cache while <w:sdt> is being written + + if (m_pStartPosition && pStartPosition == m_pStartPosition) + return; // m_aParagraphSdt's params have already been cached from the grabbag. + + clearGrabbagValues(); + m_pStartPosition = pStartPosition; // grabbag cache is valid for this paragraph + for (const beans::PropertyValue& aPropertyValue : aGrabBagSdt) { if (aPropertyValue.Name == "ooxml:CT_SdtPr_checkbox") @@ -1278,7 +1292,8 @@ void DocxAttributeOutput::EndParagraph( const ww8::WW8TableNodeInfoInner::Pointe // on export sdt blocks are never nested ATM if (!m_aParagraphSdt.m_bStartedSdt) { - m_aParagraphSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing()); + m_aParagraphSdt.WriteSdtBlock(m_pSerializer, m_rExport.m_pCurPam->Start(), + m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing()); if (m_aParagraphSdt.m_bStartedSdt) { @@ -1288,12 +1303,6 @@ void DocxAttributeOutput::EndParagraph( const ww8::WW8TableNodeInfoInner::Pointe m_rExport.SdrExporter().setParagraphSdtOpen(true); } } - else - { - //These should be written out to the actual Node and not to the anchor. - //Clear them as they will be repopulated when the node is processed. - m_aParagraphSdt.clearGrabbagValues(); - } m_pSerializer->mark(Tag_StartParagraph_2); @@ -2119,15 +2128,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_In // enclose in a sdt block, if necessary: if one is already started, then don't do it for now // (so on export sdt blocks are never nested ATM) if (!m_aRunSdt.m_bStartedSdt) - { - m_aRunSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing()); - } - else - { - //These should be written out to the actual Node and not to the anchor. - //Clear them as they will be repopulated when the node is processed. - m_aRunSdt.clearGrabbagValues(); - } + m_aRunSdt.WriteSdtBlock(m_pSerializer, nullptr, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing()); m_pSerializer->mergeTopMarks(Tag_StartRun_1); @@ -2582,7 +2583,7 @@ void DocxAttributeOutput::WriteFormDateStart(const OUString& sFullDate, const OU { // There are some extra sdt parameters came from grab bag SdtBlockHelper aSdtBlock; - aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt); + aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt, nullptr); aSdtBlock.WriteExtraParams(m_pSerializer); } @@ -2617,7 +2618,7 @@ void DocxAttributeOutput::WriteSdtPlainText(const OUString & sValue, const uno:: { // There are some extra sdt parameters came from grab bag SdtBlockHelper aSdtBlock; - aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt); + aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt, nullptr); aSdtBlock.WriteExtraParams(m_pSerializer); if (aSdtBlock.m_oSdtPrToken.has_value() && *aSdtBlock.m_oSdtPrToken) @@ -10538,7 +10539,7 @@ void DocxAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem) { const uno::Sequence<beans::PropertyValue> aGrabBagSdt = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >(); - m_aParagraphSdt.GetSdtParamsFromGrabBag(aGrabBagSdt); + m_aParagraphSdt.GetSdtParamsFromGrabBag(aGrabBagSdt, m_rExport.m_pCurPam->Start()); } else if (rGrabBagElement.first == "ParaCnfStyle") { @@ -10657,7 +10658,7 @@ void DocxAttributeOutput::CharGrabBag( const SfxGrabBagItem& rItem ) { const uno::Sequence<beans::PropertyValue> aGrabBagSdt = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >(); - m_aRunSdt.GetSdtParamsFromGrabBag(aGrabBagSdt); + m_aRunSdt.GetSdtParamsFromGrabBag(aGrabBagSdt, nullptr); } else SAL_INFO("sw.ww8", "DocxAttributeOutput::CharGrabBag: unhandled grab bag property " << rGrabBagElement.first); diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index b37547aac8f2..fb7e7feda1be 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -163,12 +163,20 @@ class SdtBlockHelper public: SdtBlockHelper() : m_bStartedSdt(false) + , m_pStartPosition(nullptr) , m_bShowingPlaceHolder(false) , m_nTabIndex(0) {} // m_bStartedSdt tracks whether startElementNS(XML_w, XML_sdt) has been written bool m_bStartedSdt; + // In order to cache the SdtBlockHelper value, some mechanism is needed to check its validity. + // Currently this is needed only for m_aParagraphSdt, so tracking the SwPosition is sufficient. + + // If the SDT has not beeen started (!m_bStartedSdt) and the text positions do not match, + // then this SdtBlockHelper cache may be cleared and re-populated. + const SwPosition* m_pStartPosition; // only used by m_aParagraphSdt + // m_oSdtPrToken is a key GrabBag value, with a two-fold purpose: // - the absence of m_oSdtPrToken also means that (XML_w, XML_sdt) should not be written // - it describes the type of content control: richText(0), plainText, checkbox, dropdown... @@ -190,13 +198,17 @@ public: void clearGrabbagValues(); - void WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer, bool bRunTextIsOn, bool bParagraphHasDrawing); + // pStartPosition must be nullptr unless this SdtBlockHelper is m_aParagraphSdt. + void WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer, + const SwPosition* pStartPosition, + bool bRunTextIsOn, bool bParagraphHasDrawing); void WriteExtraParams(const ::sax_fastparser::FSHelperPtr& pSerializer); /// Closes a currently open SDT block. void EndSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer); - void GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt); + void GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt, + const SwPosition* pStartPosition); }; /// The class that has handlers for various resource types when exporting as DOCX. @@ -1085,7 +1097,13 @@ private: // store hardcoded value which was set during import. sal_Int32 m_nParaBeforeSpacing,m_nParaAfterSpacing; + // m_aParagraphSdt contains a grabbagged block content control that needs to be round-tripped + // because in LO it is not a native content control. + // Some content controls can span multiple paragraphs. + // It starts at the paragraph containing the 'SdtPr' grabbag property, + // and ends at the paragraph before the one containing the 'ParaSdtEndBefore' property. SdtBlockHelper m_aParagraphSdt; + // Same as m_aParagraphSdt except it ends on the run before the one containng 'SdtEndBefore' SdtBlockHelper m_aRunSdt; std::vector<std::map<SvxBoxItemLine, css::table::BorderLine2>> m_aTableStyleConfs;
