offapi/com/sun/star/text/ContentControl.idl                   |    6 
 sw/inc/formatcontentcontrol.hxx                               |    9 
 sw/inc/unoprnms.hxx                                           |    1 
 sw/qa/extras/ooxmlexport/ooxmlexport17.cxx                    |   30 --
 sw/qa/extras/ooxmlexport/ooxmlexport19.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                     |  109 ++++++++--
 writerfilter/source/dmapper/SdtHelper.hxx                     |   13 -
 16 files changed, 221 insertions(+), 55 deletions(-)

New commits:
commit 5082d50d24c3fec4487c724a15eb0d54a82ecd0d
Author:     Jaume Pujantell <jaume.pujant...@collabora.com>
AuthorDate: Wed Sep 13 08:58:21 2023 +0200
Commit:     Jaume Pujantell <jaume.pujant...@collabora.com>
CommitDate: Wed Oct 11 15:19:58 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

diff --git a/offapi/com/sun/star/text/ContentControl.idl 
b/offapi/com/sun/star/text/ContentControl.idl
index 34beff3cb127..6f6aa80ca54d 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 190d0bd540fe..cffe326d0703 100644
--- a/sw/inc/formatcontentcontrol.hxx
+++ b/sw/inc/formatcontentcontrol.hxx
@@ -173,7 +173,7 @@ class SW_DLLPUBLIC SwContentControl final : 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 final : 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 6553153b459d..630028f0e7a6 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -941,6 +941,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 90c3c750c53c..6d3d0452740a 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -683,28 +683,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);
 }
@@ -936,9 +920,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)
-    assertXPath(pXmlDoc, "/w:document/w:body/w:p[4]/w:sdt/w:sdtPr/w:text", 0);
+    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/ooxmlexport19.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx
index 275891d4f58b..fbd3bdb4aaaf 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx
@@ -1108,7 +1108,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 ae1aa909d5fe..24b2ee333e49 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 c0578ad31a16..a7ffaa2ae7f5 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 bc62606f2d97..2dd007f480b3 100644
--- a/sw/source/core/txtnode/attrcontentcontrol.cxx
+++ b/sw/source/core/txtnode/attrcontentcontrol.cxx
@@ -220,6 +220,7 @@ SwContentControl::SwContentControl(SwFormatContentControl* 
pFormat)
     SetId(pOther->m_nId);
     SetTabIndex(pOther->m_nTabIndex);
     SetLock(pOther->m_aLock);
+    SetMultiLine(pOther->m_aMultiLine);
 }
 
 SwContentControl::~SwContentControl() {}
@@ -605,6 +606,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 541fc46699b3..8492e0fc94f7 100644
--- a/sw/source/core/unocore/unocontentcontrol.cxx
+++ b/sw/source/core/unocore/unocontentcontrol.cxx
@@ -176,6 +176,7 @@ public:
     sal_Int32 m_nId;
     sal_uInt32 m_nTabIndex;
     OUString m_aLock;
+    OUString m_aMultiLine;
 
     Impl(SwXContentControl& rThis, SwDoc& rDoc, SwContentControl* 
pContentControl,
          css::uno::Reference<SwXText> xParentText, std::unique_ptr<const 
TextRangeList_t> pPortions)
@@ -475,6 +476,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
@@ -1010,6 +1012,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();
@@ -1305,6 +1322,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 aae854fc24e0..ba165289c177 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 9c67855acbdd..4a4d440abc44 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -2739,7 +2739,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 726730ad3076..f015b79520bf 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.
@@ -1191,6 +1191,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;
@@ -2977,7 +2980,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:
@@ -4128,13 +4131,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())
     {
@@ -4387,6 +4390,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 );
@@ -4396,18 +4403,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 041802147605..c7f99878e12d 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -83,6 +83,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)
@@ -334,31 +335,102 @@ 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&)
+    {
+        TOOLS_WARN_EXCEPTION("writerfilter.dmapper",
+                             "Cannot get the right text range for date field");
+        return;
+    }
 
     std::optional<OUString> oData = getValueFromDataBinding();
     if (oData.has_value())
-        aDefaultText = *oData;
+        xCrsr->setString(*oData);
 
-    xPropertySet->setPropertyValue("Content", uno::Any(aDefaultText));
+    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);
 
-    PropertyMap aMap;
-    aMap.InsertProps(m_rDM_Impl.GetTopContext());
+    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();
+            }
+        }
 
-    // add it into document
-    m_rDM_Impl.appendTextContent(xControlModel, aMap.GetPropertyValues());
+        if (!sPropertyName.isEmpty())
+        {
+            xContentControlProps->setPropertyValue(sPropertyName, prop.Value);
+        }
+    }
 
-    // Store all unused sdt parameters from grabbag
-    xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG,
-                                   uno::Any(getInteropGrabBagAndClear()));
+    xContentControlProps->setPropertyValue("PlainText", uno::Any(true));
+
+    xText->insertTextContent(xCrsr, xContentControl, /*bAbsorb=*/true);
 
     // clean up
     clear();
@@ -366,7 +438,7 @@ void SdtHelper::createPlainTextControl()
 
 void SdtHelper::createDateContentControl()
 {
-    if (!m_xDateFieldStartRange.is())
+    if (!m_xFieldStartRange.is())
         return;
 
     uno::Reference<text::XTextCursor> xCrsr;
@@ -383,7 +455,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())
@@ -520,6 +592,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