sw/qa/extras/ooxmlexport/ooxmlexport20.cxx | 9 -- sw/source/filter/ww8/docxattributeoutput.cxx | 11 +++ sw/source/filter/ww8/docxattributeoutput.hxx | 2 sw/source/filter/ww8/wrtw8nds.cxx | 61 ++++++++++++++++++- sw/source/filter/ww8/wrtww8.hxx | 2 sw/source/writerfilter/dmapper/DomainMapper.cxx | 4 + sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx | 7 +- sw/source/writerfilter/dmapper/PropertyIds.cxx | 1 sw/source/writerfilter/dmapper/PropertyIds.hxx | 1 9 files changed, 85 insertions(+), 13 deletions(-)
New commits: commit d87cf67f8f3346a1e380383917a3a4552fd9248e Author: László Németh <nem...@numbertext.org> AuthorDate: Tue Oct 22 02:01:59 2024 +0200 Commit: László Németh <nem...@numbertext.org> CommitDate: Wed Oct 23 18:26:07 2024 +0200 tdf#131728 sw inline heading: fix missing/broken DOCX export Fix layout interoperability during DOCX round-trip by grab- bagging w:p/w:pPr/w:rPr/w:specVanish, i.e. the style separators. Note: use FrameInteropGrabBag to select the text frames, which are inline headings, exporting only their text content (a single paragraph), and use also ParaInteropGrabBag to export w:specVanish. Note: specVanish lost completely originally, converting inline headings to normal paragraphs. After commit 56588663a0fddc005c12afaa7d3f8874d036875f, text frames (the workaround for inline heading/ToC/bookmark support) were exported instead of plain paragraphs, which were broken at least in LibreOffice. Follow-up to commit 56588663a0fddc005c12afaa7d3f8874d036875f "tdf#131728 sw inline heading: fix DOCX paragraph layout interoperability". Change-Id: Ic61617f9c9652c9364f8262914c66dec093d6910 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175372 Tested-by: Jenkins Reviewed-by: László Németh <nem...@numbertext.org> diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport20.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport20.cxx index f7b87a1fdb8a..6a5b8e6c0535 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport20.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport20.cxx @@ -259,13 +259,8 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf131728) // original inline paragraph, keeping also ODF ToC/PDF bookmark support. xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); - // This was 22 (the 5 inline headings were not inline, i.e. normal paragraphs) - assertXPath(pXmlDoc, "/w:document/w:body/w:p", 17); - - // Still existing headings (duplicated by alternate content) - assertXPath(pXmlDoc, "//w:p", 27); - assertXPath(pXmlDoc, "//w:txbxContent/w:p", 10); - assertXPath(pXmlDoc, "//w:pStyle[@w:val='Heading2']", 10); + // This was 0 (lost style separators) + assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:pPr/w:rPr/w:specVanish", 7); } CPPUNIT_TEST_FIXTURE(Test, testFdo77129) diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 33f20bb6cd0e..de5deb897463 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -3643,6 +3643,12 @@ void DocxAttributeOutput::WriteCollectedRunProperties() } m_aTextEffectsGrabBag.clear(); m_aTextFillGrabBag.clear(); + + if ( m_bParaInlineHeading ) + { + m_pSerializer->singleElementNS(XML_w, XML_specVanish); + m_bParaInlineHeading = false; + } } void DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData ) @@ -10195,6 +10201,8 @@ void DocxAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem) { // Handled already in StartParagraph(). } + else if (rGrabBagElement.first == "ParaInlineHeading") + m_bParaInlineHeading = true; else SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled grab bag property " << rGrabBagElement.first ); } @@ -10350,7 +10358,8 @@ DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr m_bParaBeforeAutoSpacing(false), m_bParaAfterAutoSpacing(false), m_nParaBeforeSpacing(0), - m_nParaAfterSpacing(0) + m_nParaAfterSpacing(0), + m_bParaInlineHeading(false) , m_nStateOfFlyFrame( FLY_NOT_PROCESSED ) { m_nHyperLinkCount.push_back(0); diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index c6626051f332..02b8190ad31d 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -1073,6 +1073,8 @@ private: bool m_bParaBeforeAutoSpacing,m_bParaAfterAutoSpacing; // store hardcoded value which was set during import. sal_Int32 m_nParaBeforeSpacing,m_nParaAfterSpacing; + // flag inline heading + bool m_bParaInlineHeading; SdtBlockHelper m_aParagraphSdt; SdtBlockHelper m_aRunSdt; diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx index c67a7b691cc0..b4451803bb28 100644 --- a/sw/source/filter/ww8/wrtw8nds.cxx +++ b/sw/source/filter/ww8/wrtw8nds.cxx @@ -179,6 +179,34 @@ lcl_getLinkChainName(const uno::Reference<beans::XPropertySet>& rPropertySet, return sLinkChainName; } +static bool lcl_IsInlineHeading(const ww8::Frame &rFrame) +{ + const SwFrameFormat& rFrameFormat = rFrame.GetFrameFormat(); + + uno::Reference<drawing::XShape> xShape; + const SdrObject* pSdrObj = rFrameFormat.FindRealSdrObject(); + if (pSdrObj) + xShape.set(const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropSetInfo; + if (xPropertySet.is()) + xPropSetInfo = xPropertySet->getPropertySetInfo(); + + if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName(u"FrameInteropGrabBag"_ustr)) + { + uno::Sequence<beans::PropertyValue> propList; + xPropertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= propList; + auto pProp = std::find_if(std::cbegin(propList), std::cend(propList), + [](const beans::PropertyValue& rProp) { + return rProp.Name == "FrameInlineHeading"; + }); + if (pProp != std::cend(propList)) + return true; + } + + return false; +} + MSWordAttrIter::MSWordAttrIter( MSWordExportBase& rExport ) : m_pOld( rExport.m_pChpIter ), m_rExport( rExport ) { @@ -724,7 +752,7 @@ bool SwWW8AttrIter::IsAnchorLinkedToThisNode( SwNodeOffset nNodePos ) return nNodePos == maFlyIter->GetPosition().GetNodeIndex(); } -bool SwWW8AttrIter::HasFlysAt(sal_Int32 nSwPos) const +bool SwWW8AttrIter::HasFlysAt(sal_Int32 nSwPos, const ww8::Frame** pInlineHeading) const { for (const auto& rFly : maFlyFrames) { @@ -732,7 +760,16 @@ bool SwWW8AttrIter::HasFlysAt(sal_Int32 nSwPos) const const sal_Int32 nPos = rAnchor.GetContentIndex(); if (nPos == nSwPos) { - return true; + if ( pInlineHeading ) + { + if ( lcl_IsInlineHeading(rFly) ) + { + *pInlineHeading = &rFly; + return true; + } + } + else + return true; } } @@ -836,6 +873,10 @@ FlyProcessingState SwWW8AttrIter::OutFlys(sal_Int32 nSwPos) // Should not write watermark object in the main body text } } + else if ( lcl_IsInlineHeading(*maFlyIter) ) + { + // Should not write inline heading again + } else { // This is not a watermark object - write normally @@ -2306,6 +2347,21 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode ) { SAL_INFO( "sw.ww8", "<OutWW8_SwTextNode>" ); + SwWW8AttrIter aWatermarkAttrIter( *this, rNode ); + + // export inline heading + const ww8::Frame* pInlineHeading; + if (aWatermarkAttrIter.HasFlysAt(0, &pInlineHeading)) + { + if (pInlineHeading->GetContent()->IsTextNode()) + { + SwTextNode *pTextNode = + const_cast<SwTextNode*>(pInlineHeading->GetContent()->GetTextNode()); + if (pTextNode) + OutputTextNode(*pTextNode); + } + } + ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo( m_pTableInfo->getTableNodeInfo( &rNode ) ); //For i120928,identify the last node @@ -2320,7 +2376,6 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode ) // In order to make sure watermark is stored in 'header.xml', check nTextTyp. // if it is document.xml, don't write the tags (watermark should be only in the 'header') - SwWW8AttrIter aWatermarkAttrIter( *this, rNode ); if (( TXT_HDFT != m_nTextTyp) && aWatermarkAttrIter.IsWatermarkFrame()) { return; diff --git a/sw/source/filter/ww8/wrtww8.hxx b/sw/source/filter/ww8/wrtww8.hxx index fb90c8a464e1..a4e85ebc7c1b 100644 --- a/sw/source/filter/ww8/wrtww8.hxx +++ b/sw/source/filter/ww8/wrtww8.hxx @@ -1576,7 +1576,7 @@ public: const SwRedlineData* GetParagraphLevelRedline( ); const SwRedlineData* GetRunLevelRedline( sal_Int32 nPos ); FlyProcessingState OutFlys(sal_Int32 nSwPos); - bool HasFlysAt(sal_Int32 nSwPos) const; + bool HasFlysAt(sal_Int32 nSwPos, const ww8::Frame** pInlineHeading = nullptr) const; sal_Int32 WhereNext() const { return m_nCurrentSwPos; } sal_uInt16 GetScript() const { return mnScript; } diff --git a/sw/source/writerfilter/dmapper/DomainMapper.cxx b/sw/source/writerfilter/dmapper/DomainMapper.cxx index 5d4a0c3b5c14..d99ac0c37cb4 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.cxx @@ -2097,7 +2097,11 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) break; case NS_ooxml::LN_EG_RPrBase_specVanish: if ( nIntValue && !IsStyleSheetImport() ) + { + // put inline heading inside a text frame to get the same layout with ToC/PDF bookmark support m_pImpl->m_StreamStateStack.top().bIsInlineParagraph = true; + m_pImpl->GetTopContext()->Insert( PROP_INLINE_HEADING, uno::Any( true ), false, PARA_GRAB_BAG ); + } break; case NS_ooxml::LN_EG_RPrBase_sz: case NS_ooxml::LN_EG_RPrBase_szCs: diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx index bd444bab73b6..2c495ed140b0 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -2773,6 +2773,9 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con xRangeStart = xParaCursor->getStart(); xRangeEnd = xParaCursor->getEnd(); + comphelper::SequenceAsHashMap aFrameGrabBag; + aFrameGrabBag[u"FrameInlineHeading"_ustr] <<= true; + // TODO anchor as character std::vector<beans::PropertyValue> aFrameProperties { @@ -2782,7 +2785,9 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), text::HoriOrientation::LEFT), comphelper::makePropertyValue(getPropertyName(PROP_OPAQUE), false), comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), text::SizeType::MIN), - comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN) + comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN), + comphelper::makePropertyValue(u"FrameInteropGrabBag"_ustr, + aFrameGrabBag.getAsConstPropertyValueList()) }; fillEmptyFrameProperties(aFrameProperties, false); diff --git a/sw/source/writerfilter/dmapper/PropertyIds.cxx b/sw/source/writerfilter/dmapper/PropertyIds.cxx index 0126632b4c0d..75de7b841df8 100644 --- a/sw/source/writerfilter/dmapper/PropertyIds.cxx +++ b/sw/source/writerfilter/dmapper/PropertyIds.cxx @@ -396,6 +396,7 @@ OUString getPropertyName( PropertyIds eId ) { PROP_DECORATIVE, u"Decorative"_ustr}, { PROP_PAPER_TRAY, u"PrinterPaperTray"_ustr}, { PROP_CHAR_FONT_FAMILY, u"CharFontFamily"_ustr}, + { PROP_INLINE_HEADING, u"ParaInlineHeading"_ustr}, }; auto iterator = constPropertyMap.find(eId); if (iterator != constPropertyMap.end()) diff --git a/sw/source/writerfilter/dmapper/PropertyIds.hxx b/sw/source/writerfilter/dmapper/PropertyIds.hxx index 72f3ffb149ab..ebc9cdaeede2 100644 --- a/sw/source/writerfilter/dmapper/PropertyIds.hxx +++ b/sw/source/writerfilter/dmapper/PropertyIds.hxx @@ -398,6 +398,7 @@ enum PropertyIds ,PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF ,PROP_PARA_CONNECT_BORDERS ,PROP_CHAR_FONT_FAMILY + ,PROP_INLINE_HEADING }; //Returns the UNO string equivalent to eId.