sw/qa/extras/ooxmlexport/ooxmlexport17.cxx                       |    5 
 sw/source/filter/ww8/docxattributeoutput.cxx                     |    1 
 writerfilter/CppunitTest_writerfilter_dmapper.mk                 |    1 
 writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx               |   89 
++++++++++
 writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx |binary
 writerfilter/source/dmapper/DomainMapper.cxx                     |   24 ++
 writerfilter/source/dmapper/DomainMapper_Impl.cxx                |   54 ++++++
 writerfilter/source/dmapper/DomainMapper_Impl.hxx                |    3 
 writerfilter/source/dmapper/SdtHelper.cxx                        |    5 
 writerfilter/source/dmapper/SdtHelper.hxx                        |    7 
 writerfilter/source/ooxml/OOXMLFastContextHandler.cxx            |   16 +
 writerfilter/source/ooxml/OOXMLFastContextHandler.hxx            |    2 
 writerfilter/source/ooxml/model.xml                              |    5 
 13 files changed, 208 insertions(+), 4 deletions(-)

New commits:
commit 636ac1c46665cfe35884afb9a6663feb15f6b4c2
Author:     Miklos Vajna <[email protected]>
AuthorDate: Wed Apr 20 08:29:35 2022 +0200
Commit:     Miklos Vajna <[email protected]>
CommitDate: Mon Apr 25 08:26:29 2022 +0200

    sw content controls: add initial DOCX import
    
    - map inline/run SDTs with unknown type (i.e. rich text) to
      SwContentControl
    
    - decouple block and run SDTs and leave block ones unchanged
    
    - track start position of run SDTs similar to bookmarks, which needs
      different code to SDT at text start vs later
    
    - fix DocxAttributeOutput::RunText() to please
      CppunitTest_sw_ooxmlexport2's testFdo67013, which had an inline SDT in
      footer, not at para end
    
    (cherry picked from commit f2ab1375b266d7465ef31d06ec8f949c6b91d853)
    
    Change-Id: I59b8b7f3170cf37f1547db07ae0992850e0e3aa8
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133330
    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 a3bd1a18094f..d0e3c151282b 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -233,8 +233,9 @@ DECLARE_OOXMLEXPORT_TEST(testTdf148361, "tdf148361.docx")
     uno::Reference<text::XTextField> xTextField1(xFields->nextElement(), 
uno::UNO_QUERY);
     CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), 
xTextField1->getPresentation(false));
 
-    uno::Reference<text::XTextField> xTextField2(xFields->nextElement(), 
uno::UNO_QUERY);
-    CPPUNIT_ASSERT_EQUAL(OUString("[Type text]"), 
xTextField2->getPresentation(false));
+    OUString aActual = getParagraph(2)->getString();
+    // This was "itadmin".
+    CPPUNIT_ASSERT_EQUAL(OUString("[Type text]"), aActual);
 }
 
 DECLARE_OOXMLEXPORT_TEST(testTdf142407, "tdf142407.docx")
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 038a5bd86520..059c7204b01f 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -3281,6 +3281,7 @@ void DocxAttributeOutput::RunText( const OUString& rText, 
rtl_TextEncoding /*eCh
     if (m_nCloseContentControlInThisRun > 0)
     {
         ++m_nCloseContentControlInPreviousRun;
+        --m_nCloseContentControlInThisRun;
     }
     m_bRunTextIsOn = true;
     // one text can be split into more <w:t>blah</w:t>'s by line breaks etc.
diff --git a/writerfilter/CppunitTest_writerfilter_dmapper.mk 
b/writerfilter/CppunitTest_writerfilter_dmapper.mk
index 48b4ee87e087..de1a8cea9f48 100644
--- a/writerfilter/CppunitTest_writerfilter_dmapper.mk
+++ b/writerfilter/CppunitTest_writerfilter_dmapper.mk
@@ -23,6 +23,7 @@ $(eval $(call 
gb_CppunitTest_add_exception_objects,writerfilter_dmapper, \
     writerfilter/qa/cppunittests/dmapper/GraphicImport \
     writerfilter/qa/cppunittests/dmapper/TextEffectsHandler \
     writerfilter/qa/cppunittests/dmapper/PropertyMap \
+    writerfilter/qa/cppunittests/dmapper/SdtHelper \
 ))
 
 $(eval $(call gb_CppunitTest_use_libraries,writerfilter_dmapper, \
diff --git a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx 
b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
new file mode 100644
index 000000000000..da2663b93409
--- /dev/null
+++ b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
@@ -0,0 +1,89 @@
+/* -*- 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 <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/text/XTextDocument.hpp>
+
+using namespace com::sun::star;
+
+namespace
+{
+/// Tests for writerfilter/source/dmapper/SdtHelper.cxx.
+class Test : public test::BootstrapFixture, public unotest::MacrosTest
+{
+private:
+    uno::Reference<lang::XComponent> mxComponent;
+
+public:
+    void setUp() override;
+    void tearDown() override;
+    uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+};
+
+void Test::setUp()
+{
+    test::BootstrapFixture::setUp();
+
+    mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+void Test::tearDown()
+{
+    if (mxComponent.is())
+        mxComponent->dispose();
+
+    test::BootstrapFixture::tearDown();
+}
+
+constexpr OUStringLiteral DATA_DIRECTORY = 
u"/writerfilter/qa/cppunittests/dmapper/data/";
+
+CPPUNIT_TEST_FIXTURE(Test, testSdtRunRichText)
+{
+    // Given a document with a rich text inline/run SDT:
+    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + 
"sdt-run-rich-text.docx";
+
+    // When loading the document:
+    getComponent() = loadFromDesktop(aURL);
+
+    // Then make sure that formatting of the text inside the SDT is not lost:
+    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, and the whole SDT had 12pt 
font size.
+    CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aTextPortionType);
+    uno::Reference<text::XTextContent> xContentControl;
+    xPortion->getPropertyValue("ContentControl") >>= xContentControl;
+    uno::Reference<text::XTextRange> xContentControlRange(xContentControl, 
uno::UNO_QUERY);
+    uno::Reference<text::XText> xText = xContentControlRange->getText();
+    uno::Reference<container::XEnumerationAccess> xContentEnumAccess(xText, 
uno::UNO_QUERY);
+    uno::Reference<container::XEnumeration> xContentEnum = 
xContentEnumAccess->createEnumeration();
+    uno::Reference<beans::XPropertySet> xContent(xContentEnum->nextElement(), 
uno::UNO_QUERY);
+    float fCharheight{};
+    xContent->getPropertyValue("CharHeight") >>= fCharheight;
+    CPPUNIT_ASSERT_EQUAL(12.f, fCharheight);
+    xContent.set(xContentEnum->nextElement(), uno::UNO_QUERY);
+    xContent->getPropertyValue("CharHeight") >>= fCharheight;
+    CPPUNIT_ASSERT_EQUAL(24.f, fCharheight);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx
new file mode 100644
index 000000000000..b945d0bb3b55
Binary files /dev/null and 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx differ
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx 
b/writerfilter/source/dmapper/DomainMapper.cxx
index 789322fe8779..2ae843c9834f 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -1074,14 +1074,33 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
         }
         break;
         case NS_ooxml::LN_CT_SdtBlock_sdtContent:
+        case NS_ooxml::LN_CT_SdtRun_sdtContent:
             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
                 
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText);
+                if (nName == NS_ooxml::LN_CT_SdtRun_sdtContent)
+                {
+                    
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::richText);
+                    m_pImpl->PushSdt();
+                }
             }
             m_pImpl->SetSdt(true);
         break;
         case NS_ooxml::LN_CT_SdtBlock_sdtEndContent:
+        case NS_ooxml::LN_CT_SdtRun_sdtEndContent:
+            if (nName == NS_ooxml::LN_CT_SdtRun_sdtEndContent)
+            {
+                switch (m_pImpl->m_pSdtHelper->getControlType())
+                {
+                    case SdtControlType::richText:
+                        m_pImpl->PopSdt();
+                        break;
+                    default:
+                        break;
+                }
+            }
+
             m_pImpl->SetSdt(false);
 
             // It's not possible to insert the relevant property to the 
character context here:
@@ -2725,6 +2744,11 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
         m_pImpl->disableInteropGrabBag();
     }
     break;
+    case NS_ooxml::LN_CT_SdtPr_showingPlcHdr:
+    {
+        m_pImpl->m_pSdtHelper->SetShowingPlcHdr();
+    }
+    break;
     case NS_ooxml::LN_CT_SdtPr_dataBinding:
     case NS_ooxml::LN_CT_SdtPr_equation:
     case NS_ooxml::LN_CT_SdtPr_checkbox:
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index f5877b554770..1e9d8f55c587 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -829,6 +829,60 @@ void DomainMapper_Impl::SetSdt(bool bSdt)
     }
 }
 
+void DomainMapper_Impl::PushSdt()
+{
+    if (m_aTextAppendStack.empty())
+    {
+        return;
+    }
+
+    uno::Reference<text::XTextAppend> xTextAppend = 
m_aTextAppendStack.top().xTextAppend;
+    uno::Reference<text::XTextCursor> xCursor
+        = 
xTextAppend->getText()->createTextCursorByRange(xTextAppend->getEnd());
+    // Offset so the cursor is not adjusted as we import the SDT's content.
+    bool bStart = !xCursor->goLeft(1, /*bExpand=*/false);
+    m_xSdtStarts.push({bStart, OUString(), xCursor->getStart()});
+}
+
+void DomainMapper_Impl::PopSdt()
+{
+    if (m_xSdtStarts.empty())
+    {
+        return;
+    }
+
+    BookmarkInsertPosition aPosition = m_xSdtStarts.top();
+    m_xSdtStarts.pop();
+    uno::Reference<text::XTextRange> xStart = aPosition.m_xTextRange;
+    uno::Reference<text::XTextRange> xEnd = GetTopTextAppend()->getEnd();
+    uno::Reference<text::XText> xText = xEnd->getText();
+    uno::Reference<text::XTextCursor> xCursor = 
xText->createTextCursorByRange(xStart);
+    if (!xCursor)
+    {
+        SAL_WARN("writerfilter.dmapper", "DomainMapper_Impl::PopSdt: no start 
position");
+        return;
+    }
+
+    if (aPosition.m_bIsStartOfText)
+    {
+        xCursor->gotoStart(/*bExpand=*/false);
+    }
+    else
+    {
+        // Undo the goLeft() in DomainMapper_Impl::PushSdt();
+        xCursor->goRight(1, /*bExpand=*/false);
+    }
+    xCursor->gotoRange(xEnd, /*bExpand=*/true);
+    uno::Reference<text::XTextContent> xContentControl(
+        m_xTextFactory->createInstance("com.sun.star.text.ContentControl"), 
uno::UNO_QUERY);
+    uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, 
uno::UNO_QUERY);
+    if (m_pSdtHelper->GetShowingPlcHdr())
+    {
+        xContentControlProps->setPropertyValue("ShowingPlaceHolder",
+                                               
uno::makeAny(m_pSdtHelper->GetShowingPlcHdr()));
+    }
+    xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
+}
 
 void    DomainMapper_Impl::PushProperties(ContextType eId)
 {
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index c65a03520a15..fcd410c6ce9d 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -623,6 +623,7 @@ private:
 
     css::uno::Reference<css::text::XTextRange> m_xGlossaryEntryStart;
     css::uno::Reference<css::text::XTextRange> m_xSdtEntryStart;
+    std::stack<BookmarkInsertPosition> m_xSdtStarts;
 
 public:
     css::uno::Reference<css::text::XTextRange> m_xInsertTextRange;
@@ -715,6 +716,8 @@ public:
 
     /// Setter method for m_bSdt.
     void SetSdt(bool bSdt);
+    void PushSdt();
+    void PopSdt();
     /// Getter method for m_bSdt.
     bool GetSdt() const { return m_bSdt;}
     bool GetParaChanged() const { return m_bParaChanged;}
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx 
b/writerfilter/source/dmapper/SdtHelper.cxx
index f4b02fab4d02..0d91a242c0d5 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -421,6 +421,10 @@ bool SdtHelper::containedInInteropGrabBag(const OUString& 
rValueName)
         [&rValueName](const beans::PropertyValue& i) { return i.Name == 
rValueName; });
 }
 
+void SdtHelper::SetShowingPlcHdr() { m_bShowingPlcHdr = true; }
+
+bool SdtHelper::GetShowingPlcHdr() const { return m_bShowingPlcHdr; }
+
 void SdtHelper::clear()
 {
     m_aDropDownItems.clear();
@@ -429,6 +433,7 @@ void SdtHelper::clear()
     m_sDataBindingXPath.clear();
     m_sDataBindingStoreItemID.clear();
     m_aGrabBag.clear();
+    m_bShowingPlcHdr = false;
 }
 
 } // namespace writerfilter::dmapper
diff --git a/writerfilter/source/dmapper/SdtHelper.hxx 
b/writerfilter/source/dmapper/SdtHelper.hxx
index d9a6115a1251..578bb5edd4fd 100644
--- a/writerfilter/source/dmapper/SdtHelper.hxx
+++ b/writerfilter/source/dmapper/SdtHelper.hxx
@@ -42,6 +42,7 @@ enum class SdtControlType
     datePicker,
     dropDown,
     plainText,
+    richText,
     unsupported, // Sdt block is defined, but we still do not support such 
type of field
     unknown
 };
@@ -93,6 +94,9 @@ class SdtHelper final : public virtual SvRefBase
     /// empty sequence from not yet initialized)
     bool m_bPropertiesXMLsLoaded;
 
+    /// Current contents are placeholder text.
+    bool m_bShowingPlcHdr = false;
+
     /// Create and append the drawing::XControlShape, containing the various 
models.
     void createControlShape(css::awt::Size aSize,
                             css::uno::Reference<css::awt::XControlModel> 
const& xControlModel,
@@ -155,6 +159,9 @@ public:
     bool isInteropGrabBagEmpty() const;
     bool containedInInteropGrabBag(const OUString& rValueName);
     sal_Int32 getInteropGrabBagSize() const;
+
+    void SetShowingPlcHdr();
+    bool GetShowingPlcHdr() const;
 };
 
 } // namespace writerfilter::dmapper
diff --git a/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx 
b/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx
index a915380fc203..c915cf40c1eb 100644
--- a/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx
+++ b/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx
@@ -468,6 +468,22 @@ void OOXMLFastContextHandler::endSdt()
     mpStream->props(pProps.get());
 }
 
+void OOXMLFastContextHandler::startSdtRun()
+{
+    OOXMLPropertySet::Pointer_t pProps(new OOXMLPropertySet);
+    OOXMLValue::Pointer_t pVal = OOXMLIntegerValue::Create(1);
+    pProps->add(NS_ooxml::LN_CT_SdtRun_sdtContent, pVal, 
OOXMLProperty::ATTRIBUTE);
+    mpStream->props(pProps.get());
+}
+
+void OOXMLFastContextHandler::endSdtRun()
+{
+    OOXMLPropertySet::Pointer_t pProps(new OOXMLPropertySet);
+    OOXMLValue::Pointer_t pVal = OOXMLIntegerValue::Create(1);
+    pProps->add(NS_ooxml::LN_CT_SdtRun_sdtEndContent, pVal, 
OOXMLProperty::ATTRIBUTE);
+    mpStream->props(pProps.get());
+}
+
 void OOXMLFastContextHandler::startSectionGroup()
 {
     if (isForwardEvents())
diff --git a/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx 
b/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx
index 0b79f9450afa..d0b1ed791478 100644
--- a/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx
+++ b/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx
@@ -139,6 +139,8 @@ public:
     virtual void popBiDiEmbedLevel();
     void startSdt();
     void endSdt();
+    void startSdtRun();
+    void endSdtRun();
 
     void startField();
     void fieldSeparator();
diff --git a/writerfilter/source/ooxml/model.xml 
b/writerfilter/source/ooxml/model.xml
index 56767f526356..04fb1934b8d5 100644
--- a/writerfilter/source/ooxml/model.xml
+++ b/writerfilter/source/ooxml/model.xml
@@ -18315,8 +18315,9 @@
       <element name="sdtPr" tokenid="ooxml:CT_SdtRun_sdtPr"/>
       <element name="sdtEndPr" tokenid="ooxml:CT_SdtRun_sdtEndPr"/>
       <element name="sdtContent" tokenid="ooxml:CT_SdtRun_sdtContent"/>
-      <action name="start" action="startSdt"/>
-      <action name="end" action="endSdt"/>
+      <element name="sdtEndContent" tokenid="ooxml:CT_SdtRun_sdtEndContent"/>
+      <action name="start" action="startSdtRun"/>
+      <action name="end" action="endSdtRun"/>
     </resource>
     <resource name="CT_SdtCell" resource="Stream">
       <element name="sdtPr" tokenid="ooxml:CT_SdtCell_sdtPr"/>

Reply via email to