sw/CppunitTest_sw_filter_ww8.mk                                   |   75 
++++++++++
 sw/Module_sw.mk                                                   |    1 
 sw/qa/extras/ooxmlexport/ooxmlexport17.cxx                        |   25 ++-
 sw/qa/extras/ooxmlexport/ooxmlexport5.cxx                         |    2 
 sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx                     |    6 
 sw/qa/filter/ww8/ww8.cxx                                          |   63 
++++++++
 sw/source/filter/ww8/attributeoutputbase.hxx                      |    2 
 sw/source/filter/ww8/docxattributeoutput.cxx                      |   21 ++
 sw/source/filter/ww8/docxattributeoutput.hxx                      |    2 
 sw/source/filter/ww8/wrtw8nds.cxx                                 |    2 
 writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx                |   30 ++++
 writerfilter/qa/cppunittests/dmapper/data/sdt-run-plain-text.docx |binary
 writerfilter/source/dmapper/DomainMapper.cxx                      |   20 ++
 writerfilter/source/dmapper/DomainMapper_Impl.cxx                 |    5 
 writerfilter/source/dmapper/SdtHelper.cxx                         |    1 
 writerfilter/source/dmapper/SdtHelper.hxx                         |    4 
 16 files changed, 243 insertions(+), 16 deletions(-)

New commits:
commit 8c0ab5555c373a2aeb44b71d04ae80c67e704ec8
Author:     Miklos Vajna <[email protected]>
AuthorDate: Mon Jul 25 09:13:03 2022 +0200
Commit:     Miklos Vajna <[email protected]>
CommitDate: Wed Jul 27 13:38:30 2022 +0200

    sw content controls, plain text: add DOCX import
    
    - the core of this is the writerfilter/ change to call PopSdt() for
      SdtControlType::plainText, which maps inline plain text SDTs to Writer 
content
      controls, not to input fields
    
    - disable the grab-bag in this case, otherwise we would run duplicated 
<w:sdt>
      elements on export
    
    - fix CppunitTest_sw_ooxmlexport7's testSdtAndShapeOverlapping by postponing
      the SDT start in DocxAttributeOutput::WriteContentControlStart() in case a
      shape is anchored at the same position as the SDT start: if the shape 
should
      start inside the content control, then it should be anchored after the 
dummy
      character
    
    - reduce the debug output in VMLExport::Commit(), which could write control
      characters to the terminal on test failure, potentially breaking it 
(requiring
      a 'reset' to recover)
    
    - fix CppunitTest_sw_ooxmlexport5's testSdt2Run: now we merge two runs 
inside a
      plain text content control into a single one, and there is no problem with
      that, so adapt the test instead
    
    - fix CppunitTest_sw_ooxmlexport17's testTdf148361: plain text inline SDT is
      now a content control, not a field
    
    - fix CppunitTest_sw_ooxmlfieldexport's testfdo82492: explicitly assert that
      there is 1 text run inside the SDT and there is a shape after it 
(outside).
      Also extend DocxAttributeOutput::EndContentControl(), so it ends the 
content
      control at the correct, earlier position in case it's followed by an 
as-char
      shape
    
    - fix CppunitTest_sw_ooxmlfieldexport's testfdo82123: again assert that the 
SDT
      has 1 run with text, and there is a drawing after the SDT
    
    - fix CppunitTest_sw_ooxmlfieldexport's testTdf104823: this revealed that 
some
      more complex logic is needed to support data bindings, so exclude
      text-with-databinding from the scope of this commit and continue to map 
those
      to input fields for now
    
    - fix CppunitTest_sw_ooxmlfieldexport's testFdo81945: this had a similar
      problem as as-char shapes, but this time a new SDT is starting right 
after a
      previous SDT. Adapt DocxAttributeOutput::EndContentControl() accordingly,
      though perhaps this should be generalized later, so we always close SDTs 
in the
      previous run, unless this is the last run, or something similar
    
    (cherry picked from commit 9700c1b2170ad04453a361ed5647937833ac3c18)
    
    Conflicts:
            oox/source/export/vmlexport.cxx
    
    Change-Id: Ifaf581be884a683de6c8b932008a03ba43734b75
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137494
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
index 9f8c4c0b5c5d..2414c1b2ad38 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -397,15 +397,26 @@ DECLARE_OOXMLEXPORT_TEST(testTdf123642_BookmarkAtDocEnd, 
"tdf123642.docx")
 
 DECLARE_OOXMLEXPORT_TEST(testTdf148361, "tdf148361.docx")
 {
-    // Refresh fields and ensure cross-reference to numbered para is okay
-    uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(mxComponent, 
uno::UNO_QUERY);
-    uno::Reference<container::XEnumerationAccess> 
xFieldsAccess(xTextFieldsSupplier->getTextFields());
+    if (mbExported)
+    {
+        // Block SDT is turned into run SDT on export, so the next import will 
have this as content
+        // control, not as a field.
+        OUString aActual = getParagraph(1)->getString();
+        // This was "itadmin".
+        CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), aActual);
+    }
+    else
+    {
+        // Refresh fields and ensure cross-reference to numbered para is okay
+        uno::Reference<text::XTextFieldsSupplier> 
xTextFieldsSupplier(mxComponent, uno::UNO_QUERY);
+        uno::Reference<container::XEnumerationAccess> 
xFieldsAccess(xTextFieldsSupplier->getTextFields());
 
-    uno::Reference<container::XEnumeration> 
xFields(xFieldsAccess->createEnumeration());
-    CPPUNIT_ASSERT(xFields->hasMoreElements());
+        uno::Reference<container::XEnumeration> 
xFields(xFieldsAccess->createEnumeration());
+        CPPUNIT_ASSERT(xFields->hasMoreElements());
 
-    uno::Reference<text::XTextField> xTextField1(xFields->nextElement(), 
uno::UNO_QUERY);
-    CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), 
xTextField1->getPresentation(false));
+        uno::Reference<text::XTextField> xTextField1(xFields->nextElement(), 
uno::UNO_QUERY);
+        CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), 
xTextField1->getPresentation(false));
+    }
 
     OUString aActual = getParagraph(2)->getString();
     // This was "itadmin".
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx
index 17fe49c8f8aa..04c9d7e7d927 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx
@@ -1105,7 +1105,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSdt2Run)
     xmlDocUniquePtr pXmlDoc = parseExport();
 
     // The problem was that <w:sdt> was closed after "first", not after 
"second", so the second assert failed.
-    assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtContent/w:r", 1);
+    assertXPathContent(pXmlDoc, 
"/w:document/w:body/w:p/w:sdt/w:sdtContent/w:r/w:t", "firstsecond");
     // Make sure the third portion is still outside <w:sdt>.
     assertXPathContent(pXmlDoc, "/w:document/w:body/w:p[1]/w:r/w:t", "third");
 }
diff --git a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
index ea07a681343c..98a5b6ab6a15 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
@@ -541,7 +541,8 @@ DECLARE_OOXMLEXPORT_EXPORTONLY_TEST(testfdo82123, 
"fdo82123.docx")
     xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
 
     // make sure there is only one run inside first SDT after RT as in the 
Original file.
-    assertXPath(pXmlDoc, 
"/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:sdt[1]/w:sdtContent/w:r",1);
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:sdt[1]/w:sdtContent/w:r/w:t", 1);
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:r/w:drawing", 1);
 }
 
 DECLARE_OOXMLEXPORT_EXPORTONLY_TEST(testSdtBeforeField, 
"sdt-before-field.docx")
@@ -563,7 +564,8 @@ DECLARE_OOXMLEXPORT_EXPORTONLY_TEST(testfdo82492, 
"fdo82492.docx")
     xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
 
     // make sure there is only one run inside first SDT after RT as in the 
Original file.
-    assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt[1]/w:sdtContent/w:r",1);
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:p/w:sdt[1]/w:sdtContent/w:r/w:t", 1);
+    assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:r/mc:AlternateContent", 1);
 }
 
 DECLARE_OOXMLEXPORT_EXPORTONLY_TEST(testSdtHeader, "sdt-header.docx")
diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx 
b/sw/source/filter/ww8/attributeoutputbase.hxx
index a9331c9b628b..35ac50057786 100644
--- a/sw/source/filter/ww8/attributeoutputbase.hxx
+++ b/sw/source/filter/ww8/attributeoutputbase.hxx
@@ -373,7 +373,7 @@ public:
     virtual void StartContentControl(const SwFormatContentControl& 
/*rFormatContentControl*/) {}
 
     /// Output content control end.
-    virtual void EndContentControl() {}
+    virtual void EndContentControl( const SwTextNode& /*rNode*/, sal_Int32 
/*nPos*/ ) {}
 
 protected:
 
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index e0cd2b1318a6..84189eaf5e81 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -368,9 +368,16 @@ void DocxAttributeOutput::StartContentControl(const 
SwFormatContentControl& rFor
     m_pContentControl = rFormatContentControl.GetContentControl();
 }
 
-void DocxAttributeOutput::EndContentControl()
+void DocxAttributeOutput::EndContentControl(const SwTextNode& rNode, sal_Int32 
nPos)
 {
-    ++m_nCloseContentControlInThisRun;
+    if (rNode.GetTextAttrForCharAt(nPos, RES_TXTATR_FLYCNT) || 
rNode.GetTextAttrForCharAt(nPos, RES_TXTATR_CONTENTCONTROL))
+    {
+        ++m_nCloseContentControlInPreviousRun;
+    }
+    else
+    {
+        ++m_nCloseContentControlInThisRun;
+    }
 }
 
 static void checkAndWriteFloatingTables(DocxAttributeOutput& 
rDocxAttributeOutput)
@@ -2336,6 +2343,11 @@ void DocxAttributeOutput::WriteContentControlStart()
         return;
     }
 
+    if (m_bAnchorLinkedToNode)
+    {
+        return;
+    }
+
     m_pSerializer->startElementNS(XML_w, XML_sdt);
     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
     if (!m_pContentControl->GetPlaceholderDocPart().isEmpty())
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index a264b879fd9b..def5a955f363 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -409,7 +409,7 @@ public:
     void StartContentControl(const SwFormatContentControl& 
rFormatContentControl) override;
 
     /// See AttributeOutputBase::EndContentControl().
-    void EndContentControl() override;
+    void EndContentControl( const SwTextNode& rNode, sal_Int32 nPos ) override;
 
 private:
     /// Initialize the structures where we are going to collect some of the 
paragraph properties.
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx 
b/sw/source/filter/ww8/wrtw8nds.cxx
index 805ab17a7464..e980efa238b7 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -1416,7 +1416,7 @@ int SwWW8AttrIter::OutAttrWithRange(const SwTextNode& 
rNode, sal_Int32 nPos)
                     pEnd = pHt->End();
                     if (nPos == *pEnd && nPos != pHt->GetStart())
                     {
-                        m_rExport.AttrOutput().EndContentControl();
+                        m_rExport.AttrOutput().EndContentControl(rNode, nPos);
                         --nRet;
                     }
                     break;
diff --git a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx 
b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
index b2e7f1058f88..6b568619785e 100644
--- a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
+++ b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
@@ -88,6 +88,36 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtRunRichText)
     CPPUNIT_ASSERT_EQUAL(24.f, fCharheight);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testSdtRunPlainText)
+{
+    // Given a document with a plain text inline/run SDT:
+    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + 
"sdt-run-plain-text.docx";
+
+    // When loading the document:
+    getComponent() = loadFromDesktop(aURL);
+
+    // Then make sure that the text inside the SDT is not rich:
+    uno::Reference<text::XTextDocument> xTextDocument(getComponent(), 
uno::UNO_QUERY);
+    uno::Reference<container::XEnumerationAccess> 
xParaEnumAccess(xTextDocument->getText(),
+                                                                  
uno::UNO_QUERY);
+    uno::Reference<container::XEnumeration> xParaEnum = 
xParaEnumAccess->createEnumeration();
+    uno::Reference<container::XEnumerationAccess> 
xPara(xParaEnum->nextElement(), uno::UNO_QUERY);
+    uno::Reference<container::XEnumeration> xPortionEnum = 
xPara->createEnumeration();
+    uno::Reference<beans::XPropertySet> xPortion(xPortionEnum->nextElement(), 
uno::UNO_QUERY);
+    OUString aTextPortionType;
+    xPortion->getPropertyValue("TextPortionType") >>= aTextPortionType;
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: ContentControl
+    // - Actual  : TextField
+    // i.e. the SDT was imported as a text field, not as a content control.
+    CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aTextPortionType);
+    uno::Reference<beans::XPropertySet> xContentControl;
+    xPortion->getPropertyValue("ContentControl") >>= xContentControl;
+    bool bPlainText{};
+    xContentControl->getPropertyValue("PlainText") >>= bPlainText;
+    CPPUNIT_ASSERT(bPlainText);
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testSdtRunCheckbox)
 {
     // Given a document with a checkbox inline/run SDT:
diff --git a/writerfilter/qa/cppunittests/dmapper/data/sdt-run-plain-text.docx 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-plain-text.docx
new file mode 100644
index 000000000000..127d81fd966b
Binary files /dev/null and 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-plain-text.docx differ
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx 
b/writerfilter/source/dmapper/DomainMapper.cxx
index 33929cdddf1c..93e0b4f1bd0e 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -1070,6 +1070,7 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
         break;
         case NS_ooxml::LN_CT_SdtBlock_sdtContent:
         case NS_ooxml::LN_CT_SdtRun_sdtContent:
+            m_pImpl->m_pSdtHelper->SetSdtType(nName);
             if (m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::unknown)
             {
                 // Still not determined content type? and it is even not 
unsupported? Then it is plain text field
@@ -1110,6 +1111,15 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
                     default:
                         break;
                 }
+
+                if (m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::plainText)
+                {
+                    // The plain text && data binding case needs more work 
before it can be enabled.
+                    if 
(m_pImpl->m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty())
+                    {
+                        m_pImpl->PopSdt();
+                    }
+                }
             }
 
             m_pImpl->SetSdt(false);
@@ -2751,6 +2761,14 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
     case NS_ooxml::LN_CT_SdtPr_text:
     {
         m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText);
+        if (m_pImpl->m_pSdtHelper->GetSdtType() == 
NS_ooxml::LN_CT_SdtRun_sdtContent)
+        {
+            if (m_pImpl->m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty())
+            {
+                m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear();
+                break;
+            }
+        }
         enableInteropGrabBag("ooxml:CT_SdtPr_text");
         writerfilter::Reference<Properties>::Pointer_t pProperties = 
rSprm.getProps();
         if (pProperties)
@@ -3739,7 +3757,7 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, 
size_t len)
             return;
         }
     }
-    else if (m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::plainText)
+    else if ((m_pImpl->m_pSdtHelper->GetSdtType() != 
NS_ooxml::LN_CT_SdtRun_sdtContent || 
!m_pImpl->m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty()) && 
m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText)
     {
         m_pImpl->m_pSdtHelper->getSdtTexts().append(sText);
         if (bNewLine)
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index a1c280a9869a..0cf793f480b0 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -973,6 +973,11 @@ void DomainMapper_Impl::PopSdt()
                                                
uno::Any(m_pSdtHelper->getDate().makeStringAndClear()));
     }
 
+    if (m_pSdtHelper->getControlType() == SdtControlType::plainText)
+    {
+        xContentControlProps->setPropertyValue("PlainText", uno::Any(true));
+    }
+
     xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
 
     m_pSdtHelper->clear();
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx 
b/writerfilter/source/dmapper/SdtHelper.cxx
index a3bd08ec807a..013581a038c8 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -445,6 +445,7 @@ void SdtHelper::clear()
     m_aDropDownItems.clear();
     m_aDropDownDisplayTexts.clear();
     setControlType(SdtControlType::unknown);
+    m_nSdtType = 0;
     m_sDataBindingPrefixMapping.clear();
     m_sDataBindingXPath.clear();
     m_sDataBindingStoreItemID.clear();
diff --git a/writerfilter/source/dmapper/SdtHelper.hxx 
b/writerfilter/source/dmapper/SdtHelper.hxx
index 3d616067211e..a986da304df0 100644
--- a/writerfilter/source/dmapper/SdtHelper.hxx
+++ b/writerfilter/source/dmapper/SdtHelper.hxx
@@ -66,6 +66,7 @@ class SdtHelper final : public virtual SvRefBase
     std::vector<OUString> m_aDropDownDisplayTexts;
     /// Type of sdt control
     SdtControlType m_aControlType;
+    sal_uInt32 m_nSdtType = 0;
     /// Pieces of the default text -- currently used only by the dropdown 
control.
     OUStringBuffer m_aSdtTexts;
     /// Date ISO string contained in the w:date element, used by the date 
control.
@@ -169,6 +170,9 @@ public:
     SdtControlType getControlType() { return m_aControlType; }
     void setControlType(SdtControlType aType) { m_aControlType = aType; }
 
+    void SetSdtType(sal_uInt32 nSdtType) { m_nSdtType = nSdtType; }
+    sal_uInt32 GetSdtType() const { return m_nSdtType; }
+
     /// Create drop-down control from w:sdt's w:dropDownList.
     void createDropDownControl();
     /// Create date control from w:sdt's w:date.
commit 1fdfda440ae62ca40e757cf14af238d18b9b550a
Author:     Miklos Vajna <[email protected]>
AuthorDate: Mon Jul 25 08:10:07 2022 +0200
Commit:     Miklos Vajna <[email protected]>
CommitDate: Wed Jul 27 13:38:15 2022 +0200

    sw content controls, plain text: add DOCX export
    
    Map the PlainText UNO property to:
    
            <w:sdtPr>
              <w:text/>
            </w:sdtPr>
    
    (cherry picked from commit a6e5726f186bf9d2a0ea91169649504c7396c539)
    
    Conflicts:
            sw/qa/filter/ww8/ww8.cxx
    
    Change-Id: I57f365fcfb3a80acb74aa932432a8ae8f3acc92b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137493
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git a/sw/CppunitTest_sw_filter_ww8.mk b/sw/CppunitTest_sw_filter_ww8.mk
new file mode 100644
index 000000000000..0452776478df
--- /dev/null
+++ b/sw/CppunitTest_sw_filter_ww8.mk
@@ -0,0 +1,75 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,sw_filter_ww8))
+
+$(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_filter_ww8))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,sw_filter_ww8, \
+    sw/qa/filter/ww8/ww8 \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sw_filter_ww8, \
+    comphelper \
+    cppu \
+    cppuhelper \
+    editeng \
+    sal \
+    sfx \
+    svl \
+    svx \
+    svxcore \
+    sw \
+    swqahelper \
+    test \
+    unotest \
+    utl \
+    vcl \
+    tl \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,sw_filter_ww8,\
+    boost_headers \
+    libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_set_include,sw_filter_ww8,\
+    -I$(SRCDIR)/sw/inc \
+    -I$(SRCDIR)/sw/source/core/inc \
+    -I$(SRCDIR)/sw/source/uibase/inc \
+    -I$(SRCDIR)/sw/qa/inc \
+    $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_api,sw_filter_ww8,\
+       udkapi \
+       offapi \
+       oovbaapi \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,sw_filter_ww8))
+$(eval $(call gb_CppunitTest_use_vcl,sw_filter_ww8))
+
+$(eval $(call gb_CppunitTest_use_rdb,sw_filter_ww8,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,sw_filter_ww8,\
+    officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,sw_filter_ww8))
+
+$(eval $(call gb_CppunitTest_use_uiconfigs,sw_filter_ww8, \
+    modules/swriter \
+))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,sw_filter_ww8))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sw/Module_sw.mk b/sw/Module_sw.mk
index ce9a13066a96..e624630187b7 100644
--- a/sw/Module_sw.mk
+++ b/sw/Module_sw.mk
@@ -142,6 +142,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sw,\
     CppunitTest_sw_core_edit \
     CppunitTest_sw_uibase_fldui \
     CppunitTest_sw_core_attr \
+    CppunitTest_sw_filter_ww8 \
 ))
 
 ifneq ($(DISABLE_GUI),TRUE)
diff --git a/sw/qa/filter/ww8/ww8.cxx b/sw/qa/filter/ww8/ww8.cxx
new file mode 100644
index 000000000000..d98ff7dae151
--- /dev/null
+++ b/sw/qa/filter/ww8/ww8.cxx
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <swmodeltestbase.hxx>
+
+#include <com/sun/star/text/XTextDocument.hpp>
+
+namespace
+{
+/**
+ * Covers sw/source/filter/ww8/ fixes.
+ *
+ * Note that these tests are meant to be simple: either load a file and assert 
some result or build
+ * a document model with code, export and assert that result.
+ *
+ * Keep using the various sw_<format>import/export suites for multiple filter 
calls inside a single
+ * test.
+ */
+class Test : public SwModelTestBase
+{
+};
+
+CPPUNIT_TEST_FIXTURE(Test, testPlainTextContentControlExport)
+{
+    // Given a document with a plain text content control around a text 
portion:
+    mxComponent = loadFromDesktop("private:factory/swriter");
+    uno::Reference<lang::XMultiServiceFactory> xMSF(mxComponent, 
uno::UNO_QUERY);
+    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, 
uno::UNO_QUERY);
+    uno::Reference<text::XText> xText = xTextDocument->getText();
+    uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
+    xText->insertString(xCursor, "test", /*bAbsorb=*/false);
+    xCursor->gotoStart(/*bExpand=*/false);
+    xCursor->gotoEnd(/*bExpand=*/true);
+    uno::Reference<text::XTextContent> xContentControl(
+        xMSF->createInstance("com.sun.star.text.ContentControl"), 
uno::UNO_QUERY);
+    uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, 
uno::UNO_QUERY);
+    xContentControlProps->setPropertyValue("PlainText", uno::Any(true));
+    xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
+
+    // When exporting to DOCX:
+    save("Office Open XML Text", maTempFile);
+    mbExported = true;
+
+    // Then make sure the expected markup is used:
+    xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 1
+    // - Actual  : 0
+    // - XPath '//w:sdt/w:sdtPr/w:text' number of nodes is incorrect
+    // i.e. the plain text content control was turned into a rich text one on 
export.
+    assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:text", 1);
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index e74ea397c40a..e0cd2b1318a6 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -2428,6 +2428,11 @@ void DocxAttributeOutput::WriteContentControlStart()
         m_pSerializer->endElementNS(XML_w, XML_date);
     }
 
+    if (m_pContentControl->GetPlainText())
+    {
+        m_pSerializer->singleElementNS(XML_w, XML_text);
+    }
+
     m_pSerializer->endElementNS(XML_w, XML_sdtPr);
     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
     m_pContentControl = nullptr;

Reply via email to