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 xTextField1(xFields->nextElement(), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), xTextField1->getPresentation(false)); - uno::Reference 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 blah'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 +#include + +#include +#include +#include + +using namespace com::sun::star; + +namespace +{ +/// Tests for writerfilter/source/dmapper/SdtHelper.cxx. +class Test : public test::BootstrapFixture, public unotest::MacrosTest +{ +private: + uno::Reference mxComponent; + +public: + void setUp() override; + void tearDown() override; + uno::Reference& 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 xTextDocument(getComponent(), uno::UNO_QUERY); + uno::Reference xParaEnumAccess(xTextDocument->getText(), + uno::UNO_QUERY); + uno::Reference xParaEnum = xParaEnumAccess->createEnumeration(); + uno::Reference xPara(xParaEnum->nextElement(), uno::UNO_QUERY); + uno::Reference xPortionEnum = xPara->createEnumeration(); + uno::Reference 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 xContentControl; + xPortion->getPropertyValue("ContentControl") >>= xContentControl; + uno::Reference xContentControlRange(xContentControl, uno::UNO_QUERY); + uno::Reference xText = xContentControlRange->getText(); + uno::Reference xContentEnumAccess(xText, uno::UNO_QUERY); + uno::Reference xContentEnum = xContentEnumAccess->createEnumeration(); + uno::Reference 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 xTextAppend = m_aTextAppendStack.top().xTextAppend; + uno::Reference 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 xStart = aPosition.m_xTextRange; + uno::Reference xEnd = GetTopTextAppend()->getEnd(); + uno::Reference xText = xEnd->getText(); + uno::Reference 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 xContentControl( + m_xTextFactory->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference 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 m_xGlossaryEntryStart; css::uno::Reference m_xSdtEntryStart; + std::stack 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 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 @@ - - + + +