/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * 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/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * Maps database URIs to the registered database names for quick lookups */ typedef std::map DBuriMap; DBuriMap aDBuriMap; class MailMergeTestBase : public SwModelTestBase { public: MailMergeTestBase() : SwModelTestBase(u"/sw/qa/extras/mailmerge/data/"_ustr, u"writer8"_ustr) , mnCurOutputType(0) , maMMtestFilename(nullptr) { } virtual void tearDown() override { if (mxSwTextDocument.is()) { if (mnCurOutputType == text::MailMergeType::SHELL) mxSwTextDocument->GetDocShell()->DoClose(); else mxSwTextDocument->dispose(); } if (mxCurResultSet.is()) { css::uno::Reference(mxCurResultSet, css::uno::UNO_QUERY_THROW) ->dispose(); } SwModelTestBase::tearDown(); } /** * Helper func used by each unit test to test the 'mail merge' code. * * Registers the data source, loads the original file as reference, * initializes the mail merge job and its default argument sequence. * * The 'verify' method actually has to execute the mail merge by * calling executeMailMerge() after modifying the job arguments. */ void executeMailMergeTest(const char* filename, const char* datasource, const char* tablename, char const* const filter, int selection, const char* column) { maMMtestFilename = filename; header(); utl::TempFileNamed aTempDir(nullptr, true); aTempDir.EnableKillingFile(); const OUString aWorkDir = aTempDir.GetURL(); const OUString aURI(createFileURL(OUString::createFromAscii(datasource))); const OUString aPrefix = column ? OUString::createFromAscii(column) : u"LOMM_"_ustr; const OUString aDBName = registerDBsource(aURI, aWorkDir); initMailMergeJobAndArgs(filename, tablename, aDBName, aPrefix, aWorkDir, filter, selection, column != nullptr); verify(); mnCurOutputType = 0; } OUString registerDBsource(const OUString& aURI, const OUString& aWorkDir) { OUString aDBName; DBuriMap::const_iterator pos = aDBuriMap.find(aURI); if (pos == aDBuriMap.end()) { aDBName = SwDBManager::LoadAndRegisterDataSource(aURI, &aWorkDir); aDBuriMap.insert(std::pair(aURI, aDBName)); std::cout << "New datasource name: '" << aDBName << "'" << std::endl; } else { aDBName = pos->second; std::cout << "Old datasource name: '" << aDBName << "'" << std::endl; } CPPUNIT_ASSERT(!aDBName.isEmpty()); return aDBName; } uno::Reference getXResultFromDataset(const char* tablename, const OUString& aDBName) { uno::Reference xCurResultSet; uno::Reference xInstance = getMultiServiceFactory()->createInstance(u"com.sun.star.sdb.RowSet"_ustr); uno::Reference xRowSetPropSet(xInstance, uno::UNO_QUERY); assert(xRowSetPropSet.is() && "failed to get XPropertySet interface from RowSet"); if (xRowSetPropSet.is()) { xRowSetPropSet->setPropertyValue(u"DataSourceName"_ustr, uno::Any(aDBName)); xRowSetPropSet->setPropertyValue(u"Command"_ustr, uno::Any(OUString::createFromAscii(tablename))); xRowSetPropSet->setPropertyValue(u"CommandType"_ustr, uno::Any(sdb::CommandType::TABLE)); uno::Reference xRowSet(xInstance, uno::UNO_QUERY); if (xRowSet.is()) xRowSet->execute(); // build ResultSet from properties xCurResultSet = xRowSet; assert(xCurResultSet.is() && "failed to build ResultSet"); } return xCurResultSet; } void initMailMergeJobAndArgs(const char* filename, const char* tablename, const OUString& aDBName, const OUString& aPrefix, const OUString& aWorkDir, char const* const filter, int nDataSets, const bool bPrefixIsColumn) { uno::Reference xJob( getMultiServiceFactory()->createInstance(u"com.sun.star.text.MailMerge"_ustr), uno::UNO_QUERY_THROW); mxJob.set(xJob); mMMargs.reserve(15); mMMargs.emplace_back(UNO_NAME_OUTPUT_TYPE, uno::Any(filter ? text::MailMergeType::FILE : text::MailMergeType::SHELL)); mMMargs.emplace_back(UNO_NAME_DOCUMENT_URL, uno::Any((createFileURL(OUString::createFromAscii(filename))))); mMMargs.emplace_back(UNO_NAME_DATA_SOURCE_NAME, uno::Any(aDBName)); mMMargs.emplace_back(UNO_NAME_OUTPUT_URL, uno::Any(aWorkDir)); if (filter) { mMMargs.emplace_back(UNO_NAME_FILE_NAME_PREFIX, uno::Any(aPrefix)); mMMargs.emplace_back(UNO_NAME_SAVE_FILTER, uno::Any(OUString::createFromAscii(filter))); } if (bPrefixIsColumn) mMMargs.emplace_back(UNO_NAME_FILE_NAME_FROM_COLUMN, uno::Any(true)); if (tablename) { mMMargs.emplace_back(UNO_NAME_DAD_COMMAND_TYPE, uno::Any(sdb::CommandType::TABLE)); mMMargs.emplace_back(UNO_NAME_DAD_COMMAND, uno::Any(OUString::createFromAscii(tablename))); } if (nDataSets > 0) { mxCurResultSet = getXResultFromDataset(tablename, aDBName); uno::Reference xCurRowLocate(mxCurResultSet, uno::UNO_QUERY); mMMargs.emplace_back(UNO_NAME_RESULT_SET, uno::Any(mxCurResultSet)); std::vector vResult; vResult.reserve(nDataSets); sal_Int32 i; for (i = 0, mxCurResultSet->first(); i < nDataSets; i++, mxCurResultSet->next()) { vResult.emplace_back(xCurRowLocate->getBookmark()); } mMMargs.emplace_back(UNO_NAME_SELECTION, uno::Any(comphelper::containerToSequence(vResult))); } } void executeMailMerge(bool bDontLoadResult = false) { const uno::Sequence aSeqMailMergeArgs = comphelper::containerToSequence(mMMargs); uno::Any res = mxJob->execute(aSeqMailMergeArgs); bool bOk = true; bool bMMFilenameFromColumn = false; for (const beans::NamedValue& rArgument : aSeqMailMergeArgs) { const OUString& rName = rArgument.Name; const uno::Any& rValue = rArgument.Value; // all error checking was already done by the MM job execution if (rName == UNO_NAME_OUTPUT_URL) bOk &= rValue >>= msMailMergeOutputURL; else if (rName == UNO_NAME_FILE_NAME_PREFIX) bOk &= rValue >>= msMailMergeOutputPrefix; else if (rName == UNO_NAME_OUTPUT_TYPE) bOk &= rValue >>= mnCurOutputType; else if (rName == UNO_NAME_FILE_NAME_FROM_COLUMN) bOk &= rValue >>= bMMFilenameFromColumn; else if (rName == UNO_NAME_DOCUMENT_URL) bOk &= rValue >>= msMailMergeDocumentURL; } CPPUNIT_ASSERT(bOk); // MM via UNO just works with file names. If we load the file on // Windows before MM uses it, MM won't work, as it's already open. // Don't move the load before the mail merge execution! // (see gb_CppunitTest_use_instdir_configuration) createSwDoc(maMMtestFilename); if (mnCurOutputType == text::MailMergeType::SHELL) { uno::Reference xTmp; CPPUNIT_ASSERT(res >>= xTmp); mxSwTextDocument = dynamic_cast(xTmp.get()); CPPUNIT_ASSERT(mxSwTextDocument.is()); } else { CPPUNIT_ASSERT_EQUAL(uno::Any(true), res); if (!bMMFilenameFromColumn && !bDontLoadResult) loadMailMergeDocument(0); } } /** * Like parseExport(), but for given mail merge document. */ xmlDocUniquePtr parseMailMergeExport(const OUString& rStreamName) { if (mnCurOutputType != text::MailMergeType::FILE) return nullptr; OUString name = msMailMergeOutputPrefix + OUString::number(0) + ".odt"; std::unique_ptr pStream( parseExportStream(msMailMergeOutputURL + "/" + name, rStreamName)); return parseXmlStream(pStream.get()); } void loadMailMergeDocument(const OUString& filename) { assert(mnCurOutputType == text::MailMergeType::FILE); // Output name early, so in the case of a hang, the name of the hanging input file is visible. std::cout << filename << ","; loadFromURL(msMailMergeOutputURL + "/" + filename); calcLayout(); } /** Loads number-th document from mail merge. Requires file output from mail merge. */ void loadMailMergeDocument(int number, char const* const ext = ".odt") { OUString name; if (!msMailMergeOutputPrefix.isEmpty()) name = msMailMergeOutputPrefix; else { INetURLObject aURLObj; aURLObj.SetSmartProtocol(INetProtocol::File); aURLObj.SetSmartURL(msMailMergeDocumentURL); name = aURLObj.GetBase(); } name += OUString::number(number) + OStringToOUString(std::string_view(ext, strlen(ext)), RTL_TEXTENCODING_ASCII_US); loadMailMergeDocument(name); } // Returns page number of the first page of a MM document inside the large MM document (used in the SHELL case). int documentStartPageNumber(int document) const { // See documentStartPageNumber() . CPPUNIT_ASSERT(mxSwTextDocument); SwWrtShell* shell = mxSwTextDocument->GetDocShell()->GetWrtShell(); IDocumentMarkAccess* marks = shell->GetDoc()->getIDocumentMarkAccess(); // Unfortunately, the pages are marked using UNO bookmarks, which have internals names, so they cannot be referred to by their names. // Assume that there are no other UNO bookmarks than the ones used by mail merge, and that they are in the sorted order. IDocumentMarkAccess::const_iterator mark; int pos = 0; for (mark = marks->getAllMarksBegin(); mark != marks->getAllMarksEnd() && pos < document; ++mark) { if (IDocumentMarkAccess::GetType(**mark) == IDocumentMarkAccess::MarkType::UNO_BOOKMARK) ++pos; } CPPUNIT_ASSERT_EQUAL(document, pos); sal_uInt16 page, dummy; shell->Push(); shell->GotoMark(*mark); shell->GetPageNum(page, dummy); shell->Pop(SwCursorShell::PopMode::DeleteCurrent); return page; } protected: uno::Reference mxJob; std::vector mMMargs; OUString msMailMergeDocumentURL; OUString msMailMergeOutputURL; OUString msMailMergeOutputPrefix; sal_Int16 mnCurOutputType; rtl::Reference mxSwTextDocument; uno::Reference mxCurResultSet; const char* maMMtestFilename; }; #define DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, filter, BaseClass, \ selection, column) \ class TestName : public BaseClass \ { \ public: \ CPPUNIT_TEST_SUITE(TestName); \ CPPUNIT_TEST(MailMerge); \ CPPUNIT_TEST_SUITE_END(); \ \ void MailMerge() \ { \ executeMailMergeTest(filename, datasource, tablename, filter, selection, column); \ } \ void verify() override; \ }; \ CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \ void TestName::verify() // Will generate the resulting document in mxMMDocument. #define DECLARE_SHELL_MAILMERGE_TEST(TestName, filename, datasource, tablename) \ DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, nullptr, MailMergeTestBase, \ 0, nullptr) // Will generate documents as files, use loadMailMergeDocument(). #define DECLARE_FILE_MAILMERGE_TEST(TestName, filename, datasource, tablename) \ DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, "writer8", \ MailMergeTestBase, 0, nullptr) #define DECLARE_SHELL_MAILMERGE_TEST_SELECTION(TestName, filename, datasource, tablename, \ selection) \ DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, nullptr, MailMergeTestBase, \ selection, nullptr) #define DECLARE_FILE_MAILMERGE_TEST_COLUMN(TestName, filename, datasource, tablename, column) \ DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, "writer8", \ MailMergeTestBase, 0, column) /* vim:set shiftwidth=4 softtabstop=4 expandtab: */