comphelper/source/misc/sequenceashashmap.cxx | 8 +++ sfx2/qa/cppunit/doc.cxx | 60 +++++++++++++++++++++++++++ sfx2/sdi/sfx.sdi | 2 sfx2/source/doc/objserv.cxx | 57 +++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-)
New commits: commit afb362c60a18243621834dcf2b30672be6eed76f Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Wed Nov 30 13:58:39 2022 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Wed Nov 30 16:51:00 2022 +0100 sfx2: extend .uno:SetDocumentProperties to update custom doc props Scripting clients (like the LOK API) had a way to get all custom properties where the name matches a certain prefix, but setting such properties was not possible. .uno:SetDocumentProperties can already show a dialog to edit properties interactively and had a parameter to set some properties in a non-interactive way, but there doesn't seem to be a way to influence custom properties there without using the internal API. Fix the problem by adding a new UpdatedProperties parameter that allows removing all old custom properties matching the prefix and adding new ones with a single UNO command dispatch. This is meant to be the write side of the reading commit 5e8f6dcb8ce00d2d5e35b3cf5654187b3068276c (sw lok, .uno:SetDocumentProperties: expose value of custom document properties, 2022-11-29). Change-Id: Ib7450d4d21285d9a73758e1c172543521fc07cef Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143491 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/comphelper/source/misc/sequenceashashmap.cxx b/comphelper/source/misc/sequenceashashmap.cxx index b18202aab5f5..3bbce289ffe0 100644 --- a/comphelper/source/misc/sequenceashashmap.cxx +++ b/comphelper/source/misc/sequenceashashmap.cxx @@ -367,6 +367,14 @@ std::vector<css::beans::PropertyValue> JsonToPropertyValues(const OString& rJson aValue.Value <<= aSeq; } } + else if (rType == "[]com.sun.star.beans.PropertyValue") + { + aNodeValue = rPair.second.get_child("value", aNodeNull); + std::stringstream s; + boost::property_tree::write_json(s, aNodeValue); + std::vector<beans::PropertyValue> aPropertyValues = JsonToPropertyValues(s.str().c_str()); + aValue.Value <<= comphelper::containerToSequence(aPropertyValues); + } else if (rType == "[][]com.sun.star.beans.PropertyValue") { aNodeValue = rPair.second.get_child("value", aNodeNull); diff --git a/sfx2/qa/cppunit/doc.cxx b/sfx2/qa/cppunit/doc.cxx index 9ef48043b582..d7538925113e 100644 --- a/sfx2/qa/cppunit/doc.cxx +++ b/sfx2/qa/cppunit/doc.cxx @@ -11,11 +11,16 @@ #include <com/sun/star/view/XSelectionSupplier.hpp> #include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/XPropertyAccess.hpp> #include <comphelper/propertyvalue.hxx> #include <sfx2/objsh.hxx> #include <sfx2/sfxbasemodel.hxx> #include <osl/file.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/sequence.hxx> using namespace com::sun::star; @@ -84,6 +89,61 @@ CPPUNIT_TEST_FIXTURE(Test, testTempFilePath) // "test%25C3%25Bf" instead of a directory named "test%C3%Bf". pBaseModel->storeToURL(aPdfTarget, aPdfArgs); } + +CPPUNIT_TEST_FIXTURE(Test, testSetDocumentPropertiesUpdate) +{ + // Given a document with 3 custom props, 2 Zotero ones and an other: + mxComponent = loadFromDesktop("private:factory/swriter"); + auto pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get()); + CPPUNIT_ASSERT(pBaseModel); + uno::Reference<document::XDocumentProperties> xDP = pBaseModel->getDocumentProperties(); + uno::Reference<beans::XPropertyContainer> xUDP = xDP->getUserDefinedProperties(); + xUDP->addProperty("ZOTERO_PREF_1", beans::PropertyAttribute::REMOVABLE, + uno::Any(OUString("foo"))); + xUDP->addProperty("ZOTERO_PREF_2", beans::PropertyAttribute::REMOVABLE, + uno::Any(OUString("bar"))); + xUDP->addProperty("OTHER", beans::PropertyAttribute::REMOVABLE, uno::Any(OUString("baz"))); + + // When updating the Zotero ones (1 update, 1 removal): + std::vector<beans::PropertyValue> aArgsVec = comphelper::JsonToPropertyValues(R"json( +{ + "UpdatedProperties": { + "type": "[]com.sun.star.beans.PropertyValue", + "value": { + "NamePrefix": { + "type": "string", + "value": "ZOTERO_PREF_" + }, + "UserDefinedProperties": { + "type": "[]com.sun.star.beans.PropertyValue", + "value": { + "ZOTERO_PREF_1": { + "type": "string", + "value": "test" + } + } + } + } + } +} +)json"); + uno::Sequence<beans::PropertyValue> aArgs = comphelper::containerToSequence(aArgsVec); + dispatchCommand(mxComponent, ".uno:SetDocumentProperties", aArgs); + + // Then make sure that OTHER is still there and that ZOTERO_PREF_1 + ZOTERO_PREF_2 gets updated + // to the new value of a single ZOTERO_PREF_1: + uno::Reference<beans::XPropertyAccess> xUDPAccess(xUDP, uno::UNO_QUERY); + comphelper::SequenceAsHashMap aMap(xUDPAccess->getPropertyValues()); + auto it = aMap.find("ZOTERO_PREF_1"); + CPPUNIT_ASSERT(it != aMap.end()); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: test + // - Actual : foo + // i.e. ZOTERO_PREF_1 was not updated. + CPPUNIT_ASSERT_EQUAL(OUString("test"), it->second.get<OUString>()); + CPPUNIT_ASSERT(bool(aMap.find("ZOTERO_PREF_2") == aMap.end())); + CPPUNIT_ASSERT(aMap.find("OTHER") != aMap.end()); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sfx2/sdi/sfx.sdi b/sfx2/sdi/sfx.sdi index 5da078252261..d98e8e0b93bc 100644 --- a/sfx2/sdi/sfx.sdi +++ b/sfx2/sdi/sfx.sdi @@ -3308,7 +3308,7 @@ SfxBoolItem PrintPreview SID_PRINTPREVIEW SfxVoidItem SetDocumentProperties SID_DOCINFO -(SfxDocumentInfoItem Properties SID_DOCINFO) +(SfxDocumentInfoItem Properties SID_DOCINFO, SfxUnoAnyItem UpdatedProperties FN_PARAM_1) [ AutoUpdate = FALSE, FastCall = FALSE, diff --git a/sfx2/source/doc/objserv.cxx b/sfx2/source/doc/objserv.cxx index ed8c0bf81a5a..bb2a01b77620 100644 --- a/sfx2/source/doc/objserv.cxx +++ b/sfx2/source/doc/objserv.cxx @@ -24,6 +24,7 @@ #include <com/sun/star/util/CloseVetoException.hpp> #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> #include <com/sun/star/document/XCmisDocument.hpp> #include <com/sun/star/drawing/LineStyle.hpp> #include <com/sun/star/lang/XServiceInfo.hpp> @@ -467,6 +468,56 @@ static void sendErrorToLOK(ErrCode error) SfxViewShell::Current()->libreOfficeKitViewCallback(LOK_CALLBACK_ERROR, aStream.str().c_str()); } +namespace +{ +void SetDocProperties(const uno::Reference<document::XDocumentProperties>& xDP, + const uno::Sequence<beans::PropertyValue>& rUpdatedProperties) +{ + comphelper::SequenceAsHashMap aMap(rUpdatedProperties); + OUString aNamePrefix; + auto it = aMap.find("NamePrefix"); + if (it != aMap.end()) + { + it->second >>= aNamePrefix; + } + + uno::Sequence<beans::PropertyValue> aUserDefinedProperties; + it = aMap.find("UserDefinedProperties"); + if (it != aMap.end()) + { + it->second >>= aUserDefinedProperties; + } + + uno::Reference<beans::XPropertyContainer> xUDP = xDP->getUserDefinedProperties(); + if (!aNamePrefix.isEmpty()) + { + uno::Reference<beans::XPropertySet> xSet(xUDP, UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xSetInfo = xSet->getPropertySetInfo(); + const uno::Sequence<beans::Property> aProperties = xSetInfo->getProperties(); + for (const auto& rProperty : aProperties) + { + if (!rProperty.Name.startsWith(aNamePrefix)) + { + continue; + } + + if (!(rProperty.Attributes & beans::PropertyAttribute::REMOVABLE)) + { + continue; + } + + xUDP->removeProperty(rProperty.Name); + } + } + + for (const auto& rUserDefinedProperty : aUserDefinedProperties) + { + xUDP->addProperty(rUserDefinedProperty.Name, beans::PropertyAttribute::REMOVABLE, + rUserDefinedProperty.Value); + } +} +} + void SfxObjectShell::ExecFile_Impl(SfxRequest &rReq) { weld::Window* pDialogParent = rReq.GetFrameWeld(); @@ -571,6 +622,12 @@ void SfxObjectShell::ExecFile_Impl(SfxRequest &rReq) SetUseUserData( pDocInfItem->IsUseUserData() ); SetUseThumbnailSave( pDocInfItem->IsUseThumbnailSave() ); } + else if (const SfxUnoAnyItem* pItem = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_1)) + { + uno::Sequence<beans::PropertyValue> aUpdatedProperties; + pItem->GetValue() >>= aUpdatedProperties; + SetDocProperties(getDocProperties(), aUpdatedProperties); + } else { // no argument containing DocInfo; check optional arguments