1
0
Fork 0
libreoffice/sw/qa/extras/mailmerge/mailmergetestbase.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

364 lines
15 KiB
C++

/* -*- 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 <sal/config.h>
#include <set>
#include <vector>
#include <swmodeltestbase.hxx>
#include <com/sun/star/text/MailMergeType.hpp>
#include <com/sun/star/sdb/CommandType.hpp>
#include <com/sun/star/table/TableBorder.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/XTextTable.hpp>
#include <com/sun/star/sdbc/XRowSet.hpp>
#include <com/sun/star/sdbcx/XRowLocate.hpp>
#include <com/sun/star/task/XJob.hpp>
#include <tools/urlobj.hxx>
#include <comphelper/sequence.hxx>
#include <wrtsh.hxx>
#include <ndtxt.hxx>
#include <pagefrm.hxx>
#include <unoprnms.hxx>
#include <dbmgr.hxx>
#include <unotxdoc.hxx>
#include <docsh.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <rootfrm.hxx>
/**
* Maps database URIs to the registered database names for quick lookups
*/
typedef std::map<OUString, OUString> 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<css::lang::XComponent>(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<OUString, OUString>(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<sdbc::XRowSet> getXResultFromDataset(const char* tablename,
const OUString& aDBName)
{
uno::Reference<sdbc::XRowSet> xCurResultSet;
uno::Reference<uno::XInterface> xInstance
= getMultiServiceFactory()->createInstance(u"com.sun.star.sdb.RowSet"_ustr);
uno::Reference<beans::XPropertySet> 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<sdbc::XRowSet> 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<task::XJob> 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<sdbcx::XRowLocate> xCurRowLocate(mxCurResultSet, uno::UNO_QUERY);
mMMargs.emplace_back(UNO_NAME_RESULT_SET, uno::Any(mxCurResultSet));
std::vector<uno::Any> 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<beans::NamedValue> 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<lang::XComponent> xTmp;
CPPUNIT_ASSERT(res >>= xTmp);
mxSwTextDocument = dynamic_cast<SwXTextDocument*>(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<SvStream> 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<css::task::XJob> mxJob;
std::vector<beans::NamedValue> mMMargs;
OUString msMailMergeDocumentURL;
OUString msMailMergeOutputURL;
OUString msMailMergeOutputPrefix;
sal_Int16 mnCurOutputType;
rtl::Reference<SwXTextDocument> mxSwTextDocument;
uno::Reference<sdbc::XRowSet> 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: */