offapi/com/sun/star/text/ContentControl.idl                   |    6 
 sw/inc/formatcontentcontrol.hxx                               |    9 
 sw/inc/unoprnms.hxx                                           |    1 
 sw/qa/extras/ooxmlexport/ooxmlexport17.cxx                    |   33 ---
 sw/qa/extras/ooxmlexport/ooxmlexport18.cxx                    |    5 
 sw/qa/extras/ooxmlexport/ooxmlexport3.cxx                     |    3 
 sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx                 |    2 
 sw/source/core/txtnode/attrcontentcontrol.cxx                 |    3 
 sw/source/core/unocore/unocontentcontrol.cxx                  |   28 ++
 sw/source/core/unocore/unomap1.cxx                            |    1 
 sw/source/filter/ww8/docxattributeoutput.cxx                  |    6 
 writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx         |   30 ++
 writerfilter/qa/cppunittests/dmapper/data/sdt-block-text.docx |binary
 writerfilter/source/dmapper/DomainMapper.cxx                  |   30 ++
 writerfilter/source/dmapper/SdtHelper.cxx                     |  108 ++++++++--
 writerfilter/source/dmapper/SdtHelper.hxx                     |   13 -
 16 files changed, 220 insertions(+), 58 deletions(-)

New commits:
commit 2f60fa7ccdefd76b61e1c7b7cc27ae92824111da
Author:     Jaume Pujantell <jaume.pujant...@collabora.com>
AuthorDate: Wed Sep 13 08:58:21 2023 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Thu Oct 12 08:21:39 2023 +0200

    writerfilter: use content controls for text in block SDTs
    
    Text inside block SDTs was shown as Text Fields wich ignored properties 
such as
    alias and formatting. Now those texts are imported as content controls like 
in
    the case of run SDTs.
    
    Added the ability for content controls to remember and export the 
"multiline"
    property of block SDT text.
    
    Some existing tests have been changed due to some different export results.
    
    Change-Id: Ice1eb4ca6dd53c99d5abb239371f8ac896c3b6e4
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/156867
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins
    (cherry picked from commit f974779b18609c35e548c27118827af20e55306a)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/157787
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>

diff --git a/offapi/com/sun/star/text/ContentControl.idl 
b/offapi/com/sun/star/text/ContentControl.idl
index c2fe46757656..41f538415af9 100644
--- a/offapi/com/sun/star/text/ContentControl.idl
+++ b/offapi/com/sun/star/text/ContentControl.idl
@@ -147,6 +147,12 @@ service ContentControl
         @since LibreOffice 7.6
     */
     [optional, property] string Lock;
+
+    /** Indicates if the control accepts soft breaks.
+
+        @since LibreOffice 24.2
+    */
+    [optional, property] string MultiLine;
 };
 
 
diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx
index d0801f671cd3..9bad2bd23ff3 100644
--- a/sw/inc/formatcontentcontrol.hxx
+++ b/sw/inc/formatcontentcontrol.hxx
@@ -173,7 +173,7 @@ class SW_DLLPUBLIC SwContentControl : public 
sw::BroadcastingModify
     /// The appearance: just remembered.
     OUString m_aAppearance;
 
-    /// The alias: just remembered.
+    /// The alias.
     OUString m_aAlias;
 
     /// The tag: just remembered.
@@ -188,6 +188,9 @@ class SW_DLLPUBLIC SwContentControl : public 
sw::BroadcastingModify
     /// The control and content locks: mostly just remembered.
     OUString m_aLock;
 
+    /// The multiline property: just remembered.
+    OUString m_aMultiLine;
+
     /// Stores a list item index, in case the doc model is not yet updated.
     // i.e. temporarily store the selected item until the text is inserted by 
GotoContentControl.
     std::optional<size_t> m_oSelectedListItem;
@@ -389,6 +392,10 @@ public:
     // At the implementation level, define whether the user can directly 
modify the contents.
     bool GetReadWrite() const { return m_bReadWrite; }
 
+    void SetMultiLine(const OUString& rMultiline) { m_aMultiLine = rMultiline; 
}
+
+    const OUString& GetMultiLine() const { return m_aMultiLine; }
+
     SwContentControlType GetType() const;
 };
 
diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index d72883efdb9f..9d484371ed5f 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -942,6 +942,7 @@ inline constexpr OUStringLiteral UNO_NAME_TAG = u"Tag";
 inline constexpr OUStringLiteral UNO_NAME_ID = u"Id";
 inline constexpr OUStringLiteral UNO_NAME_TAB_INDEX = u"TabIndex";
 inline constexpr OUStringLiteral UNO_NAME_LOCK = u"Lock";
+inline constexpr OUStringLiteral UNO_NAME_MULTILINE = u"MultiLine";
 inline constexpr OUStringLiteral UNO_NAME_DATE_STRING = u"DateString";
 inline constexpr OUStringLiteral UNO_NAME_PARA_ID = u"ParaId";
 inline constexpr OUStringLiteral UNO_NAME_PARA_ID_PARENT = u"ParaIdParent";
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
index 0c3ff64e52f7..c8d37ee0e1c7 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -741,28 +741,12 @@ DECLARE_OOXMLEXPORT_TEST(testTdf123642_BookmarkAtDocEnd, 
"tdf123642.docx")
 
 DECLARE_OOXMLEXPORT_TEST(testTdf148361, "tdf148361.docx")
 {
-    if (isExported())
-    {
-        // 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<text::XTextField> xTextField1(xFields->nextElement(), 
uno::UNO_QUERY);
-        CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), 
xTextField1->getPresentation(false));
-    }
+    // Plain text Block SDT is imported as content control
+    OUString aActual = getParagraph(1)->getString();
+    // This was "itadmin".
+    CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), aActual);
 
-    OUString aActual = getParagraph(2)->getString();
+    aActual = getParagraph(2)->getString();
     // This was "itadmin".
     CPPUNIT_ASSERT_EQUAL(OUString("[Type text]"), aActual);
 }
@@ -994,12 +978,7 @@ DECLARE_OOXMLEXPORT_TEST(testTdf81507, "tdf81507.docx")
 
     // Ensure that we have <w:text/>
     assertXPath(pXmlDoc, "/w:document/w:body/w:p[3]/w:sdt/w:sdtPr/w:text");
-
-    // Ensure that we have no <w:text/> (not quite correct case, but to ensure 
import/export are okay)
-    xmlXPathObjectPtr pXmlObj = getXPathNode(pXmlDoc, 
"/w:document/w:body/w:p[4]/w:sdt/w:sdtPr/w:text");
-    CPPUNIT_ASSERT_EQUAL(sal_Int32(0),
-                           
static_cast<sal_Int32>(xmlXPathNodeSetGetLength(pXmlObj->nodesetval)));
-    xmlXPathFreeObject(pXmlObj);
+    assertXPath(pXmlDoc, "/w:document/w:body/w:p[4]/w:sdt/w:sdtPr/w:text");
 }
 
 DECLARE_OOXMLEXPORT_TEST(testTdf139948, "tdf139948.docx")
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
index 856efb375d11..f9f71f77f2f9 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
@@ -670,7 +670,10 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf157136)
     {
         // 1st paragraph - block content control
         auto xRun = getRun(getParagraph(1), 1);
-        CPPUNIT_ASSERT_EQUAL(OUString("Click or tap here to enter text.\r"), 
xRun->getString());
+        auto xContentControl
+            = getProperty<css::uno::Reference<css::text::XTextRange>>(xRun, 
"ContentControl");
+        CPPUNIT_ASSERT_EQUAL(OUString("Click or tap here to enter text."),
+                             xContentControl->getString());
         // Without the fix in place, this would fail with
         // - Expected: Placeholder Text
         // - Actual  :
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport3.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport3.cxx
index db2abe3513a7..e1faa4aea678 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport3.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport3.cxx
@@ -1003,7 +1003,8 @@ CPPUNIT_TEST_FIXTURE(Test, testGlossaryWithEmail)
 
     // preserve the ShowingPlaceholder setting on both block SDTs.
     pXmlDoc = parseExport("word/document.xml");
-    
assertXPath(pXmlDoc,"/w:document/w:body/w:p/w:sdt/w:sdtPr/w:showingPlcHdr", 2);
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:p/w:sdt/w:sdtPr/w:showingPlcHdr", 1);
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:p/w:hyperlink/w:sdt/w:sdtPr/w:showingPlcHdr", 1);
 }
 
 DECLARE_OOXMLEXPORT_TEST(testFdo71785, "fdo71785.docx")
diff --git a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
index 2c542e87309f..5e992a6ca1d4 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
@@ -636,7 +636,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtCompanyMultipara)
     // Here is just a simple text node, so there should be either one or zero 
paragraph
     // (in this case sdt element is inside paragraph)
     assertXPath(pXmlDoc, "//w:sdtContent/w:p", 0);
-    assertXPath(pXmlDoc, "//w:sdtContent/w:r", 1);
+    assertXPath(pXmlDoc, "//w:sdtContent/w:r", 2);
 }
 
 DECLARE_OOXMLEXPORT_TEST(testFixedDateFields, "fixed-date-field.docx")
diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx 
b/sw/source/core/txtnode/attrcontentcontrol.cxx
index c7d04664d38e..65f604ab62ec 100644
--- a/sw/source/core/txtnode/attrcontentcontrol.cxx
+++ b/sw/source/core/txtnode/attrcontentcontrol.cxx
@@ -219,6 +219,7 @@ SwContentControl::SwContentControl(SwFormatContentControl* 
pFormat)
     SetId(pOther->m_nId);
     SetTabIndex(pOther->m_nTabIndex);
     SetLock(pOther->m_aLock);
+    SetMultiLine(pOther->m_aMultiLine);
 }
 
 SwContentControl::~SwContentControl() {}
@@ -604,6 +605,8 @@ void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) 
const
                                       
BAD_CAST(OString::number(m_nTabIndex).getStr()));
     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("lock"),
                                       BAD_CAST(m_aLock.toUtf8().getStr()));
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("multiline"),
+                                      
BAD_CAST(m_aMultiLine.toUtf8().getStr()));
 
     if (!m_aListItems.empty())
     {
diff --git a/sw/source/core/unocore/unocontentcontrol.cxx 
b/sw/source/core/unocore/unocontentcontrol.cxx
index b8546a0a6e28..3d9de19bcc2d 100644
--- a/sw/source/core/unocore/unocontentcontrol.cxx
+++ b/sw/source/core/unocore/unocontentcontrol.cxx
@@ -182,6 +182,7 @@ public:
     sal_Int32 m_nId;
     sal_uInt32 m_nTabIndex;
     OUString m_aLock;
+    OUString m_aMultiLine;
 
     Impl(SwXContentControl& rThis, SwDoc& rDoc, SwContentControl* 
pContentControl,
          uno::Reference<text::XText> xParentText, std::unique_ptr<const 
TextRangeList_t> pPortions)
@@ -500,6 +501,7 @@ void SwXContentControl::AttachImpl(const 
uno::Reference<text::XTextRange>& xText
     pContentControl->SetId(m_pImpl->m_nId);
     pContentControl->SetTabIndex(m_pImpl->m_nTabIndex);
     pContentControl->SetLock(m_pImpl->m_aLock);
+    pContentControl->SetMultiLine(m_pImpl->m_aMultiLine);
 
     SwFormatContentControl aContentControl(pContentControl, nWhich);
     bool bSuccess
@@ -1035,6 +1037,21 @@ void SAL_CALL SwXContentControl::setPropertyValue(const 
OUString& rPropertyName,
             }
         }
     }
+    else if (rPropertyName == UNO_NAME_MULTILINE)
+    {
+        OUString aValue;
+        if (rValue >>= aValue)
+        {
+            if (m_pImpl->m_bIsDescriptor)
+            {
+                m_pImpl->m_aMultiLine = aValue;
+            }
+            else
+            {
+                m_pImpl->m_pContentControl->SetMultiLine(aValue);
+            }
+        }
+    }
     else
     {
         throw beans::UnknownPropertyException();
@@ -1330,6 +1347,17 @@ uno::Any SAL_CALL 
SwXContentControl::getPropertyValue(const OUString& rPropertyN
             aRet <<= m_pImpl->m_pContentControl->GetLock();
         }
     }
+    else if (rPropertyName == UNO_NAME_MULTILINE)
+    {
+        if (m_pImpl->m_bIsDescriptor)
+        {
+            aRet <<= m_pImpl->m_aMultiLine;
+        }
+        else
+        {
+            aRet <<= m_pImpl->m_pContentControl->GetMultiLine();
+        }
+    }
     else
     {
         throw beans::UnknownPropertyException();
diff --git a/sw/source/core/unocore/unomap1.cxx 
b/sw/source/core/unocore/unomap1.cxx
index 67112cd7a003..9ea90a1a3bc4 100644
--- a/sw/source/core/unocore/unomap1.cxx
+++ b/sw/source/core/unocore/unomap1.cxx
@@ -1053,6 +1053,7 @@ o3tl::span<const SfxItemPropertyMapEntry> 
SwUnoPropertyMapProvider::GetContentCo
         { UNO_NAME_ID, 0, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0 },
         { UNO_NAME_TAB_INDEX, 0, cppu::UnoType<sal_uInt32>::get(), 
PROPERTY_NONE, 0 },
         { UNO_NAME_LOCK, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 },
+        { UNO_NAME_MULTILINE, 0, cppu::UnoType<OUString>::get(), 
PROPERTY_NONE, 0 },
         { UNO_NAME_DATE_STRING, 0, cppu::UnoType<OUString>::get(), 
PropertyAttribute::READONLY, 0 },
     };
 
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index f557065eb739..a920684b4a71 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -2679,7 +2679,11 @@ void DocxAttributeOutput::WriteContentControlStart()
         m_pSerializer->endElementNS(XML_w, XML_date);
     }
 
-    if (m_pContentControl->GetPlainText())
+    if (!m_pContentControl->GetMultiLine().isEmpty())
+    {
+        m_pSerializer->singleElementNS(XML_w, XML_text, FSNS(XML_w, 
XML_multiLine), m_pContentControl->GetMultiLine());
+    }
+    else if (m_pContentControl->GetPlainText())
     {
         m_pSerializer->singleElementNS(XML_w, XML_text);
     }
diff --git a/writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx 
b/writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx
index 97f856044d6c..7cafcd19f280 100644
--- a/writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx
+++ b/writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx
@@ -119,6 +119,36 @@ CPPUNIT_TEST_FIXTURE(Test, testFloattableThenTable)
     // Make sure the anchor text is the body text, not some cell.
     CPPUNIT_ASSERT_EQUAL(xBodyText, xAnchor->getText());
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testSdtBlockText)
+{
+    // Given a document with a block SDT that only contains text:
+    loadFromURL(u"sdt-block-text.docx");
+
+    // Then make sure that the text inside the SDT is imported as a content 
control:
+    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, 
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);
+
+    // Make sure the properties are imported
+    uno::Reference<text::XTextContent> xContentControl;
+    xPortion->getPropertyValue("ContentControl") >>= xContentControl;
+    uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, 
uno::UNO_QUERY);
+    OUString aAlias;
+    xContentControlProps->getPropertyValue("Alias") >>= aAlias;
+    CPPUNIT_ASSERT_EQUAL(OUString("myalias"), aAlias);
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/writerfilter/qa/cppunittests/dmapper/data/sdt-block-text.docx 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-block-text.docx
new file mode 100644
index 000000000000..2dfbc4a3284a
Binary files /dev/null and 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-block-text.docx differ
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx 
b/writerfilter/source/dmapper/DomainMapper.cxx
index 8a024228ff88..0c5f5d855261 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -1132,7 +1132,7 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
             }
             if (nName == NS_ooxml::LN_CT_SdtRun_sdtContent)
             {
-                if (m_pImpl->GetSdtStarts().empty() && 
!m_pImpl->m_pSdtHelper->getSdtTexts().isEmpty())
+                if (m_pImpl->GetSdtStarts().empty() && 
m_pImpl->m_pSdtHelper->hasUnusedText())
                 {
                     // A non-inline SDT is already started, first convert that 
to a field and only
                     // then map the inline SDT to a content control.
@@ -1190,6 +1190,9 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
                 m_pImpl->PushSdt();
                 break;
             }
+            if (m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::plainText
+                && GetCurrentTextRange().is())
+                
m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
             m_pImpl->SetSdt(true);
         }
         break;
@@ -2968,7 +2971,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
     {
         m_pImpl->m_pSdtHelper->setControlType(SdtControlType::datePicker);
         resolveSprmProps(*this, rSprm);
-        
m_pImpl->m_pSdtHelper->setDateFieldStartRange(GetCurrentTextRange()->getEnd());
+        
m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
     }
     break;
     case NS_ooxml::LN_CT_SdtDate_dateFormat:
@@ -4108,13 +4111,13 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, 
size_t len)
     }
     else if (m_pImpl->m_pSdtHelper->GetSdtType() != 
NS_ooxml::LN_CT_SdtRun_sdtContent && m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::plainText)
     {
-        m_pImpl->m_pSdtHelper->getSdtTexts().append(sText);
         if (bNewLine)
         {
             m_pImpl->m_pSdtHelper->createPlainTextControl();
             finishParagraph();
+            
m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
+            return;
         }
-        return;
     }
     else if (!m_pImpl->m_pSdtHelper->isInteropGrabBagEmpty())
     {
@@ -4366,6 +4369,10 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, 
size_t len)
                 m_pImpl->clearDeferredBreaks();
             }
 
+            bool bSdtBlockUnusedText
+                = m_pImpl->m_pSdtHelper->GetSdtType() != 
NS_ooxml::LN_CT_SdtRun_sdtContent
+                  && m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::plainText
+                  && m_pImpl->m_pSdtHelper->hasUnusedText();
             if (pContext && pContext->GetFootnote().is())
             {
                 pContext->GetFootnote()->setLabel( sText );
@@ -4375,18 +4382,33 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, 
size_t len)
             }
             else if (m_pImpl->IsOpenFieldCommand() && 
!m_pImpl->IsForceGenericFields())
             {
+                if (bSdtBlockUnusedText)
+                    m_pImpl->m_pSdtHelper->createPlainTextControl();
                 m_pImpl->AppendFieldCommand(sText);
+                if (bSdtBlockUnusedText)
+                    
m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
             }
             else if( m_pImpl->IsOpenField() && 
m_pImpl->IsFieldResultAsString())
+            {
+                if (bSdtBlockUnusedText)
+                    m_pImpl->m_pSdtHelper->createPlainTextControl();
                 /*depending on the success of the field insert operation this 
result will be
                   set at the field or directly inserted into the text*/
                 m_pImpl->AppendFieldResult(sText);
+                if (bSdtBlockUnusedText)
+                    
m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
+            }
             else
             {
                 if (pContext == nullptr)
                     pContext = new PropertyMap();
 
                 m_pImpl->appendTextPortion( sText, pContext );
+
+                if (m_pImpl->m_pSdtHelper->GetSdtType() == 
NS_ooxml::LN_CT_SdtBlock_sdtContent
+                    && m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::plainText
+                    && !sText.isEmpty())
+                    m_pImpl->m_pSdtHelper->setHasUnusedText(true);
             }
 
         }
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx 
b/writerfilter/source/dmapper/SdtHelper.cxx
index 871708bc37c9..d56ea2f4873b 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -82,6 +82,7 @@ SdtHelper::SdtHelper(DomainMapper_Impl& rDM_Impl,
     : m_rDM_Impl(rDM_Impl)
     , m_xComponentContext(std::move(xContext))
     , m_aControlType(SdtControlType::unknown)
+    , m_bHasUnusedText(false)
     , m_bHasElements(false)
     , m_bOutsideAParagraph(false)
     , m_bPropertiesXMLsLoaded(false)
@@ -333,31 +334,101 @@ void SdtHelper::createPlainTextControl()
 {
     assert(getControlType() == SdtControlType::plainText);
 
-    OUString aDefaultText = m_aSdtTexts.makeStringAndClear();
+    if (!m_xFieldStartRange.is())
+        return;
 
-    // create field
-    uno::Reference<css::text::XTextField> xControlModel(
-        
m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.Input"),
-        uno::UNO_QUERY);
+    uno::Reference<text::XTextCursor> xCrsr;
+    uno::Reference<text::XText> xText;
+    if (m_rDM_Impl.HasTopText())
+    {
+        uno::Reference<text::XTextAppend> xTextAppend = 
m_rDM_Impl.GetTopTextAppend();
+        if (xTextAppend.is())
+        {
+            xText = m_rDM_Impl.GetTopTextAppend()->getEnd()->getText();
+            xCrsr = xText->createTextCursorByRange(m_xFieldStartRange);
+        }
+    }
+    if (!xCrsr.is())
+        return;
 
-    // set properties
-    uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, 
uno::UNO_QUERY);
+    try
+    {
+        bool bIsInTable = (m_rDM_Impl.hasTableManager() && 
m_rDM_Impl.getTableManager().isInTable())
+                              != (m_rDM_Impl.m_nTableDepth > 0)
+                          && m_rDM_Impl.GetIsDummyParaAddedForTableInSection();
+        if (bIsInTable)
+            xCrsr->goRight(1, false);
+        xCrsr->gotoEnd(true);
+    }
+    catch (uno::Exception&)
+    {
+        OSL_ENSURE(false, "Cannot get the right text range for text control");
+        return;
+    }
 
     std::optional<OUString> oData = getValueFromDataBinding();
     if (oData.has_value())
-        aDefaultText = *oData;
+        xCrsr->setString(*oData);
+
+    uno::Reference<text::XTextContent> xContentControl(
+        
m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.ContentControl"),
+        uno::UNO_QUERY);
+    uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, 
uno::UNO_QUERY);
 
-    xPropertySet->setPropertyValue("Content", uno::Any(aDefaultText));
+    for (const beans::PropertyValue& prop : getInteropGrabBagAndClear())
+    {
+        OUString sPropertyName;
+        if (prop.Name == "ooxml:CT_SdtPr_showingPlcHdr")
+            sPropertyName = "ShowingPlaceHolder";
+        else if (prop.Name == "ooxml:CT_SdtPr_alias")
+            sPropertyName = "Alias";
+        else if (prop.Name == "ooxml:CT_SdtPr_tag")
+            sPropertyName = "Tag";
+        else if (prop.Name == "ooxml:CT_SdtPr_id")
+            sPropertyName = "Id";
+        else if (prop.Name == "ooxml:CT_SdtPr_tabIndex")
+            sPropertyName = "TabIndex";
+        else if (prop.Name == "ooxml:CT_SdtPr_lock")
+            sPropertyName = "Lock";
+        else if (prop.Name == "ooxml:CT_SdtPlaceholder_docPart"
+                 || prop.Name == "ooxml:CT_SdtPr_dataBinding" || prop.Name == 
"ooxml:CT_SdtPr_color"
+                 || prop.Name == "ooxml:CT_SdtPr_appearance" || prop.Name == 
"ooxml:CT_SdtPr_text")
+        {
+            uno::Sequence<beans::PropertyValue> aInternalGrabBag;
+            prop.Value >>= aInternalGrabBag;
+            for (const beans::PropertyValue& internalProp : aInternalGrabBag)
+            {
+                if (internalProp.Name == "ooxml:CT_SdtPlaceholder_docPart_val")
+                    sPropertyName = "PlaceholderDocPart";
+                else if (internalProp.Name == 
"ooxml:CT_DataBinding_prefixMappings")
+                    sPropertyName = "DataBindingPrefixMappings";
+                else if (internalProp.Name == "ooxml:CT_DataBinding_xpath")
+                    sPropertyName = "DataBindingXpath";
+                else if (internalProp.Name == 
"ooxml:CT_DataBinding_storeItemID")
+                    sPropertyName = "DataBindingStoreItemID";
+                else if (internalProp.Name == "ooxml:CT_SdtAppearance_val")
+                    sPropertyName = "Appearance";
+                else if (internalProp.Name == "ooxml:CT_SdtColor_val")
+                    sPropertyName = "Color";
+                else if (internalProp.Name == "ooxml:CT_SdtText_multiLine")
+                    sPropertyName = "MultiLine";
+                if (!sPropertyName.isEmpty())
+                {
+                    xContentControlProps->setPropertyValue(sPropertyName, 
internalProp.Value);
+                }
+                sPropertyName.clear();
+            }
+        }
 
-    PropertyMap aMap;
-    aMap.InsertProps(m_rDM_Impl.GetTopContext());
+        if (!sPropertyName.isEmpty())
+        {
+            xContentControlProps->setPropertyValue(sPropertyName, prop.Value);
+        }
+    }
 
-    // add it into document
-    m_rDM_Impl.appendTextContent(xControlModel, aMap.GetPropertyValues());
+    xContentControlProps->setPropertyValue("PlainText", uno::Any(true));
 
-    // Store all unused sdt parameters from grabbag
-    xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG,
-                                   uno::Any(getInteropGrabBagAndClear()));
+    xText->insertTextContent(xCrsr, xContentControl, /*bAbsorb=*/true);
 
     // clean up
     clear();
@@ -365,7 +436,7 @@ void SdtHelper::createPlainTextControl()
 
 void SdtHelper::createDateContentControl()
 {
-    if (!m_xDateFieldStartRange.is())
+    if (!m_xFieldStartRange.is())
         return;
 
     uno::Reference<text::XTextCursor> xCrsr;
@@ -382,7 +453,7 @@ void SdtHelper::createDateContentControl()
 
     try
     {
-        xCrsr->gotoRange(m_xDateFieldStartRange, false);
+        xCrsr->gotoRange(m_xFieldStartRange, false);
         // tdf#138093: Date selector reset, if placed inside table
         // Modified to XOR relationship and adding dummy paragraph conditions
         bool bIsInTable = (m_rDM_Impl.hasTableManager() && 
m_rDM_Impl.getTableManager().isInTable())
@@ -518,6 +589,7 @@ void SdtHelper::clear()
     m_sDataBindingXPath.clear();
     m_sDataBindingStoreItemID.clear();
     m_aGrabBag.clear();
+    m_bHasUnusedText = false;
     m_bShowingPlcHdr = false;
     m_bChecked = false;
     m_aCheckedState.clear();
diff --git a/writerfilter/source/dmapper/SdtHelper.hxx 
b/writerfilter/source/dmapper/SdtHelper.hxx
index 6803db16ef06..5db799bd1fd2 100644
--- a/writerfilter/source/dmapper/SdtHelper.hxx
+++ b/writerfilter/source/dmapper/SdtHelper.hxx
@@ -83,12 +83,14 @@ class SdtHelper final : public virtual SvRefBase
     /// <w:dataBinding w:storeItemID="">
     OUString m_sDataBindingStoreItemID;
 
-    /// Start range of the date field
-    css::uno::Reference<css::text::XTextRange> m_xDateFieldStartRange;
+    /// Start range of the date or plain text field
+    css::uno::Reference<css::text::XTextRange> m_xFieldStartRange;
     /// Locale string as it comes from the ooxml document.
     OUStringBuffer m_sLocale;
     /// Grab bag to store unsupported SDTs, aiming to save them back on export.
     std::vector<css::beans::PropertyValue> m_aGrabBag;
+    /// Has inserted texts for plain text control
+    bool m_bHasUnusedText;
 
     bool m_bHasElements;
     /// The last stored SDT element is outside paragraphs.
@@ -169,9 +171,9 @@ public:
     void setDataBindingStoreItemID(const OUString& sValue) { 
m_sDataBindingStoreItemID = sValue; }
     const OUString& GetDataBindingStoreItemID() const { return 
m_sDataBindingStoreItemID; }
 
-    void setDateFieldStartRange(const 
css::uno::Reference<css::text::XTextRange>& xStartRange)
+    void setFieldStartRange(const css::uno::Reference<css::text::XTextRange>& 
xStartRange)
     {
-        m_xDateFieldStartRange = xStartRange;
+        m_xFieldStartRange = xStartRange;
     }
 
     OUStringBuffer& getLocale() { return m_sLocale; }
@@ -204,6 +206,9 @@ public:
     bool containedInInteropGrabBag(const OUString& rValueName);
     sal_Int32 getInteropGrabBagSize() const;
 
+    void setHasUnusedText(bool bHasUnusedText) { m_bHasUnusedText = 
bHasUnusedText; }
+    bool hasUnusedText() const { return m_bHasUnusedText; }
+
     void SetShowingPlcHdr();
     bool GetShowingPlcHdr() const;
 

Reply via email to