From c4620dec0818802e9b56e223cb78eabca632abba Mon Sep 17 00:00:00 2001 From: Justin Luth Date: Wed, 30 Nov 2022 13:09:07 -0500 Subject: tdf#151548 sw content controls: preserve lock DOCX SdtControls can be locked in two ways: -Content Control cannot be deleted (sdtLocked) -Contents cannot be edited (contentLocked) or both (sdtContentLocked) make CppunitTest_writerfilter_dmapper CPPUNIT_TEST_NAME=testSdtRunRichText make CppunitTest_sw_ooxmlexport4 CPPUNIT_TEST_NAME=testSimpleSdts make CppunitTest_sw_ooxmlexport17 CPPUNIT_TEST_NAME=testDateContentControlExport make CppunitTest_sw_core_unocore CPPUNIT_TEST_NAME=testContentControlDate make CppunitTest_sw_macros_test CPPUNIT_TEST_NAME=testVba make CppunitTest_xmloff_text CPPUNIT_TEST_NAME=testAliasContentControlExport make CppunitTest_xmloff_text CPPUNIT_TEST_NAME=testAliasContentControlImport Change-Id: I5a82d9f6b5103a4902f59af66cd8a99addd4e690 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143542 Tested-by: Jenkins Reviewed-by: Justin Luth Reviewed-by: Miklos Vajna Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144553 Tested-by: Jenkins CollaboraOffice --- include/xmloff/xmltoken.hxx | 1 + offapi/com/sun/star/text/ContentControl.idl | 6 +++ .../OpenDocument-v1.3+libreoffice-schema.rng | 5 +++ sw/inc/formatcontentcontrol.hxx | 12 ++++++ sw/inc/unoprnms.hxx | 1 + sw/qa/core/data/docm/testModernVBA.docm | Bin 32539 -> 32970 bytes sw/qa/core/unocore/unocore.cxx | 2 + sw/qa/extras/ooxmlexport/ooxmlexport17.cxx | 3 ++ sw/qa/extras/ooxmlexport/ooxmlexport4.cxx | 1 + sw/source/core/txtnode/attrcontentcontrol.cxx | 38 +++++++++++++++++++ sw/source/core/unocore/unocontentcontrol.cxx | 28 ++++++++++++++ sw/source/core/unocore/unomap1.cxx | 1 + sw/source/filter/ww8/docxattributeoutput.cxx | 16 ++++++++ sw/source/filter/ww8/docxattributeoutput.hxx | 1 + sw/source/ui/vba/vbacontentcontrol.cxx | 42 ++++++++++++++++----- writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx | 4 ++ .../dmapper/data/sdt-run-rich-text.docx | Bin 4376 -> 5381 bytes writerfilter/source/dmapper/DomainMapper.cxx | 12 +++++- writerfilter/source/dmapper/DomainMapper_Impl.cxx | 5 +++ writerfilter/source/dmapper/SdtHelper.cxx | 5 +++ writerfilter/source/dmapper/SdtHelper.hxx | 6 +++ xmloff/qa/unit/data/content-control-alias.fodt | 2 +- xmloff/qa/unit/text.cxx | 5 +++ xmloff/source/core/xmltoken.cxx | 1 + xmloff/source/text/txtparae.cxx | 7 ++++ xmloff/source/text/xmlcontentcontrolcontext.cxx | 10 +++++ xmloff/source/text/xmlcontentcontrolcontext.hxx | 1 + xmloff/source/token/tokens.txt | 1 + 28 files changed, 205 insertions(+), 11 deletions(-) diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx index e5c38a25bcfd..fb3685a64c59 100644 --- a/include/xmloff/xmltoken.hxx +++ b/include/xmloff/xmltoken.hxx @@ -1218,6 +1218,7 @@ namespace xmloff::token { XML_LIST_STYLE, XML_LIST_STYLE_NAME, XML_LN, + XML_LOCK, XML_LOCKED, XML_LOG, XML_LOGARITHMIC, diff --git a/offapi/com/sun/star/text/ContentControl.idl b/offapi/com/sun/star/text/ContentControl.idl index 6abcc79fd204..59894741de2b 100644 --- a/offapi/com/sun/star/text/ContentControl.idl +++ b/offapi/com/sun/star/text/ContentControl.idl @@ -127,6 +127,12 @@ service ContentControl @since LibreOffice 7.5 */ [optional, property] long Id; + + /** Describes whether the control itself and/or its data can be modified or deleted by the user. + + @since LibreOffice 7.6 + */ + [optional, property] string Lock; }; diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng index 5359dec15298..0acadbb8e71c 100644 --- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng +++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng @@ -2944,6 +2944,11 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1. + + + + + diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx index b8b2db8882a9..c0b200683277 100644 --- a/sw/inc/formatcontentcontrol.hxx +++ b/sw/inc/formatcontentcontrol.hxx @@ -178,6 +178,9 @@ class SW_DLLPUBLIC SwContentControl : public sw::BroadcastingModify /// The id: just remembered. sal_Int32 m_nId = 0; + /// The control and content locks: mostly just remembered. + OUString m_aLock; + /// 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 m_oSelectedListItem; @@ -361,8 +364,17 @@ public: sal_Int32 GetId() const { return m_nId; } + // At the design level, define how the control should be locked. No effect at implementation lvl + void SetLock(bool bLockContent, bool bLockControl); + void SetLock(const OUString& rLock) { m_aLock = rLock; } + + // At the design level, get how the control is locked. Does not reflect actual implementation. + std::optional GetLock(bool bControl) const; + const OUString& GetLock() const { return m_aLock; } + void SetReadWrite(bool bReadWrite) { m_bReadWrite = bReadWrite; } + // At the implementation level, define whether the user can directly modify the contents. bool GetReadWrite() const { return m_bReadWrite; } SwContentControlType GetType() const; diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index 2dffbeb739af..a850b76994f4 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -891,6 +891,7 @@ #define UNO_NAME_ALIAS "Alias" #define UNO_NAME_TAG "Tag" #define UNO_NAME_ID "Id" +#define UNO_NAME_LOCK "Lock" #define UNO_NAME_DATE_STRING "DateString" #endif diff --git a/sw/qa/core/data/docm/testModernVBA.docm b/sw/qa/core/data/docm/testModernVBA.docm index 49e53b615622..c08d738c8adb 100644 Binary files a/sw/qa/core/data/docm/testModernVBA.docm and b/sw/qa/core/data/docm/testModernVBA.docm differ diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx index 20d93d688f96..be5d74678e2e 100644 --- a/sw/qa/core/unocore/unocore.cxx +++ b/sw/qa/core/unocore/unocore.cxx @@ -574,6 +574,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlDate) xContentControlProps->setPropertyValue("Color", uno::Any(OUString("008000"))); xContentControlProps->setPropertyValue("Alias", uno::Any(OUString("myalias"))); xContentControlProps->setPropertyValue("Tag", uno::Any(OUString("mytag"))); + xContentControlProps->setPropertyValue("Lock", uno::Any(OUString("sdtContentLocked"))); xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); // Then make sure that the specified properties are set: @@ -599,6 +600,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlDate) CPPUNIT_ASSERT_EQUAL(OUString("008000"), pContentControl->GetColor()); CPPUNIT_ASSERT_EQUAL(OUString("myalias"), pContentControl->GetAlias()); CPPUNIT_ASSERT_EQUAL(OUString("mytag"), pContentControl->GetTag()); + CPPUNIT_ASSERT_EQUAL(OUString("sdtContentLocked"), pContentControl->GetLock()); } CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlPlainText) diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx index 8ccd9f1f6540..63fda18c23f3 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx @@ -300,6 +300,8 @@ CPPUNIT_TEST_FIXTURE(Test, testDateContentControlExport) xContentControlProps->setPropertyValue("Color", uno::Any(OUString("008000"))); xContentControlProps->setPropertyValue("Alias", uno::Any(OUString("myalias"))); xContentControlProps->setPropertyValue("Tag", uno::Any(OUString("mytag"))); + xContentControlProps->setPropertyValue("Lock", uno::Any(OUString("sdtLocked"))); + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); // When exporting to DOCX: @@ -323,6 +325,7 @@ CPPUNIT_TEST_FIXTURE(Test, testDateContentControlExport) assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w15:color", "val", "008000"); assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:alias", "val", "myalias"); assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:tag", "val", "mytag"); + assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:lock", "val", "sdtLocked"); } DECLARE_OOXMLEXPORT_TEST(testTdf137466, "tdf137466.docx") diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx index e0b9db36e980..a32da9cb88ae 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx @@ -1022,6 +1022,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSimpleSdts) assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:sdt/w:sdtPr/w:text", 1); assertXPath(pXmlDoc, "//*/w:sdt/w:sdtPr/w:id", 5); + assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:lock", 1); assertXPath(pXmlDoc, "/w:document/w:body/w:sdt[1]/w:sdtPr/w:picture", 1); assertXPath(pXmlDoc, "/w:document/w:body/w:sdt[2]/w:sdtPr/w:group", 1); assertXPath(pXmlDoc, "/w:document/w:body/w:p[4]/w:sdt/w:sdtPr/w:citation", 1); diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx index 8542423e8696..19fd922a439b 100644 --- a/sw/source/core/txtnode/attrcontentcontrol.cxx +++ b/sw/source/core/txtnode/attrcontentcontrol.cxx @@ -439,6 +439,42 @@ bool SwContentControl::ShouldOpenPopup(const vcl::KeyCode& rKeyCode) return false; } +// NOTE: call SetReadWrite separately to implement true (un)locking. +// This is mostly a theoretical function; the lock state is mainly kept for round-tripping purposes. +// It is implemented here primarily for pointless VBA control, but with the intention that it +// could be made functionally useful as well for checkboxes/dropdowns/pictures. +// Returns whether the content (bControl=false) cannot be modified, +// or if the control cannot be deleted. +std::optional SwContentControl::GetLock(bool bControl) const +{ + std::optional oLock; + if (m_aLock.isEmpty()) + return oLock; + else if (m_aLock.equalsIgnoreAsciiCase("sdtContentLocked")) + oLock = true; + else if (m_aLock.equalsIgnoreAsciiCase("unlocked")) + oLock = false; + else if (m_aLock.equalsIgnoreAsciiCase("sdtLocked")) + oLock = bControl; + else if (m_aLock.equalsIgnoreAsciiCase("contentLocked")) + oLock = !bControl; + + assert(oLock && "invalid or unknown lock state"); + return oLock; +} + +void SwContentControl::SetLock(bool bLockContent, bool bLockControl) +{ + if (!bLockContent && !bLockControl) + m_aLock = "unlocked"; + else if (bLockContent && bLockControl) + m_aLock = "sdtContentLocked"; + else if (bLockContent) + m_aLock = "contentLocked"; + else + m_aLock = "sdtLocked"; +} + SwContentControlType SwContentControl::GetType() const { if (m_bCheckbox) @@ -520,6 +556,8 @@ void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("tag"), BAD_CAST(m_aTag.toUtf8().getStr())); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(m_nId).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("lock"), + BAD_CAST(m_aLock.toUtf8().getStr())); if (!m_aListItems.empty()) { diff --git a/sw/source/core/unocore/unocontentcontrol.cxx b/sw/source/core/unocore/unocontentcontrol.cxx index 61c7ac66c9fd..01b1771e6edf 100644 --- a/sw/source/core/unocore/unocontentcontrol.cxx +++ b/sw/source/core/unocore/unocontentcontrol.cxx @@ -178,6 +178,7 @@ public: OUString m_aAlias; OUString m_aTag; sal_Int32 m_nId; + OUString m_aLock; Impl(SwXContentControl& rThis, SwDoc& rDoc, SwContentControl* pContentControl, const uno::Reference& xParentText, @@ -556,6 +557,7 @@ void SwXContentControl::AttachImpl(const uno::Reference& xText pContentControl->SetAlias(m_pImpl->m_aAlias); pContentControl->SetTag(m_pImpl->m_aTag); pContentControl->SetId(m_pImpl->m_nId); + pContentControl->SetLock(m_pImpl->m_aLock); SwFormatContentControl aContentControl(pContentControl, nWhich); bool bSuccess @@ -1046,6 +1048,21 @@ void SAL_CALL SwXContentControl::setPropertyValue(const OUString& rPropertyName, } } } + else if (rPropertyName == UNO_NAME_LOCK) + { + OUString aValue; + if (rValue >>= aValue) + { + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_aLock = aValue; + } + else + { + m_pImpl->m_pContentControl->SetLock(aValue); + } + } + } else { throw beans::UnknownPropertyException(); @@ -1308,6 +1325,17 @@ uno::Any SAL_CALL SwXContentControl::getPropertyValue(const OUString& rPropertyN aRet <<= m_pImpl->m_pContentControl->GetId(); } } + else if (rPropertyName == UNO_NAME_LOCK) + { + if (m_pImpl->m_bIsDescriptor) + { + aRet <<= m_pImpl->m_aLock; + } + else + { + aRet <<= m_pImpl->m_pContentControl->GetLock(); + } + } else { throw beans::UnknownPropertyException(); diff --git a/sw/source/core/unocore/unomap1.cxx b/sw/source/core/unocore/unomap1.cxx index 5d4d0c0b1a77..0ae874b3b9e1 100644 --- a/sw/source/core/unocore/unomap1.cxx +++ b/sw/source/core/unocore/unomap1.cxx @@ -1048,6 +1048,7 @@ const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetContentControlProper { u"" UNO_NAME_ALIAS, 0, cppu::UnoType::get(), PROPERTY_NONE, 0 }, { u"" UNO_NAME_TAG, 0, cppu::UnoType::get(), PROPERTY_NONE, 0 }, { u"" UNO_NAME_ID, 0, cppu::UnoType::get(), PROPERTY_NONE, 0 }, + { u"" UNO_NAME_LOCK, 0, cppu::UnoType::get(), PROPERTY_NONE, 0 }, { u"" UNO_NAME_DATE_STRING, 0, cppu::UnoType::get(), PropertyAttribute::READONLY, 0 }, { u"", 0, css::uno::Type(), 0, 0 } }; diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index bd9bba939aa7..f2aaefa22440 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -617,6 +617,8 @@ void SdtBlockHelper::DeleteAndResetTheLists() m_aAlias.clear(); if (!m_aTag.isEmpty()) m_aTag.clear(); + if (!m_aLock.isEmpty()) + m_aLock.clear(); if (!m_aPlaceHolderDocPart.isEmpty()) m_aPlaceHolderDocPart.clear(); if (!m_aColor.isEmpty()) @@ -723,6 +725,9 @@ void SdtBlockHelper::WriteExtraParams(::sax_fastparser::FSHelperPtr& pSerializer if (!m_aTag.isEmpty()) pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val), m_aTag); + + if (!m_aLock.isEmpty()) + pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val), m_aLock); } void SdtBlockHelper::EndSdtBlock(::sax_fastparser::FSHelperPtr& pSerializer) @@ -832,6 +837,11 @@ void SdtBlockHelper::GetSdtParamsFromGrabBag(const uno::Sequence>= m_aTag)) SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tag value"); } + else if (aPropertyValue.Name == "ooxml:CT_SdtPr_lock" && m_aLock.isEmpty()) + { + if (!(aPropertyValue.Value >>= m_aLock)) + SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt lock value"); + } else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id") m_bHasId = true; else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation") @@ -2391,6 +2401,12 @@ void DocxAttributeOutput::WriteContentControlStart() OString::number(m_pContentControl->GetId())); } + if (!m_pContentControl->GetLock().isEmpty()) + { + m_pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val), + m_pContentControl->GetLock()); + } + if (m_pContentControl->GetShowingPlaceHolder()) { m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr); diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index 07bffdb061ae..f61b4576a941 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -143,6 +143,7 @@ public: OUString m_aPlaceHolderDocPart; OUString m_aAlias; OUString m_aTag; + OUString m_aLock; sal_Int32 m_nSdtPrToken; void DeleteAndResetTheLists(); diff --git a/sw/source/ui/vba/vbacontentcontrol.cxx b/sw/source/ui/vba/vbacontentcontrol.cxx index 7ecd5ccc0365..03569406cb53 100644 --- a/sw/source/ui/vba/vbacontentcontrol.cxx +++ b/sw/source/ui/vba/vbacontentcontrol.cxx @@ -97,6 +97,13 @@ sal_Bool SwVbaContentControl::getChecked() void SwVbaContentControl::setChecked(sal_Bool bSet) { + // Word 2010: if locked, then the checked status is changed, but not the underlying text. + // Do we really want to do that? That is pretty bizarre behaviour... + // For now, just implement what seems to be a more logical response. + // TODO: test with modern versions. + if (getLockContents()) + return; + std::shared_ptr pCC = m_rCC.GetContentControl().GetContentControl(); if (pCC->GetCheckbox() && pCC->GetChecked() != static_cast(bSet)) { @@ -489,21 +496,27 @@ sal_Int32 SwVbaContentControl::getLevel() sal_Bool SwVbaContentControl::getLockContentControl() { - SAL_INFO("sw.vba", "SwVbaContentControl::getLockContentControl stub"); - // returns whether the user can delete a content control from the active document. - return true; + const std::shared_ptr& pCC = m_rCC.GetContentControl().GetContentControl(); + std::optional oLock = pCC->GetLock(/*bControl=*/true); + return oLock && *oLock; } -void SwVbaContentControl::setLockContentControl(sal_Bool /*bSet*/) +void SwVbaContentControl::setLockContentControl(sal_Bool bSet) { - SAL_INFO("sw.vba", "SwVbaContentControl::setLockContentControl stub"); + std::shared_ptr pCC = m_rCC.GetContentControl().GetContentControl(); + std::optional oLock = pCC->GetLock(/*bControl=*/false); + pCC->SetLock(/*bContents=*/oLock && *oLock, /*bControl=*/bSet); } sal_Bool SwVbaContentControl::getLockContents() { - // Pseudo-implementation - the need for locking in a form would be very rare. - // LO uses this for internal purposes. Only expose it to VBA when safe. const std::shared_ptr& pCC = m_rCC.GetContentControl().GetContentControl(); + // If the theoretical design model says it is locked, then report as locked. + std::optional oLock = pCC->GetLock(/*bControl=*/false); + if (oLock && *oLock) + return true; + + // Now check the real implementation. // Checkbox/DropDown/Picture are normally locked - but not in this sense. Report as unlocked. if (pCC->GetType() == SwContentControlType::CHECKBOX || pCC->GetType() == SwContentControlType::DROP_DOWN_LIST @@ -518,6 +531,10 @@ sal_Bool SwVbaContentControl::getLockContents() void SwVbaContentControl::setLockContents(sal_Bool bSet) { std::shared_ptr pCC = m_rCC.GetContentControl().GetContentControl(); + // Set the lock both theoretically and actually. + std::optional oLock = pCC->GetLock(/*bControl=*/true); + pCC->SetLock(/*bContents=*/bSet, /*bControl=*/oLock && *oLock); + // Checkbox/DropDown/Picture are normally locked in LO implementation - don't unlock them. if (pCC->GetType() == SwContentControlType::CHECKBOX || pCC->GetType() == SwContentControlType::DROP_DOWN_LIST @@ -688,17 +705,24 @@ void SwVbaContentControl::Copy() void SwVbaContentControl::Cut() { + if (getLockContentControl()) + return; + SAL_INFO("sw.vba", "SwVbaContentControl::Cut[" << getID() << "], but missing sending to clipboard"); - m_rCC.Delete(/*bSaveContents=*/false); + m_rCC.Delete(/*bSaveContents=*/getLockContents()); } void SwVbaContentControl::Delete(const uno::Any& DeleteContents) { + if (getLockContentControl()) + return; + bool bDeleteContents = false; DeleteContents >>= bDeleteContents; - m_rCC.Delete(!bDeleteContents); + + m_rCC.Delete(/*bSaveContents=*/!bDeleteContents || getLockContents()); } void SwVbaContentControl::SetCheckedSymbol(sal_Int32 Character, const uno::Any& Font) diff --git a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx index 5b62fdf55122..f773991f7e02 100644 --- a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx +++ b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx @@ -95,6 +95,10 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtRunRichText) xContentControlProps->getPropertyValue("Tag") >>= aTag; // This was empty. CPPUNIT_ASSERT_EQUAL(OUString("mytag"), aTag); + OUString aLock; + xContentControlProps->getPropertyValue("Lock") >>= aLock; + // This was empty. + CPPUNIT_ASSERT_EQUAL(OUString("contentLocked"), aLock); } CPPUNIT_TEST_FIXTURE(Test, testSdtRunPlainText) diff --git a/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx index b7f291f776bf..aabc745bcf0e 100644 Binary files a/writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx 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 4395b65fc9c8..2dd9b0583445 100644 --- a/writerfilter/source/dmapper/DomainMapper.cxx +++ b/writerfilter/source/dmapper/DomainMapper.cxx @@ -2812,6 +2812,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) case NS_ooxml::LN_CT_SdtPlaceholder_docPart: case NS_ooxml::LN_CT_SdtPr_color: case NS_ooxml::LN_CT_SdtPr_tag: + case NS_ooxml::LN_CT_SdtPr_lock: { if (!m_pImpl->GetSdtStarts().empty()) { @@ -2843,6 +2844,12 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) break; } + if (nSprmId == NS_ooxml::LN_CT_SdtPr_lock) + { + m_pImpl->m_pSdtHelper->SetLock(sStringValue); + break; + } + if (nSprmId == NS_ooxml::LN_CT_SdtPr_checkbox) { m_pImpl->m_pSdtHelper->setControlType(SdtControlType::checkBox); @@ -2890,6 +2897,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) case NS_ooxml::LN_CT_SdtPr_id: sName = "ooxml:CT_SdtPr_id"; break; case NS_ooxml::LN_CT_SdtPr_alias: sName = "ooxml:CT_SdtPr_alias"; break; case NS_ooxml::LN_CT_SdtPr_tag: sName = "ooxml:CT_SdtPr_tag"; break; + case NS_ooxml::LN_CT_SdtPr_lock: sName = "ooxml:CT_SdtPr_lock"; break; case NS_ooxml::LN_CT_SdtPlaceholder_docPart: sName = "ooxml:CT_SdtPlaceholder_docPart"; break; case NS_ooxml::LN_CT_SdtPr_color: sName = "ooxml:CT_SdtPr_color"; break; default: assert(false); @@ -2910,8 +2918,10 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) if (pProperties) pProperties->resolve(*this); - if (nSprmId == NS_ooxml::LN_CT_SdtPr_alias || nSprmId == NS_ooxml::LN_CT_SdtPr_tag) + if (nSprmId == NS_ooxml::LN_CT_SdtPr_alias || nSprmId == NS_ooxml::LN_CT_SdtPr_tag + || nSprmId == NS_ooxml::LN_CT_SdtPr_lock) { + // Grabbag string values beans::PropertyValue aValue; aValue.Name = sName; aValue.Value <<= sStringValue; diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx index a3fba36fc39c..71f73d3fedea 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -948,6 +948,11 @@ void DomainMapper_Impl::PopSdt() xContentControlProps->setPropertyValue("Id", uno::Any(m_pSdtHelper->GetId())); } + if (!m_pSdtHelper->GetLock().isEmpty()) + { + xContentControlProps->setPropertyValue("Lock", uno::Any(m_pSdtHelper->GetLock())); + } + if (m_pSdtHelper->getControlType() == SdtControlType::checkBox) { xContentControlProps->setPropertyValue("Checkbox", uno::makeAny(true)); diff --git a/writerfilter/source/dmapper/SdtHelper.cxx b/writerfilter/source/dmapper/SdtHelper.cxx index 3e8811c69e92..9a72ef5e1f3a 100644 --- a/writerfilter/source/dmapper/SdtHelper.cxx +++ b/writerfilter/source/dmapper/SdtHelper.cxx @@ -460,6 +460,7 @@ void SdtHelper::clear() m_aAlias.clear(); m_aTag.clear(); m_nId = 0; + m_aLock.clear(); } void SdtHelper::SetPlaceholderDocPart(const OUString& rPlaceholderDocPart) @@ -485,6 +486,10 @@ void SdtHelper::SetId(sal_Int32 nId) { m_nId = nId; } sal_Int32 SdtHelper::GetId() const { return m_nId; } +void SdtHelper::SetLock(const OUString& rLock) { m_aLock = rLock; } + +const OUString& SdtHelper::GetLock() const { return m_aLock; } + } // namespace writerfilter::dmapper /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SdtHelper.hxx b/writerfilter/source/dmapper/SdtHelper.hxx index 201441547208..9504be5ddd57 100644 --- a/writerfilter/source/dmapper/SdtHelper.hxx +++ b/writerfilter/source/dmapper/SdtHelper.hxx @@ -134,6 +134,9 @@ class SdtHelper final : public virtual SvRefBase /// 's . sal_Int32 m_nId = 0; + /// 's . + OUString m_aLock; + public: explicit SdtHelper(DomainMapper_Impl& rDM_Impl, css::uno::Reference const& xContext); @@ -222,6 +225,9 @@ public: void SetId(sal_Int32 nId); sal_Int32 GetId() const; + void SetLock(const OUString& rLock); + const OUString& GetLock() const; + std::optional getValueFromDataBinding(); }; diff --git a/xmloff/qa/unit/data/content-control-alias.fodt b/xmloff/qa/unit/data/content-control-alias.fodt index 8307f682e474..8f541bef42cc 100644 --- a/xmloff/qa/unit/data/content-control-alias.fodt +++ b/xmloff/qa/unit/data/content-control-alias.fodt @@ -2,7 +2,7 @@ - test + test diff --git a/xmloff/qa/unit/text.cxx b/xmloff/qa/unit/text.cxx index e2c18f4878da..a57bbc55d4dc 100644 --- a/xmloff/qa/unit/text.cxx +++ b/xmloff/qa/unit/text.cxx @@ -849,6 +849,7 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testAliasContentControlExport) uno::Reference xContentControlProps(xContentControl, uno::UNO_QUERY); xContentControlProps->setPropertyValue("Alias", uno::Any(OUString("my alias"))); xContentControlProps->setPropertyValue("Tag", uno::Any(OUString("my tag"))); + xContentControlProps->setPropertyValue("Lock", uno::Any(OUString("unlocked"))); xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); // When exporting to ODT: @@ -870,6 +871,7 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testAliasContentControlExport) // i.e. alias was lost on export. assertXPath(pXmlDoc, "//loext:content-control", "alias", "my alias"); assertXPath(pXmlDoc, "//loext:content-control", "tag", "my tag"); + assertXPath(pXmlDoc, "//loext:content-control", "lock", "unlocked"); } CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testComboBoxContentControlImport) @@ -935,6 +937,9 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testAliasContentControlImport) OUString aTag; xContentControlProps->getPropertyValue("Tag") >>= aTag; CPPUNIT_ASSERT_EQUAL(OUString("my tag"), aTag); + OUString aLock; + xContentControlProps->getPropertyValue("Lock") >>= aLock; + CPPUNIT_ASSERT_EQUAL(OUString("sdtContentLocked"), aLock); } CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDropdownContentControlAutostyleExport) diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx index 468c727e3d52..f605ab33ac26 100644 --- a/xmloff/source/core/xmltoken.cxx +++ b/xmloff/source/core/xmltoken.cxx @@ -1231,6 +1231,7 @@ namespace xmloff::token { TOKEN( "list-style", XML_LIST_STYLE ), TOKEN( "list-style-name", XML_LIST_STYLE_NAME ), TOKEN( "ln", XML_LN ), + TOKEN( "lock", XML_LOCK ), TOKEN( "locked", XML_LOCKED ), TOKEN( "log", XML_LOG ), TOKEN( "logarithmic", XML_LOGARITHMIC ), diff --git a/xmloff/source/text/txtparae.cxx b/xmloff/source/text/txtparae.cxx index e1ae73aae8cd..e46cbfc68649 100644 --- a/xmloff/source/text/txtparae.cxx +++ b/xmloff/source/text/txtparae.cxx @@ -3988,6 +3988,13 @@ void XMLTextParagraphExport::ExportContentControl( { GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_TAG, aTag); } + + OUString aLock; + xPropertySet->getPropertyValue("Lock") >>= aLock; + if (!aLock.isEmpty()) + { + GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_LOCK, aLock); + } } SvXMLElementExport aElem(GetExport(), bExport, XML_NAMESPACE_LO_EXT, XML_CONTENT_CONTROL, false, diff --git a/xmloff/source/text/xmlcontentcontrolcontext.cxx b/xmloff/source/text/xmlcontentcontrolcontext.cxx index 0a7574625ab9..d08d3f02cf32 100644 --- a/xmloff/source/text/xmlcontentcontrolcontext.cxx +++ b/xmloff/source/text/xmlcontentcontrolcontext.cxx @@ -151,6 +151,11 @@ void XMLContentControlContext::startFastElement( m_aTag = rIter.toString(); break; } + case XML_ELEMENT(LO_EXT, XML_LOCK): + { + m_aLock = rIter.toString(); + break; + } default: XMLOFF_WARN_UNKNOWN("xmloff", rIter); } @@ -261,6 +266,11 @@ void XMLContentControlContext::endFastElement(sal_Int32) { xPropertySet->setPropertyValue("Tag", uno::Any(m_aTag)); } + + if (!m_aLock.isEmpty()) + { + xPropertySet->setPropertyValue("Lock", uno::Any(m_aLock)); + } } css::uno::Reference diff --git a/xmloff/source/text/xmlcontentcontrolcontext.hxx b/xmloff/source/text/xmlcontentcontrolcontext.hxx index 936fc03c781b..f0b1eea0b010 100644 --- a/xmloff/source/text/xmlcontentcontrolcontext.hxx +++ b/xmloff/source/text/xmlcontentcontrolcontext.hxx @@ -53,6 +53,7 @@ class XMLContentControlContext : public SvXMLImportContext bool m_bDropDown = false; OUString m_aAlias; OUString m_aTag; + OUString m_aLock; public: XMLContentControlContext(SvXMLImport& rImport, sal_Int32 nElement, XMLHints_Impl& rHints, diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt index d3f9c6f45d81..aed630735aaf 100644 --- a/xmloff/source/token/tokens.txt +++ b/xmloff/source/token/tokens.txt @@ -1131,6 +1131,7 @@ list-name list-style list-style-name ln +lock locked log logarithmic -- cgit