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 f2ab1375b266d7465ef31d06ec8f949c6b91d853
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Apr 20 08:29:35 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Apr 20 11:47:40 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
    
    Change-Id: I59b8b7f3170cf37f1547db07ae0992850e0e3aa8
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133195
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
index 5d5bdb22471a..dcfede6b43ee 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -321,8 +321,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 c80ea4972e9a..ca52bace2012 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -3273,6 +3273,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 7c2e93227f05..61ff5a2c67fb 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:
@@ -2734,6 +2753,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 6bf0651bcbe8..2ce7081d286d 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -832,6 +832,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 9bef6fb17dc1..a8355e17a4c9 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -625,6 +625,7 @@ private:
 
     css::uno::Reference<css::text::XTextRange> m_xGlossaryEntryStart;
     css::uno::Reference<css::text::XTextRange> m_xSdtEntryStart;
+    std::stack<BookmarkInsertPosition> m_xSdtStarts;
 
     std::queue< css::uno::Reference< css::text::XTextFrame > > 
m_xPendingTextBoxFrames;
 
@@ -725,6 +726,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 0bda90d71e4f..924d70272181 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -419,6 +419,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();
@@ -427,6 +431,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 d898aee8ed72..f0515f91c7a3 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 e6aa15298c05..9feaffaf4db5 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 b64a87e6f18c..30491f08dc43 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