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.

Reply via email to