From 6a4bf34e5afa8545894f674659370ea3c62cf295 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Mon, 13 Jul 2026 14:32:46 +0200 Subject: [PATCH] singleprov/ScriptFile: Implement XCopyableBrowseNode Implements XCopyableBrowseNode on the ScriptFile class so that all script providers based on the single-file provider can support copying. Change-Id: I14aa057ac432ff80e4a4841fbe5c19be082aa099 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208161 Tested-by: Jenkins Reviewed-by: Neil Roberts --- scripting/qa/cppunit/jsprov/jsprovtest.cxx | 130 +++++++++++++++++- .../source/inc/singleprov/directorynode.hxx | 29 ++++ .../source/inc/singleprov/scriptprovider.hxx | 16 ++- scripting/source/singleprov/scriptdir.cxx | 10 ++ scripting/source/singleprov/scriptdir.hxx | 9 +- scripting/source/singleprov/scriptfile.cxx | 62 +++++++++ scripting/source/singleprov/scriptfile.hxx | 13 ++ .../source/singleprov/scriptprovider.cxx | 14 ++ 8 files changed, 274 insertions(+), 9 deletions(-) create mode 100644 scripting/source/inc/singleprov/directorynode.hxx diff --git a/scripting/qa/cppunit/jsprov/jsprovtest.cxx b/scripting/qa/cppunit/jsprov/jsprovtest.cxx index 7f86fc1e7bab..c223f75941ba 100644 --- a/scripting/qa/cppunit/jsprov/jsprovtest.cxx +++ b/scripting/qa/cppunit/jsprov/jsprovtest.cxx @@ -12,10 +12,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -41,12 +43,14 @@ private: css::uno::Reference m_xScriptProvider; css::uno::Reference createScriptProvider(); + void createDummyMacro(const OUString& sDirectoryUri); void testEditableCreatable(); void testCreate(); void testRun(); void testUnrelatedFile(); void testFolder(); + void testCopyable(); CPPUNIT_TEST_SUITE(JsProvTest); CPPUNIT_TEST(testEditableCreatable); @@ -54,6 +58,7 @@ private: CPPUNIT_TEST(testRun); CPPUNIT_TEST(testUnrelatedFile); CPPUNIT_TEST(testFolder); + CPPUNIT_TEST(testCopyable); CPPUNIT_TEST_SUITE_END(); }; @@ -144,7 +149,7 @@ bool isCreatable(const css::uno::Reference& xN void JsProvTest::testEditableCreatable() { // Create a dummy macro in the script directory - m_xFileAccess->openFileWrite(m_xUriHelper->getRootStorageURI() + "/MyScript.js"); + createDummyMacro(m_xUriHelper->getRootStorageURI() + "/MyScript.js"); // The root node should be creatable but not editable css::uno::Reference xRootNode(m_xScriptProvider, @@ -229,11 +234,11 @@ void JsProvTest::testCreate() CPPUNIT_ASSERT_EQUAL(u"My Script"_ustr, xBrowseNode->getChildNodes()[0]->getName()); } -void JsProvTest::testRun() +void JsProvTest::createDummyMacro(const OUString& sDirectoryUri) { - // Create a dummy macro in the script directory + // Create a dummy macro in the given directory css::uno::Reference xOutput - = m_xFileAccess->openFileWrite(m_xUriHelper->getRootStorageURI() + "/MyScript.js"); + = m_xFileAccess->openFileWrite(sDirectoryUri); // Make the script insert some text in all of the writer documents static constexpr OString sSource @@ -251,6 +256,11 @@ void JsProvTest::testRun() xOutput->writeBytes(aSource); xOutput->closeOutput(); +} + +void JsProvTest::testRun() +{ + createDummyMacro(m_xUriHelper->getRootStorageURI() + "/MyScript.js"); // Get the macro as a property set css::uno::Reference xBrowseNode(m_xScriptProvider, @@ -326,6 +336,118 @@ void JsProvTest::testFolder() CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xBrowseNode->getName()); } +bool isCopyableNode(const css::uno::Reference& xNode) +{ + css::uno::Reference xCopyable(xNode, + css::uno::UNO_QUERY); + + return xCopyable.is() && xCopyable->isCopyableNode(); +} + +void JsProvTest::testCopyable() +{ + // Create a dummy macro in a subfolder of the script directory + m_xFileAccess->createFolder(m_xUriHelper->getRootStorageURI() + "/SrcDir"); + createDummyMacro(m_xUriHelper->getRootStorageURI() + "/SrcDir/MyScript.js"); + + css::uno::Reference xRootNode(m_xScriptProvider, + css::uno::UNO_QUERY_THROW); + + // The root node shouldn’t be copyable + CPPUNIT_ASSERT(!isCopyableNode(xRootNode)); + + css::uno::Reference xLibrary = xRootNode->getChildNodes()[0]; + CPPUNIT_ASSERT_EQUAL(u"SrcDir"_ustr, xLibrary->getName()); + + // The library shouldn’t be copyable + CPPUNIT_ASSERT(!isCopyableNode(xLibrary)); + + css::uno::Reference xFile = xLibrary->getChildNodes()[0]; + CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xFile->getName()); + + // File nodes are copyable + CPPUNIT_ASSERT(isCopyableNode(xFile)); + + css::uno::Reference xMacro = xFile->getChildNodes()[0]; + CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xMacro->getName()); + + // Macro nodes are not copyable + CPPUNIT_ASSERT(!isCopyableNode(xMacro)); + + css::uno::Reference xCopyable( + xFile, css::uno::UNO_QUERY_THROW); + + // We can copy to the root level of the JavaScript hierarchy + CPPUNIT_ASSERT(xCopyable->nodeCanBeCopiedTo(xRootNode)); + // We can’t copy to the same directory that the file is already in + CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xLibrary)); + // We can’t copy to a file + CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xFile)); + // or a macro + CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xMacro)); + + // We shouldn’t be able to copy to any other language providers + css::uno::Reference xMasterProviderFactory + = css::script::provider::theMasterScriptProviderFactory::get(m_xContext); + css::uno::Reference xMasterProvider( + xMasterProviderFactory->createScriptProvider(css::uno::Any(u"user"_ustr)), + css::uno::UNO_QUERY_THROW); + for (const auto& xChild : xMasterProvider->getChildNodes()) + { + if (xChild->getName() == u"JavaScript"_ustr) + continue; + CPPUNIT_ASSERT(!xCopyable->nodeCanBeCopiedTo(xChild)); + } + + // Make a new library that we can copy to + m_xFileAccess->createFolder(m_xUriHelper->getRootStorageURI() + "/DestDir"); + css::uno::Reference xOtherLibrary; + for (const auto& xChild : xRootNode->getChildNodes()) + { + if (xChild->getName() == u"DestDir"_ustr) + { + xOtherLibrary = xChild; + break; + } + } + CPPUNIT_ASSERT(xOtherLibrary.is()); + + // We should be able to copy into the new library + CPPUNIT_ASSERT(xCopyable->nodeCanBeCopiedTo(xOtherLibrary)); + css::uno::Reference xCopiedNode + = xCopyable->copyNode(xOtherLibrary); + + CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xCopiedNode->getName()); + + css::uno::Reference xCopiedMacro + = xCopiedNode->getChildNodes()[0]; + CPPUNIT_ASSERT_EQUAL(u"MyScript"_ustr, xCopiedMacro->getName()); + + // Get the URI of the copied macro + css::uno::Reference xPropertySet(xCopiedMacro, + css::uno::UNO_QUERY_THROW); + css::uno::Any xURI = xPropertySet->getPropertyValue(u"URI"_ustr); + OUString sURI; + CPPUNIT_ASSERT(xURI >>= sURI); + + // The URI should contain the new library name + CPPUNIT_ASSERT(sURI.indexOf(":DestDir|") != -1); + + // Create a document to run the script on + loadFromURL(u"private:factory/swriter"_ustr); + + // Execute it + css::uno::Sequence aScriptParams; + css::uno::Sequence aOutParamIndex; + css::uno::Sequence aOutParam; + m_xScriptProvider->getScript(sURI)->invoke(aScriptParams, aOutParamIndex, aOutParam); + + // Check that the text was inserted into our test document + css::uno::Reference xTextDocument(mxComponent, + css::uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(u"jsprovtest"_ustr, xTextDocument->getText()->getString()); +} + CPPUNIT_TEST_SUITE_REGISTRATION(JsProvTest); } // namespace diff --git a/scripting/source/inc/singleprov/directorynode.hxx b/scripting/source/inc/singleprov/directorynode.hxx new file mode 100644 index 000000000000..5df7bdf1280d --- /dev/null +++ b/scripting/source/inc/singleprov/directorynode.hxx @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include + +#include +#include + +namespace singleprovider +{ +class SingleScriptFactory; + +class SAL_LOPLUGIN_ANNOTATE("crosscast") DirectoryNode +{ +public: + virtual std::shared_ptr getScriptFactory() const = 0; + virtual std::optional getDirectoryUri() const = 0; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/scripting/source/inc/singleprov/scriptprovider.hxx b/scripting/source/inc/singleprov/scriptprovider.hxx index 3848afced4ee..cc1b43e0c1c9 100644 --- a/scripting/source/inc/singleprov/scriptprovider.hxx +++ b/scripting/source/inc/singleprov/scriptprovider.hxx @@ -9,6 +9,8 @@ #pragma once +#include + #include #include #include @@ -27,10 +29,12 @@ class ScriptBrowser; class SingleScriptFactory; class ScriptProvider - : public cppu::WeakImplHelper< - css::lang::XInitialization, css::script::browse::XBrowseNode, - css::script::browse::XCreatableBrowseNode, css::script::browse::XEditableBrowseNode, - css::beans::XPropertySet, css::script::provider::XScriptProvider, css::lang::XServiceInfo> + : public cppu::WeakImplHelper, + public DirectoryNode { public: // XInitialization @@ -79,6 +83,10 @@ public: const OUString& PropertyName, const css::uno::Reference& aListener) override; + // DirectoryNode + std::shared_ptr getScriptFactory() const override; + std::optional getDirectoryUri() const override; + static SAL_DLLPUBLIC_EXPORT OUString getImplementationNameStatic(std::u16string_view sLanguageName); static SAL_DLLPUBLIC_EXPORT css::uno::Sequence diff --git a/scripting/source/singleprov/scriptdir.cxx b/scripting/source/singleprov/scriptdir.cxx index ce7b37eb2a56..f0c0dff9e526 100644 --- a/scripting/source/singleprov/scriptdir.cxx +++ b/scripting/source/singleprov/scriptdir.cxx @@ -134,6 +134,16 @@ css::uno::Reference return new ScriptFile(m_pProviderContext, sMacroName, sUrl); } + +std::shared_ptr ScriptDir::getScriptFactory() const +{ + return m_pProviderContext->m_pSingleScriptFactory; +} + +std::optional ScriptDir::getDirectoryUri() const +{ + return std::make_optional(m_sBaseUri); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/scripting/source/singleprov/scriptdir.hxx b/scripting/source/singleprov/scriptdir.hxx index fb1b2fdbe64f..0a7cea44a77b 100644 --- a/scripting/source/singleprov/scriptdir.hxx +++ b/scripting/source/singleprov/scriptdir.hxx @@ -9,6 +9,8 @@ #pragma once +#include + #include #include #include @@ -19,7 +21,8 @@ namespace singleprovider class ProviderContext; class ScriptDir : public cppu::WeakImplHelper + css::script::browse::XCreatableBrowseNode>, + public DirectoryNode { public: ScriptDir(const std::shared_ptr& pProviderContext); @@ -38,6 +41,10 @@ public: css::uno::Reference SAL_CALL createNode(const OUString& sName) override; + // DirectoryNode + std::shared_ptr getScriptFactory() const override; + std::optional getDirectoryUri() const override; + private: OUString nameFromUrl(const OUString& sUrl); diff --git a/scripting/source/singleprov/scriptfile.cxx b/scripting/source/singleprov/scriptfile.cxx index 0cede9b24deb..42ec06978740 100644 --- a/scripting/source/singleprov/scriptfile.cxx +++ b/scripting/source/singleprov/scriptfile.cxx @@ -12,6 +12,7 @@ #include #include +#include #include #include "externaledit.hxx" @@ -45,6 +46,67 @@ sal_Bool SAL_CALL ScriptFile::hasChildNodes() { return true; } sal_Int16 SAL_CALL ScriptFile::getType() { return css::script::browse::BrowseNodeTypes::CONTAINER; } +sal_Bool SAL_CALL ScriptFile::isCopyableNode() { return true; } + +std::optional ScriptFile::getCopyDestinationUri( + const css::uno::Reference& xDest) const +{ + const DirectoryNode* pDirectoryNode = dynamic_cast(xDest.get()); + + if (!pDirectoryNode) + return std::nullopt; + + // Don’t allow copying into providers for other languages + if (pDirectoryNode->getScriptFactory()->getLanguageName() + != m_pProviderContext->m_pSingleScriptFactory->getLanguageName()) + { + return std::nullopt; + } + + std::optional sDirectoryUri = pDirectoryNode->getDirectoryUri(); + + // Don’t allow copying into the same directory + if (sDirectoryUri) + { + sal_Int32 nDirEnd = m_sBaseUri.lastIndexOf(u'/'); + + if (sDirectoryUri.value() == (nDirEnd == -1 ? m_sBaseUri : m_sBaseUri.copy(0, nDirEnd))) + return std::nullopt; + } + + return sDirectoryUri; +} + +sal_Bool SAL_CALL ScriptFile::nodeCanBeCopiedTo( + const css::uno::Reference& xParentNode) +{ + return getCopyDestinationUri(xParentNode).has_value(); +} + +css::uno::Reference SAL_CALL +ScriptFile::copyNode(const css::uno::Reference& xParentNode) +{ + std::optional sDirectoryUri = getCopyDestinationUri(xParentNode); + + if (!sDirectoryUri) + { + throw css::lang::IllegalArgumentException(u"Invalid parent node passed to copyNode"_ustr, + getXWeak(), 0); + } + + const css::uno::Reference& xFileAccess + = m_pProviderContext->m_xFileAccess; + + xFileAccess->createFolder(sDirectoryUri.value()); + + std::u16string_view sBaseName = m_sBaseUri.subView(m_sBaseUri.lastIndexOf(u'/') + 1); + OUString sTargetUri = sDirectoryUri.value() + OUStringChar('/') + sBaseName; + + xFileAccess->copy(m_sBaseUri, sTargetUri); + + return new ScriptFile(m_pProviderContext, m_sName, sTargetUri); +} + sal_Bool SAL_CALL ScriptFile::isEditableNode() { return isEditable(m_pProviderContext, m_sBaseUri); diff --git a/scripting/source/singleprov/scriptfile.hxx b/scripting/source/singleprov/scriptfile.hxx index d91f5fe73819..6a280dc587a9 100644 --- a/scripting/source/singleprov/scriptfile.hxx +++ b/scripting/source/singleprov/scriptfile.hxx @@ -10,15 +10,18 @@ #pragma once #include +#include #include #include #include +#include namespace singleprovider { class ProviderContext; class ScriptFile : public cppu::WeakImplHelper { public: @@ -32,6 +35,13 @@ public: sal_Bool SAL_CALL hasChildNodes() override; sal_Int16 SAL_CALL getType() override; + // XCopyableBrowseNode + sal_Bool SAL_CALL isCopyableNode() override; + sal_Bool SAL_CALL nodeCanBeCopiedTo( + const css::uno::Reference& xParentNode) override; + css::uno::Reference SAL_CALL + copyNode(const css::uno::Reference& xParentNode) override; + // XEditableBrowseNode sal_Bool SAL_CALL isEditableNode() override; sal_Bool SAL_CALL editNode() override; @@ -41,6 +51,9 @@ private: OUString m_sName; OUString m_sBaseUri; + + std::optional + getCopyDestinationUri(const css::uno::Reference& xDest) const; }; } diff --git a/scripting/source/singleprov/scriptprovider.cxx b/scripting/source/singleprov/scriptprovider.cxx index 42907dcccce1..1cae3d373f34 100644 --- a/scripting/source/singleprov/scriptprovider.cxx +++ b/scripting/source/singleprov/scriptprovider.cxx @@ -203,6 +203,20 @@ sal_Bool SAL_CALL ScriptProvider::editNode() return false; } +std::shared_ptr ScriptProvider::getScriptFactory() const +{ + return m_pProviderContext->m_pSingleScriptFactory; +} + +std::optional ScriptProvider::getDirectoryUri() const +{ + if (const DirectoryNode* pDirectoryNode + = dynamic_cast(m_xRootBrowser.get())) + return pDirectoryNode->getDirectoryUri(); + else + return std::nullopt; +} + OUString SAL_CALL ScriptProvider::getImplementationName() { return getImplementationNameStatic(