1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <sal/config.h>
15 #include <swmodeltestbase.hxx>
17 #include <com/sun/star/text/MailMergeType.hpp>
18 #include <com/sun/star/sdb/CommandType.hpp>
19 #include <com/sun/star/table/TableBorder.hpp>
20 #include <com/sun/star/text/TextContentAnchorType.hpp>
21 #include <com/sun/star/text/XTextTable.hpp>
22 #include <com/sun/star/sdbc/XRowSet.hpp>
23 #include <com/sun/star/sdbcx/XRowLocate.hpp>
24 #include <com/sun/star/task/XJob.hpp>
26 #include <tools/urlobj.hxx>
27 #include <comphelper/sequence.hxx>
31 #include <pagefrm.hxx>
32 #include <unoprnms.hxx>
34 #include <unotxdoc.hxx>
36 #include <IDocumentLayoutAccess.hxx>
37 #include <rootfrm.hxx>
40 * Maps database URIs to the registered database names for quick lookups
42 typedef std::map
<OUString
, OUString
> DBuriMap
;
45 class MailMergeTestBase
: public SwModelTestBase
49 : SwModelTestBase(u
"/sw/qa/extras/mailmerge/data/"_ustr
, u
"writer8"_ustr
)
51 , maMMtestFilename(nullptr)
55 virtual void tearDown() override
57 if (mxSwTextDocument
.is())
59 if (mnCurOutputType
== text::MailMergeType::SHELL
)
60 mxSwTextDocument
->GetDocShell()->DoClose();
62 mxSwTextDocument
->dispose();
64 if (mxCurResultSet
.is())
66 css::uno::Reference
<css::lang::XComponent
>(mxCurResultSet
, css::uno::UNO_QUERY_THROW
)
69 SwModelTestBase::tearDown();
73 * Helper func used by each unit test to test the 'mail merge' code.
75 * Registers the data source, loads the original file as reference,
76 * initializes the mail merge job and its default argument sequence.
78 * The 'verify' method actually has to execute the mail merge by
79 * calling executeMailMerge() after modifying the job arguments.
81 void executeMailMergeTest(const char* filename
, const char* datasource
, const char* tablename
,
82 char const* const filter
, int selection
, const char* column
)
84 maMMtestFilename
= filename
;
87 utl::TempFileNamed
aTempDir(nullptr, true);
88 aTempDir
.EnableKillingFile();
89 const OUString aWorkDir
= aTempDir
.GetURL();
90 const OUString
aURI(createFileURL(OUString::createFromAscii(datasource
)));
91 const OUString aPrefix
= column
? OUString::createFromAscii(column
) : u
"LOMM_"_ustr
;
92 const OUString aDBName
= registerDBsource(aURI
, aWorkDir
);
93 initMailMergeJobAndArgs(filename
, tablename
, aDBName
, aPrefix
, aWorkDir
, filter
, selection
,
101 OUString
registerDBsource(const OUString
& aURI
, const OUString
& aWorkDir
)
104 DBuriMap::const_iterator pos
= aDBuriMap
.find(aURI
);
105 if (pos
== aDBuriMap
.end())
107 aDBName
= SwDBManager::LoadAndRegisterDataSource(aURI
, &aWorkDir
);
108 aDBuriMap
.insert(std::pair
<OUString
, OUString
>(aURI
, aDBName
));
109 std::cout
<< "New datasource name: '" << aDBName
<< "'" << std::endl
;
113 aDBName
= pos
->second
;
114 std::cout
<< "Old datasource name: '" << aDBName
<< "'" << std::endl
;
116 CPPUNIT_ASSERT(!aDBName
.isEmpty());
120 uno::Reference
<sdbc::XRowSet
> getXResultFromDataset(const char* tablename
,
121 const OUString
& aDBName
)
123 uno::Reference
<sdbc::XRowSet
> xCurResultSet
;
124 uno::Reference
<uno::XInterface
> xInstance
125 = getMultiServiceFactory()->createInstance(u
"com.sun.star.sdb.RowSet"_ustr
);
126 uno::Reference
<beans::XPropertySet
> xRowSetPropSet(xInstance
, uno::UNO_QUERY
);
127 assert(xRowSetPropSet
.is() && "failed to get XPropertySet interface from RowSet");
128 if (xRowSetPropSet
.is())
130 xRowSetPropSet
->setPropertyValue(u
"DataSourceName"_ustr
, uno::Any(aDBName
));
131 xRowSetPropSet
->setPropertyValue(u
"Command"_ustr
,
132 uno::Any(OUString::createFromAscii(tablename
)));
133 xRowSetPropSet
->setPropertyValue(u
"CommandType"_ustr
,
134 uno::Any(sdb::CommandType::TABLE
));
136 uno::Reference
<sdbc::XRowSet
> xRowSet(xInstance
, uno::UNO_QUERY
);
138 xRowSet
->execute(); // build ResultSet from properties
139 xCurResultSet
= xRowSet
;
140 assert(xCurResultSet
.is() && "failed to build ResultSet");
142 return xCurResultSet
;
145 void initMailMergeJobAndArgs(const char* filename
, const char* tablename
,
146 const OUString
& aDBName
, const OUString
& aPrefix
,
147 const OUString
& aWorkDir
, char const* const filter
, int nDataSets
,
148 const bool bPrefixIsColumn
)
150 uno::Reference
<task::XJob
> xJob(
151 getMultiServiceFactory()->createInstance(u
"com.sun.star.text.MailMerge"_ustr
),
152 uno::UNO_QUERY_THROW
);
157 mMMargs
.emplace_back(UNO_NAME_OUTPUT_TYPE
, uno::Any(filter
? text::MailMergeType::FILE
158 : text::MailMergeType::SHELL
));
159 mMMargs
.emplace_back(UNO_NAME_DOCUMENT_URL
,
160 uno::Any((createFileURL(OUString::createFromAscii(filename
)))));
161 mMMargs
.emplace_back(UNO_NAME_DATA_SOURCE_NAME
, uno::Any(aDBName
));
162 mMMargs
.emplace_back(UNO_NAME_OUTPUT_URL
, uno::Any(aWorkDir
));
165 mMMargs
.emplace_back(UNO_NAME_FILE_NAME_PREFIX
, uno::Any(aPrefix
));
166 mMMargs
.emplace_back(UNO_NAME_SAVE_FILTER
, uno::Any(OUString::createFromAscii(filter
)));
170 mMMargs
.emplace_back(UNO_NAME_FILE_NAME_FROM_COLUMN
, uno::Any(true));
174 mMMargs
.emplace_back(UNO_NAME_DAD_COMMAND_TYPE
, uno::Any(sdb::CommandType::TABLE
));
175 mMMargs
.emplace_back(UNO_NAME_DAD_COMMAND
,
176 uno::Any(OUString::createFromAscii(tablename
)));
181 mxCurResultSet
= getXResultFromDataset(tablename
, aDBName
);
182 uno::Reference
<sdbcx::XRowLocate
> xCurRowLocate(mxCurResultSet
, uno::UNO_QUERY
);
183 mMMargs
.emplace_back(UNO_NAME_RESULT_SET
, uno::Any(mxCurResultSet
));
184 std::vector
<uno::Any
> vResult
;
185 vResult
.reserve(nDataSets
);
187 for (i
= 0, mxCurResultSet
->first(); i
< nDataSets
; i
++, mxCurResultSet
->next())
189 vResult
.emplace_back(xCurRowLocate
->getBookmark());
191 mMMargs
.emplace_back(UNO_NAME_SELECTION
,
192 uno::Any(comphelper::containerToSequence(vResult
)));
196 void executeMailMerge(bool bDontLoadResult
= false)
198 const uno::Sequence
<beans::NamedValue
> aSeqMailMergeArgs
199 = comphelper::containerToSequence(mMMargs
);
200 uno::Any res
= mxJob
->execute(aSeqMailMergeArgs
);
203 bool bMMFilenameFromColumn
= false;
205 for (const beans::NamedValue
& rArgument
: aSeqMailMergeArgs
)
207 const OUString
& rName
= rArgument
.Name
;
208 const uno::Any
& rValue
= rArgument
.Value
;
210 // all error checking was already done by the MM job execution
211 if (rName
== UNO_NAME_OUTPUT_URL
)
212 bOk
&= rValue
>>= msMailMergeOutputURL
;
213 else if (rName
== UNO_NAME_FILE_NAME_PREFIX
)
214 bOk
&= rValue
>>= msMailMergeOutputPrefix
;
215 else if (rName
== UNO_NAME_OUTPUT_TYPE
)
216 bOk
&= rValue
>>= mnCurOutputType
;
217 else if (rName
== UNO_NAME_FILE_NAME_FROM_COLUMN
)
218 bOk
&= rValue
>>= bMMFilenameFromColumn
;
219 else if (rName
== UNO_NAME_DOCUMENT_URL
)
220 bOk
&= rValue
>>= msMailMergeDocumentURL
;
225 // MM via UNO just works with file names. If we load the file on
226 // Windows before MM uses it, MM won't work, as it's already open.
227 // Don't move the load before the mail merge execution!
228 // (see gb_CppunitTest_use_instdir_configuration)
229 createSwDoc(maMMtestFilename
);
231 if (mnCurOutputType
== text::MailMergeType::SHELL
)
233 uno::Reference
<lang::XComponent
> xTmp
;
234 CPPUNIT_ASSERT(res
>>= xTmp
);
235 mxSwTextDocument
= dynamic_cast<SwXTextDocument
*>(xTmp
.get());
236 CPPUNIT_ASSERT(mxSwTextDocument
.is());
240 CPPUNIT_ASSERT_EQUAL(uno::Any(true), res
);
241 if (!bMMFilenameFromColumn
&& !bDontLoadResult
)
242 loadMailMergeDocument(0);
247 * Like parseExport(), but for given mail merge document.
249 xmlDocUniquePtr
parseMailMergeExport(const OUString
& rStreamName
)
251 if (mnCurOutputType
!= text::MailMergeType::FILE)
254 OUString name
= msMailMergeOutputPrefix
+ OUString::number(0) + ".odt";
255 std::unique_ptr
<SvStream
> pStream(
256 parseExportStream(msMailMergeOutputURL
+ "/" + name
, rStreamName
));
258 return parseXmlStream(pStream
.get());
261 void loadMailMergeDocument(const OUString
& filename
)
263 assert(mnCurOutputType
== text::MailMergeType::FILE);
264 // Output name early, so in the case of a hang, the name of the hanging input file is visible.
265 std::cout
<< filename
<< ",";
266 loadFromURL(msMailMergeOutputURL
+ "/" + filename
);
271 Loads number-th document from mail merge. Requires file output from mail merge.
273 void loadMailMergeDocument(int number
, char const* const ext
= ".odt")
276 if (!msMailMergeOutputPrefix
.isEmpty())
277 name
= msMailMergeOutputPrefix
;
280 INetURLObject aURLObj
;
281 aURLObj
.SetSmartProtocol(INetProtocol::File
);
282 aURLObj
.SetSmartURL(msMailMergeDocumentURL
);
283 name
= aURLObj
.GetBase();
285 name
+= OUString::number(number
)
286 + OStringToOUString(std::string_view(ext
, strlen(ext
)), RTL_TEXTENCODING_ASCII_US
);
287 loadMailMergeDocument(name
);
290 // Returns page number of the first page of a MM document inside the large MM document (used in the SHELL case).
291 int documentStartPageNumber(int document
) const
292 { // See documentStartPageNumber() .
293 CPPUNIT_ASSERT(mxSwTextDocument
);
294 SwWrtShell
* shell
= mxSwTextDocument
->GetDocShell()->GetWrtShell();
295 IDocumentMarkAccess
* marks
= shell
->GetDoc()->getIDocumentMarkAccess();
296 // Unfortunately, the pages are marked using UNO bookmarks, which have internals names, so they cannot be referred to by their names.
297 // Assume that there are no other UNO bookmarks than the ones used by mail merge, and that they are in the sorted order.
298 IDocumentMarkAccess::const_iterator mark
;
300 for (mark
= marks
->getAllMarksBegin(); mark
!= marks
->getAllMarksEnd() && pos
< document
;
303 if (IDocumentMarkAccess::GetType(**mark
) == IDocumentMarkAccess::MarkType::UNO_BOOKMARK
)
306 CPPUNIT_ASSERT_EQUAL(document
, pos
);
307 sal_uInt16 page
, dummy
;
309 shell
->GotoMark(*mark
);
310 shell
->GetPageNum(page
, dummy
);
311 shell
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
316 uno::Reference
<css::task::XJob
> mxJob
;
317 std::vector
<beans::NamedValue
> mMMargs
;
318 OUString msMailMergeDocumentURL
;
319 OUString msMailMergeOutputURL
;
320 OUString msMailMergeOutputPrefix
;
321 sal_Int16 mnCurOutputType
;
322 rtl::Reference
<SwXTextDocument
> mxSwTextDocument
;
323 uno::Reference
<sdbc::XRowSet
> mxCurResultSet
;
324 const char* maMMtestFilename
;
327 #define DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, filter, BaseClass, \
329 class TestName : public BaseClass \
332 CPPUNIT_TEST_SUITE(TestName); \
333 CPPUNIT_TEST(MailMerge); \
334 CPPUNIT_TEST_SUITE_END(); \
338 executeMailMergeTest(filename, datasource, tablename, filter, selection, column); \
340 void verify() override; \
342 CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \
343 void TestName::verify()
345 // Will generate the resulting document in mxMMDocument.
346 #define DECLARE_SHELL_MAILMERGE_TEST(TestName, filename, datasource, tablename) \
347 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, nullptr, MailMergeTestBase, \
350 // Will generate documents as files, use loadMailMergeDocument().
351 #define DECLARE_FILE_MAILMERGE_TEST(TestName, filename, datasource, tablename) \
352 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, "writer8", \
353 MailMergeTestBase, 0, nullptr)
355 #define DECLARE_SHELL_MAILMERGE_TEST_SELECTION(TestName, filename, datasource, tablename, \
357 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, nullptr, MailMergeTestBase, \
360 #define DECLARE_FILE_MAILMERGE_TEST_COLUMN(TestName, filename, datasource, tablename, column) \
361 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, "writer8", \
362 MailMergeTestBase, 0, column)
364 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */