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

Reply via email to