comphelper/qa/unit/propertyvalue.cxx | 71 ++++++++++++++++++++ comphelper/source/misc/sequenceashashmap.cxx | 14 +++ desktop/source/lib/init.cxx | 14 +++ include/vcl/ITiledRenderable.hxx | 5 + sw/inc/unotxdoc.hxx | 3 sw/qa/uibase/uno/uno.cxx | 50 ++++++++++++++ sw/source/uibase/uno/unotxdoc.cxx | 95 +++++++++++++++++++++++++++ 7 files changed, 252 insertions(+)
New commits: commit 6650a890673ad66b9d0d5bfde0e06ae2e9c3f909 Author: Miklos Vajna <[email protected]> AuthorDate: Fri Nov 25 16:32:37 2022 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Tue Nov 29 08:08:05 2022 +0100 comphelper: support property values arrays in JsonToPropertyValues() Needed for an uncoming .uno:TextFormFields uno command where one of the parameters has the []com.sun.star.beans.PropertyValues type at an UNO level, and we can't provide JSON at the moment that would express that. (cherry picked from commit 1e83197fdd4263ca4817a6ac16f274aaee3e66fd) Conflicts: comphelper/source/misc/sequenceashashmap.cxx Change-Id: I288a540b2fcac0e5a4a82bca235199c559ba2d0c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143409 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/comphelper/qa/unit/propertyvalue.cxx b/comphelper/qa/unit/propertyvalue.cxx index 40f60bb0463d..738022917e9d 100644 --- a/comphelper/qa/unit/propertyvalue.cxx +++ b/comphelper/qa/unit/propertyvalue.cxx @@ -14,9 +14,12 @@ #include <cppunit/extensions/HelperMacros.h> #include <comphelper/propertyvalue.hxx> +#include <comphelper/propertysequence.hxx> #include <cppu/unotype.hxx> #include <o3tl/any.hxx> +using namespace com::sun::star; + namespace { class MakePropertyValueTest : public CppUnit::TestFixture @@ -25,6 +28,7 @@ class MakePropertyValueTest : public CppUnit::TestFixture CPPUNIT_TEST(testLvalue); CPPUNIT_TEST(testRvalue); CPPUNIT_TEST(testBitField); + CPPUNIT_TEST(testJson); CPPUNIT_TEST_SUITE_END(); void testLvalue() @@ -52,6 +56,73 @@ class MakePropertyValueTest : public CppUnit::TestFixture CPPUNIT_ASSERT_EQUAL(cppu::UnoType<bool>::get(), v.Value.getValueType()); CPPUNIT_ASSERT_EQUAL(false, *o3tl::doAccess<bool>(v.Value)); } + + void testJson() + { + std::vector<beans::PropertyValue> aRet = comphelper::JsonToPropertyValues(R"json( +{ + "FieldType": { + "type": "string", + "value": "vnd.oasis.opendocument.field.UNHANDLED" + }, + "FieldCommandPrefix": { + "type": "string", + "value": "ADDIN ZOTERO_ITEM" + }, + "Fields": { + "type": "[][]com.sun.star.beans.PropertyValue", + "value": [ + { + "FieldType": { + "type": "string", + "value": "vnd.oasis.opendocument.field.UNHANDLED" + }, + "FieldCommand": { + "type": "string", + "value": "ADDIN ZOTERO_ITEM new command 1" + }, + "Fields": { + "type": "string", + "value": "new result 1" + } + }, + { + "FieldType": { + "type": "string", + "value": "vnd.oasis.opendocument.field.UNHANDLED" + }, + "FieldCommandPrefix": { + "type": "string", + "value": "ADDIN ZOTERO_ITEM new command 2" + }, + "Fields": { + "type": "string", + "value": "new result 2" + } + } + ] + } +} +)json"); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aRet.size()); + beans::PropertyValue aFirst = aRet[0]; + CPPUNIT_ASSERT_EQUAL(OUString("FieldType"), aFirst.Name); + CPPUNIT_ASSERT_EQUAL(OUString("vnd.oasis.opendocument.field.UNHANDLED"), + aFirst.Value.get<OUString>()); + beans::PropertyValue aSecond = aRet[1]; + CPPUNIT_ASSERT_EQUAL(OUString("FieldCommandPrefix"), aSecond.Name); + CPPUNIT_ASSERT_EQUAL(OUString("ADDIN ZOTERO_ITEM"), aSecond.Value.get<OUString>()); + beans::PropertyValue aThird = aRet[2]; + CPPUNIT_ASSERT_EQUAL(OUString("Fields"), aThird.Name); + uno::Sequence<uno::Sequence<beans::PropertyValue>> aSeqs; + aThird.Value >>= aSeqs; + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), aSeqs.getLength()); + uno::Sequence<beans::PropertyValue> aFirstSeq = aSeqs[0]; + CPPUNIT_ASSERT_EQUAL(OUString("FieldType"), aFirstSeq[0].Name); + CPPUNIT_ASSERT_EQUAL(OUString("FieldCommand"), aFirstSeq[1].Name); + CPPUNIT_ASSERT_EQUAL(OUString("ADDIN ZOTERO_ITEM new command 1"), + aFirstSeq[1].Value.get<OUString>()); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(MakePropertyValueTest); diff --git a/comphelper/source/misc/sequenceashashmap.cxx b/comphelper/source/misc/sequenceashashmap.cxx index c6ac57326935..957c76feceac 100644 --- a/comphelper/source/misc/sequenceashashmap.cxx +++ b/comphelper/source/misc/sequenceashashmap.cxx @@ -30,6 +30,7 @@ #include <comphelper/processfactory.hxx> #include <comphelper/propertysequence.hxx> #include <sal/log.hxx> +#include <comphelper/sequence.hxx> using namespace com::sun::star; @@ -364,6 +365,19 @@ 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::vector<uno::Sequence<beans::PropertyValue>> aSeqs; + for (const auto& rItem : aNodeValue) + { + std::stringstream s; + boost::property_tree::write_json(s, rItem.second); + std::vector<beans::PropertyValue> aPropertyValues = JsonToPropertyValues(s.str().c_str()); + aSeqs.push_back(comphelper::containerToSequence(aPropertyValues)); + } + aValue.Value <<= comphelper::containerToSequence(aSeqs); + } else SAL_WARN("comphelper", "JsonToPropertyValues: unhandled type '" << rType << "'"); aArguments.push_back(aValue); commit 429050f77e1e62a1befd1439d891df42eaccdccc Author: Miklos Vajna <[email protected]> AuthorDate: Fri Nov 25 12:30:56 2022 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Tue Nov 29 08:07:50 2022 +0100 sw lok, .uno:TextFormFields: expose field code of fieldmarks The fieldmarks in a document were kind of invisible for LOK clients previously. The Zotero use-case requires a way to fetch certain types of fieldmarks from a document, to be able to update them and then write back those updated versions to the document later. Fix this by introducing a new .uno:TextFormFields, you can get its value using the getCommandValues() LOK API. This allows filtering for a certain field command prefix, which is generic, but e.g. in the Zotero case allows getting the citations or the bibliography. The returned JSON is an array of matching fieldmarks, containing their type and field command. It seems there is no way to return the field result, till the motivation is to just update that field result. (If there will be need, the field result can be added.) Do this in a way that next time we add a Writer-specific command (to be able to return its values), there will be no need to touch include/vcl/ITiledRenderable.hxx, causing a large rebuild. (cherry picked from commit 24219cc1e9829f82a533667aef0f51b6a7df6fc2) Conflicts: sw/qa/uibase/uno/uno.cxx Change-Id: I2ef1159bec4034bbdd6b4ba00715a69423106edd Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143408 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 93552811ab69..8f44e686417c 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -5725,6 +5725,7 @@ static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCo static constexpr OStringLiteral aSheetGeometryData(".uno:SheetGeometryData"); static constexpr OStringLiteral aCellCursor(".uno:CellCursor"); static constexpr OStringLiteral aFontSubset(".uno:FontSubset&name="); + static constexpr OStringLiteral aTextFormFields(".uno:TextFormFields"); if (!strcmp(pCommand, ".uno:LanguageStatus")) { @@ -5901,6 +5902,19 @@ static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCo { return getFontSubset(std::string_view(pCommand + aFontSubset.getLength())); } + else if (aCommand.startsWith(aTextFormFields)) + { + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg("Document doesn't support tiled rendering"); + return nullptr; + } + + tools::JsonWriter aJsonWriter; + pDoc->getCommandValues(aJsonWriter, aCommand); + return aJsonWriter.extractData(); + } else { SetLastExceptionMsg("Unknown command, no values returned"); diff --git a/include/vcl/ITiledRenderable.hxx b/include/vcl/ITiledRenderable.hxx index 618c61060063..3ce1b9042908 100644 --- a/include/vcl/ITiledRenderable.hxx +++ b/include/vcl/ITiledRenderable.hxx @@ -369,6 +369,11 @@ public: * Allow / disable drawing current text edit (used in Impress for slide previews) */ virtual void setPaintTextEdit(bool) {} + + /// Returns a json mapping of the possible values for the given command. + virtual void getCommandValues(tools::JsonWriter& /*rJsonWriter*/, const OString& /*rCommand*/) + { + } }; } // namespace vcl diff --git a/sw/inc/unotxdoc.hxx b/sw/inc/unotxdoc.hxx index 4e4d3f160eec..fabab635edda 100644 --- a/sw/inc/unotxdoc.hxx +++ b/sw/inc/unotxdoc.hxx @@ -458,6 +458,9 @@ public: /// @see vcl::ITiledRenderable::executeContentControlEvent(). void executeContentControlEvent(const StringMap& aArguments) override; + /// @see vcl::ITiledRenderable::getCommandValues(). + void getCommandValues(tools::JsonWriter& rJsonWriter, const OString& rCommand) override; + void Invalidate(); void Reactivate(SwDocShell* pNewDocShell); SwXDocumentPropertyHelper * GetPropertyHelper (); diff --git a/sw/qa/uibase/uno/uno.cxx b/sw/qa/uibase/uno/uno.cxx index f4b337d8f9d2..18390d2c03f6 100644 --- a/sw/qa/uibase/uno/uno.cxx +++ b/sw/qa/uibase/uno/uno.cxx @@ -9,11 +9,16 @@ #include <swmodeltestbase.hxx> +#include <boost/property_tree/json_parser.hpp> + #include <com/sun/star/frame/XModel2.hpp> #include <com/sun/star/text/XTextViewTextRangeSupplier.hpp> #include <com/sun/star/util/XCloseable.hpp> #include <vcl/scheduler.hxx> +#include <tools/json_writer.hxx> +#include <comphelper/propertyvalue.hxx> +#include <xmloff/odffields.hxx> #include <docsh.hxx> #include <edtwin.hxx> @@ -22,6 +27,7 @@ #include <wrtsh.hxx> constexpr OUStringLiteral DATA_DIRECTORY = u"/sw/qa/uibase/uno/data/"; +#include <unotxdoc.hxx> /// Covers sw/source/uibase/uno/ fixes. class SwUibaseUnoTest : public SwModelTestBase @@ -90,6 +96,50 @@ CPPUNIT_TEST_FIXTURE(SwUibaseUnoTest, testCreateTextRangeByPixelPosition) CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), nActual); } +CPPUNIT_TEST_FIXTURE(SwUibaseUnoTest, testGetTextFormFields) +{ + // Given a document with 3 fieldmarks: 2 zotero items and a zotero + // bibliography: + createSwDoc(); + for (int i = 0; i < 2; ++i) + { + uno::Sequence<css::beans::PropertyValue> aArgs = { + comphelper::makePropertyValue("FieldType", uno::Any(OUString(ODF_UNHANDLED))), + comphelper::makePropertyValue("FieldCommand", + uno::Any(OUString("ADDIN ZOTERO_ITEM foo bar"))), + comphelper::makePropertyValue("FieldResult", uno::Any(OUString("result"))), + }; + dispatchCommand(mxComponent, ".uno:TextFormField", aArgs); + } + { + uno::Sequence<css::beans::PropertyValue> aArgs = { + comphelper::makePropertyValue("FieldType", uno::Any(OUString(ODF_UNHANDLED))), + comphelper::makePropertyValue("FieldCommand", + uno::Any(OUString("ADDIN ZOTERO_BIBL foo bar"))), + comphelper::makePropertyValue("FieldResult", + uno::Any(OUString("<p>aaa</p><p>bbb</p>"))), + }; + dispatchCommand(mxComponent, ".uno:TextFormField", aArgs); + } + + // When getting the zotero items: + tools::JsonWriter aJsonWriter; + OString aCommand(".uno:TextFormFields?type=vnd.oasis.opendocument.field.UNHANDLED&" + "commandPrefix=ADDIN%20ZOTERO_ITEM"); + auto pXTextDocument = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + pXTextDocument->getCommandValues(aJsonWriter, aCommand); + + // Then make sure we find the 2 items and ignore the bibliography: + std::unique_ptr<char[], o3tl::free_delete> pJSON(aJsonWriter.extractData()); + std::stringstream aStream(pJSON.get()); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + // Without the needed PixelToLogic() call in place, this test would have failed with: + // - No such node (fields) + // i.e. the returned JSON was just empty. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTree.get_child("fields").count("")); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/uno/unotxdoc.cxx b/sw/source/uibase/uno/unotxdoc.cxx index 9ab446907fdf..a13bd7b2ab42 100644 --- a/sw/source/uibase/uno/unotxdoc.cxx +++ b/sw/source/uibase/uno/unotxdoc.cxx @@ -163,6 +163,7 @@ #include <tools/json_writer.hxx> #include <svx/svdpage.hxx> +#include <o3tl/string_view.hxx> #include <IDocumentOutlineNodes.hxx> #include <SearchResultLocator.hxx> @@ -3509,6 +3510,100 @@ void SwXTextDocument::executeContentControlEvent(const StringMap& rArguments) } } +namespace +{ +/// Implements getCommandValues(".uno:TextFormFields"). +/// +/// Parameters: +/// +/// - type: e.g. ODF_UNHANDLED +/// - commandPrefix: field comment prefix not not return all fieldmarks +void GetTextFormFields(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell, + const std::map<OUString, OUString>& rArguments) +{ + OUString aType; + OUString aCommandPrefix; + { + auto it = rArguments.find("type"); + if (it != rArguments.end()) + { + aType = it->second; + } + + it = rArguments.find("commandPrefix"); + if (it != rArguments.end()) + { + aCommandPrefix = it->second; + } + } + + SwDoc* pDoc = pDocShell->GetDoc(); + IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); + tools::ScopedJsonWriterArray aFields = rJsonWriter.startArray("fields"); + for (auto it = pMarkAccess->getFieldmarksBegin(); it != pMarkAccess->getFieldmarksEnd(); ++it) + { + auto pFieldmark = dynamic_cast<sw::mark::IFieldmark*>(*it); + assert(pFieldmark); + if (pFieldmark->GetFieldname() != aType) + { + continue; + } + + auto itParam = pFieldmark->GetParameters()->find(ODF_CODE_PARAM); + if (itParam == pFieldmark->GetParameters()->end()) + { + continue; + } + + OUString aCommand; + itParam->second >>= aCommand; + if (!aCommand.startsWith(aCommandPrefix)) + { + continue; + } + + tools::ScopedJsonWriterStruct aField = rJsonWriter.startStruct(); + rJsonWriter.put("type", aType); + rJsonWriter.put("command", aCommand); + } +} +} + +void SwXTextDocument::getCommandValues(tools::JsonWriter& rJsonWriter, const OString& rCommand) +{ + std::map<OUString, OUString> aMap; + + static constexpr OStringLiteral aTextFormFields(".uno:TextFormFields"); + + if (o3tl::starts_with(rCommand, aTextFormFields)) + { + if (rCommand.getLength() > aTextFormFields.getLength()) + { + OString aArguments = rCommand.copy(aTextFormFields.getLength() + 1); + sal_Int32 nParamIndex = 0; + do + { + OString aParamToken = aArguments.getToken(0, '&', nParamIndex); + sal_Int32 nIndex = 0; + OUString aKey; + OUString aValue; + do + { + OString aToken = aParamToken.getToken(0, '=', nIndex); + if (aKey.isEmpty()) + aKey = OUString::fromUtf8(aToken); + else + aValue = OUString::fromUtf8(aToken); + } while (nIndex >= 0); + OUString aDecodedValue + = INetURLObject::decode(aValue, INetURLObject::DecodeMechanism::WithCharset); + aMap[aKey] = aDecodedValue; + } while (nParamIndex >= 0); + } + GetTextFormFields(rJsonWriter, m_pDocShell, aMap); + } +} + int SwXTextDocument::getPart() { SolarMutexGuard aGuard;
