android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / qa / extras / mailmerge / mailmerge2.cxx
blobc37946c25341878a7875a5e7b6d5e3e0b3d60418
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include <sal/config.h>
12 #include <set>
13 #include <vector>
15 #include <swmodeltestbase.hxx>
17 #include <com/sun/star/util/URLTransformer.hpp>
18 #include <com/sun/star/text/XTextViewCursorSupplier.hpp>
19 #include <com/sun/star/text/MailMergeType.hpp>
20 #include <com/sun/star/text/XPageCursor.hpp>
21 #include <com/sun/star/sdb/CommandType.hpp>
22 #include <com/sun/star/table/TableBorder.hpp>
23 #include <com/sun/star/text/TextContentAnchorType.hpp>
24 #include <com/sun/star/text/XTextTable.hpp>
25 #include <com/sun/star/sdbc/XRowSet.hpp>
26 #include <com/sun/star/sdbcx/XRowLocate.hpp>
27 #include <com/sun/star/task/XJob.hpp>
29 #include <vcl/filter/PDFiumLibrary.hxx>
30 #include <tools/urlobj.hxx>
31 #include <comphelper/sequence.hxx>
32 #include <comphelper/processfactory.hxx>
33 #include <comphelper/propertyvalue.hxx>
34 #include <comphelper/DirectoryHelper.hxx>
36 #include <wrtsh.hxx>
37 #include <ndtxt.hxx>
38 #include <pagefrm.hxx>
39 #include <unoprnms.hxx>
40 #include <dbmgr.hxx>
41 #include <unotxdoc.hxx>
42 #include <docsh.hxx>
43 #include <IDocumentLayoutAccess.hxx>
44 #include <rootfrm.hxx>
46 /**
47 * Maps database URIs to the registered database names for quick lookups
49 typedef std::map<OUString, OUString> DBuriMap;
50 static DBuriMap aDBuriMap;
52 class MMTest2 : public SwModelTestBase
54 public:
55 MMTest2();
57 virtual void tearDown() override
59 if (mxMMComponent.is())
61 if (mnCurOutputType == text::MailMergeType::SHELL)
63 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxMMComponent.get());
64 CPPUNIT_ASSERT(pTextDoc);
65 pTextDoc->GetDocShell()->DoClose();
67 else
68 mxMMComponent->dispose();
70 if (mxCurResultSet.is())
72 css::uno::Reference<css::lang::XComponent>(
73 mxCurResultSet, css::uno::UNO_QUERY_THROW)->dispose();
75 SwModelTestBase::tearDown();
78 /**
79 * Helper func used by each unit test to test the 'mail merge' code.
81 * Registers the data source, loads the original file as reference,
82 * initializes the mail merge job and its default argument sequence.
84 * The 'verify' method actually has to execute the mail merge by
85 * calling executeMailMerge() after modifying the job arguments.
87 void executeMailMergeTest( const char* filename, const char* datasource, const char* tablename,
88 char const*const filter, int selection, const char* column )
90 maMMTest2Filename = filename;
91 header();
92 preTest(filename);
94 utl::TempFileNamed aTempDir(nullptr, true);
95 aTempDir.EnableKillingFile();
96 const OUString aWorkDir = aTempDir.GetURL();
97 const OUString aURI( createFileURL(OUString::createFromAscii(datasource)) );
98 const OUString aPrefix = column ? OUString::createFromAscii( column ) : "LOMM_";
99 const OUString aDBName = registerDBsource( aURI, aWorkDir );
100 initMailMergeJobAndArgs( filename, tablename, aDBName, aPrefix, aWorkDir, filter, selection, column != nullptr );
102 verify();
103 finish();
105 mnCurOutputType = 0;
108 OUString registerDBsource( const OUString &aURI, const OUString &aWorkDir )
110 OUString aDBName;
111 DBuriMap::const_iterator pos = aDBuriMap.find( aURI );
112 if (pos == aDBuriMap.end())
114 aDBName = SwDBManager::LoadAndRegisterDataSource( aURI, &aWorkDir );
115 aDBuriMap.insert( std::pair< OUString, OUString >( aURI, aDBName ) );
116 std::cout << "New datasource name: '" << aDBName << "'" << std::endl;
118 else
120 aDBName = pos->second;
121 std::cout << "Old datasource name: '" << aDBName << "'" << std::endl;
123 CPPUNIT_ASSERT(!aDBName.isEmpty());
124 return aDBName;
127 uno::Reference< sdbc::XRowSet > getXResultFromDataset( const char* tablename, const OUString &aDBName )
129 uno::Reference< sdbc::XRowSet > xCurResultSet;
130 uno::Reference< uno::XInterface > xInstance = getMultiServiceFactory()->createInstance( "com.sun.star.sdb.RowSet" );
131 uno::Reference< beans::XPropertySet > xRowSetPropSet( xInstance, uno::UNO_QUERY );
132 assert( xRowSetPropSet.is() && "failed to get XPropertySet interface from RowSet" );
133 if (xRowSetPropSet.is())
135 xRowSetPropSet->setPropertyValue( "DataSourceName", uno::Any( aDBName ) );
136 xRowSetPropSet->setPropertyValue( "Command", uno::Any( OUString::createFromAscii(tablename) ) );
137 xRowSetPropSet->setPropertyValue( "CommandType", uno::Any( sdb::CommandType::TABLE ) );
139 uno::Reference< sdbc::XRowSet > xRowSet( xInstance, uno::UNO_QUERY );
140 if (xRowSet.is())
141 xRowSet->execute(); // build ResultSet from properties
142 xCurResultSet = xRowSet;
143 assert( xCurResultSet.is() && "failed to build ResultSet" );
145 return xCurResultSet;
148 void initMailMergeJobAndArgs( const char* filename, const char* tablename, const OUString &aDBName,
149 const OUString &aPrefix, const OUString &aWorkDir,
150 char const*const filter, int nDataSets,
151 const bool bPrefixIsColumn )
153 uno::Reference< task::XJob > xJob( getMultiServiceFactory()->createInstance( "com.sun.star.text.MailMerge" ), uno::UNO_QUERY_THROW );
154 mxJob.set( xJob );
156 mMMargs.reserve( 15 );
158 mMMargs.emplace_back( UNO_NAME_OUTPUT_TYPE, uno::Any( filter ? text::MailMergeType::FILE : text::MailMergeType::SHELL ) );
159 mMMargs.emplace_back( UNO_NAME_DOCUMENT_URL, uno::Any(
160 ( 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 ) );
163 if (filter)
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)));
169 if (bPrefixIsColumn)
170 mMMargs.emplace_back( UNO_NAME_FILE_NAME_FROM_COLUMN, uno::Any( true ) );
172 if (tablename)
174 mMMargs.emplace_back( UNO_NAME_DAD_COMMAND_TYPE, uno::Any( sdb::CommandType::TABLE ) );
175 mMMargs.emplace_back( UNO_NAME_DAD_COMMAND, uno::Any( OUString::createFromAscii(tablename) ) );
178 if (nDataSets > 0)
180 mxCurResultSet = getXResultFromDataset( tablename, aDBName );
181 uno::Reference< sdbcx::XRowLocate > xCurRowLocate( mxCurResultSet, uno::UNO_QUERY );
182 mMMargs.emplace_back( UNO_NAME_RESULT_SET, uno::Any( mxCurResultSet ) );
183 std::vector< uno::Any > vResult;
184 vResult.reserve( nDataSets );
185 sal_Int32 i;
186 for (i = 0, mxCurResultSet->first(); i < nDataSets; i++, mxCurResultSet->next())
188 vResult.emplace_back( xCurRowLocate->getBookmark() );
190 mMMargs.emplace_back( UNO_NAME_SELECTION, uno::Any( comphelper::containerToSequence(vResult) ) );
195 void executeMailMerge( bool bDontLoadResult = false )
197 const uno::Sequence< beans::NamedValue > aSeqMailMergeArgs = comphelper::containerToSequence( mMMargs );
198 uno::Any res = mxJob->execute( aSeqMailMergeArgs );
200 bool bOk = true;
201 bool bMMFilenameFromColumn = false;
203 for (const beans::NamedValue& rArgument : aSeqMailMergeArgs) {
204 const OUString &rName = rArgument.Name;
205 const uno::Any &rValue = rArgument.Value;
207 // all error checking was already done by the MM job execution
208 if (rName == UNO_NAME_OUTPUT_URL)
209 bOk &= rValue >>= msMailMergeOutputURL;
210 else if (rName == UNO_NAME_FILE_NAME_PREFIX)
211 bOk &= rValue >>= msMailMergeOutputPrefix;
212 else if (rName == UNO_NAME_OUTPUT_TYPE)
213 bOk &= rValue >>= mnCurOutputType;
214 else if (rName == UNO_NAME_FILE_NAME_FROM_COLUMN)
215 bOk &= rValue >>= bMMFilenameFromColumn;
216 else if (rName == UNO_NAME_DOCUMENT_URL)
217 bOk &= rValue >>= msMailMergeDocumentURL;
220 CPPUNIT_ASSERT(bOk);
222 // MM via UNO just works with file names. If we load the file on
223 // Windows before MM uses it, MM won't work, as it's already open.
224 // Don't move the load before the mail merge execution!
225 // (see gb_CppunitTest_use_instdir_configuration)
226 createSwDoc(maMMTest2Filename);
228 if (mnCurOutputType == text::MailMergeType::SHELL)
230 CPPUNIT_ASSERT(res >>= mxMMComponent);
231 CPPUNIT_ASSERT(mxMMComponent.is());
233 else
235 CPPUNIT_ASSERT_EQUAL(uno::Any(true), res);
236 if( !bMMFilenameFromColumn && !bDontLoadResult )
237 loadMailMergeDocument( 0 );
241 void loadMailMergeDocument( const OUString &filename )
243 assert( mnCurOutputType == text::MailMergeType::FILE );
244 if (mxComponent.is())
245 mxComponent->dispose();
246 // Output name early, so in the case of a hang, the name of the hanging input file is visible.
247 std::cout << filename << ",";
248 mnStartTime = osl_getGlobalTimer();
249 mxComponent = loadFromDesktop(msMailMergeOutputURL + "/" + filename, "com.sun.star.text.TextDocument");
250 discardDumpedLayout();
251 calcLayout();
255 Loads number-th document from mail merge. Requires file output from mail merge.
257 void loadMailMergeDocument(int number, char const*const ext = ".odt")
259 OUString name;
260 if (!msMailMergeOutputPrefix.isEmpty())
261 name = msMailMergeOutputPrefix;
262 else
264 INetURLObject aURLObj;
265 aURLObj.SetSmartProtocol( INetProtocol::File );
266 aURLObj.SetSmartURL( msMailMergeDocumentURL );
267 name = aURLObj.GetBase();
269 name += OUString::number(number) + OStringToOUString(std::string_view(ext, strlen(ext)), RTL_TEXTENCODING_ASCII_US);
270 loadMailMergeDocument( name );
274 Resets currently opened layout of the original template,
275 and creates the layout of the document with N mails inside
276 (result run with text::MailMergeType::SHELL)
278 void dumpMMLayout()
280 mpXmlBuffer = xmlBufferPtr();
281 dumpLayout(mxMMComponent);
284 protected:
285 uno::Reference< css::task::XJob > mxJob;
286 std::vector< beans::NamedValue > mMMargs;
287 OUString msMailMergeDocumentURL;
288 OUString msMailMergeOutputURL;
289 OUString msMailMergeOutputPrefix;
290 sal_Int16 mnCurOutputType;
291 uno::Reference< lang::XComponent > mxMMComponent;
292 uno::Reference< sdbc::XRowSet > mxCurResultSet;
293 const char* maMMTest2Filename;
296 #define DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, filter, BaseClass, selection, column) \
297 class TestName : public BaseClass { \
298 protected: \
299 virtual OUString getTestName() override { return #TestName; } \
300 public: \
301 CPPUNIT_TEST_SUITE(TestName); \
302 CPPUNIT_TEST(MailMerge); \
303 CPPUNIT_TEST_SUITE_END(); \
305 void MailMerge() { \
306 executeMailMergeTest(filename, datasource, tablename, filter, selection, column); \
308 void verify() override; \
309 }; \
310 CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \
311 void TestName::verify()
313 // Will generate the resulting document in mxMMDocument.
314 #define DECLARE_SHELL_MAILMERGE_TEST(TestName, filename, datasource, tablename) \
315 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, nullptr, MMTest2, 0, nullptr)
317 // Will generate documents as files, use loadMailMergeDocument().
318 #define DECLARE_FILE_MAILMERGE_TEST(TestName, filename, datasource, tablename) \
319 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, "writer8", MMTest2, 0, nullptr)
321 MMTest2::MMTest2()
322 : SwModelTestBase("/sw/qa/extras/mailmerge/data/", "writer8")
323 , mnCurOutputType(0)
324 , maMMTest2Filename(nullptr)
328 DECLARE_SHELL_MAILMERGE_TEST(tdf125522_shell, "tdf125522.odt", "10-testing-addresses.ods", "testing-addresses")
330 // prepare unit test and run
331 executeMailMerge();
333 // reset currently opened layout of the original template,
334 // and create the layout of the document with 10 mails inside
335 dumpMMLayout();
337 // there should be no any text frame in output
338 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
339 CPPUNIT_ASSERT(pTextDoc);
341 const auto & rNodes = pTextDoc->GetDocShell()->GetDoc()->GetNodes();
342 for (SwNodeOffset nodeIndex(0); nodeIndex<rNodes.Count(); nodeIndex++)
344 SwNode* aNode = rNodes[nodeIndex];
345 if (aNode->StartOfSectionNode())
347 CPPUNIT_ASSERT(!aNode->StartOfSectionNode()->GetFlyFormat());
352 DECLARE_SHELL_MAILMERGE_TEST(testTd78611_shell, "tdf78611.odt", "10-testing-addresses.ods", "testing-addresses")
354 // prepare unit test and run
355 executeMailMerge();
357 // reset currently opened layout of the original template,
358 // and create the layout of the document with 10 mails inside
359 dumpMMLayout();
361 // check: each page (one page is one sub doc) has different paragraphs and header paragraphs.
362 // All header paragraphs should have numbering.
364 // check first page
365 CPPUNIT_ASSERT_EQUAL( OUString("1"), parseDump("/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
366 CPPUNIT_ASSERT_EQUAL( OUString("1.1"), parseDump("/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
367 CPPUNIT_ASSERT_EQUAL( OUString("1.2"), parseDump("/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
369 // check some other pages
370 CPPUNIT_ASSERT_EQUAL( OUString("1"), parseDump("/root/page[3]/body/txt[6]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
371 CPPUNIT_ASSERT_EQUAL( OUString("1.1"), parseDump("/root/page[5]/body/txt[8]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
372 CPPUNIT_ASSERT_EQUAL( OUString("1.2"), parseDump("/root/page[7]/body/txt[10]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
376 DECLARE_FILE_MAILMERGE_TEST(testTd78611_file, "tdf78611.odt", "10-testing-addresses.ods", "testing-addresses")
378 executeMailMerge(true);
379 for (int doc = 0; doc < 10; ++doc)
381 loadMailMergeDocument( doc );
382 CPPUNIT_ASSERT_EQUAL( OUString("1"), parseDump("/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
383 CPPUNIT_ASSERT_EQUAL( OUString("1.1"), parseDump("/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
384 CPPUNIT_ASSERT_EQUAL( OUString("1.2"), parseDump("/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
388 DECLARE_SHELL_MAILMERGE_TEST(testTdf122156_shell, "linked-with-condition.odt", "5-with-blanks.ods",
389 "names")
391 // A document with a linked section hidden on an "empty field" condition
392 // For combined documents, hidden sections are removed completely
393 executeMailMerge();
394 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxMMComponent.get());
395 CPPUNIT_ASSERT(pTextDoc);
396 // 5 documents 1 page each, starting at odd page numbers => 9
397 CPPUNIT_ASSERT_EQUAL(sal_uInt16(9), pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum());
398 uno::Reference<text::XTextSectionsSupplier> xSectionsSupplier(mxMMComponent,
399 uno::UNO_QUERY_THROW);
400 uno::Reference<container::XIndexAccess> xSections(xSectionsSupplier->getTextSections(),
401 uno::UNO_QUERY_THROW);
402 // 2 out of 5 dataset records have empty "Title" field => no sections in respective documents
403 CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xSections->getCount());
406 DECLARE_FILE_MAILMERGE_TEST(testTdf122156_file, "linked-with-condition.odt", "5-with-blanks.ods",
407 "names")
409 // A document with a linked section hidden on an "empty field" condition
410 // For separate documents, the sections are removed
411 executeMailMerge();
413 loadMailMergeDocument(0);
414 uno::Reference<text::XTextSectionsSupplier> xSectionsSupplier(mxComponent,
415 uno::UNO_QUERY_THROW);
416 uno::Reference<container::XIndexAccess> xSections(xSectionsSupplier->getTextSections(),
417 uno::UNO_QUERY_THROW);
418 CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount());
421 loadMailMergeDocument(1);
422 uno::Reference<text::XTextSectionsSupplier> xSectionsSupplier(mxComponent,
423 uno::UNO_QUERY_THROW);
424 uno::Reference<container::XIndexAccess> xSections(xSectionsSupplier->getTextSections(),
425 uno::UNO_QUERY_THROW);
426 CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount());
427 uno::Reference<beans::XPropertySet> xSect(xSections->getByIndex(0), uno::UNO_QUERY_THROW);
428 // Record 2 has non-empty "Title" field => section is shown
429 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xSect, "IsVisible"));
432 loadMailMergeDocument(2);
433 uno::Reference<text::XTextSectionsSupplier> xSectionsSupplier(mxComponent,
434 uno::UNO_QUERY_THROW);
435 uno::Reference<container::XIndexAccess> xSections(xSectionsSupplier->getTextSections(),
436 uno::UNO_QUERY_THROW);
437 CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount());
438 uno::Reference<beans::XPropertySet> xSect(xSections->getByIndex(0), uno::UNO_QUERY_THROW);
439 // Record 3 has non-empty "Title" field => section is shown
440 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xSect, "IsVisible"));
443 loadMailMergeDocument(3);
444 uno::Reference<text::XTextSectionsSupplier> xSectionsSupplier(mxComponent,
445 uno::UNO_QUERY_THROW);
446 uno::Reference<container::XIndexAccess> xSections(xSectionsSupplier->getTextSections(),
447 uno::UNO_QUERY_THROW);
448 CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount());
451 loadMailMergeDocument(4);
452 uno::Reference<text::XTextSectionsSupplier> xSectionsSupplier(mxComponent,
453 uno::UNO_QUERY_THROW);
454 uno::Reference<container::XIndexAccess> xSections(xSectionsSupplier->getTextSections(),
455 uno::UNO_QUERY_THROW);
456 CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount());
457 uno::Reference<beans::XPropertySet> xSect(xSections->getByIndex(0), uno::UNO_QUERY_THROW);
458 // Record 5 has non-empty "Title" field => section is shown
459 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xSect, "IsVisible"));
463 DECLARE_SHELL_MAILMERGE_TEST(exportDirectToPDF_shell, "linked-with-condition.odt", "5-with-blanks.ods",
464 "names")
466 executeMailMerge();
468 uno::Reference<css::frame::XModel> xModel(mxMMComponent, uno::UNO_QUERY);
469 CPPUNIT_ASSERT(xModel.is());
471 uno::Reference<css::frame::XController> xController(xModel->getCurrentController());
472 CPPUNIT_ASSERT(xController.is());
474 uno::Reference<css::text::XTextViewCursorSupplier> xSupplier(xController, uno::UNO_QUERY);
475 CPPUNIT_ASSERT(xSupplier.is());
477 uno::Reference<css::text::XPageCursor> xPageCursor(xSupplier->getViewCursor(), uno::UNO_QUERY);
478 CPPUNIT_ASSERT(xPageCursor.is());
480 xPageCursor->jumpToFirstPage();
481 CPPUNIT_ASSERT_EQUAL(sal_Int16(1), xPageCursor->getPage());
483 uno::Reference<css::frame::XFrame> xFrame(xController->getFrame());
484 CPPUNIT_ASSERT(xFrame.is());
486 uno::Reference<css::frame::XDispatchProvider> xDispatchProvider(xFrame, uno::UNO_QUERY);
487 CPPUNIT_ASSERT(xDispatchProvider.is());
489 util::URL aURL;
490 aURL.Complete = ".uno:ExportDirectToPDF";
492 uno::Reference<css::util::XURLTransformer> xParser(css::util::URLTransformer::create(
493 comphelper::getProcessComponentContext()));
494 CPPUNIT_ASSERT(xParser.is());
495 xParser->parseStrict(aURL);
498 uno::Reference<css::frame::XDispatch> xDispatch = xDispatchProvider->queryDispatch(aURL, OUString(), 0);
499 CPPUNIT_ASSERT(xDispatch.is());
501 const OUString sExportTo(msMailMergeOutputURL + "/ExportDirectToPDF.pdf");
502 uno::Sequence <css::beans::PropertyValue> aArgs {
503 comphelper::makePropertyValue("SynchronMode", true),
504 comphelper::makePropertyValue("URL", sExportTo)
507 xDispatch->dispatch(aURL, aArgs);
508 CPPUNIT_ASSERT(comphelper::DirectoryHelper::fileExists(sExportTo));
510 SvFileStream aPDFFile(sExportTo, StreamMode::READ);
511 SvMemoryStream aMemory;
512 aMemory.WriteStream(aPDFFile);
513 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
514 if (!pPDFium)
515 return;
517 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
518 = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString());
519 CPPUNIT_ASSERT(pPdfDocument);
520 CPPUNIT_ASSERT_EQUAL(5, pPdfDocument->getPageCount());
522 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
523 CPPUNIT_ASSERT(pPdfPage);
524 CPPUNIT_ASSERT_EQUAL(4, pPdfPage->getObjectCount());
527 DECLARE_SHELL_MAILMERGE_TEST(testTdf121168, "section_ps.odt", "4_v01.ods", "Tabelle1")
529 // A document starting with a section on a page with non-default page style with header
530 executeMailMerge();
531 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxMMComponent.get());
532 CPPUNIT_ASSERT(pTextDoc);
533 // 4 documents 1 page each, starting at odd page numbers => 7
534 CPPUNIT_ASSERT_EQUAL(sal_uInt16(7), pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum());
536 SwDoc* pDocMM = pTextDoc->GetDocShell()->GetDoc();
537 SwNodeOffset nSizeMM = pDocMM->GetNodes().GetEndOfContent().GetIndex()
538 - pDocMM->GetNodes().GetEndOfExtras().GetIndex() - 2;
539 CPPUNIT_ASSERT_EQUAL(SwNodeOffset(16), nSizeMM);
541 // All even pages should be empty, all sub-documents have one page
542 const SwRootFrame* pLayout = pDocMM->getIDocumentLayoutAccess().GetCurrentLayout();
543 const SwPageFrame* pPageFrm = static_cast<const SwPageFrame*>(pLayout->Lower());
544 while (pPageFrm)
546 sal_uInt16 nPageNum = pPageFrm->GetPhyPageNum();
547 bool bOdd = (1 == (nPageNum % 2));
548 CPPUNIT_ASSERT_EQUAL(!bOdd, pPageFrm->IsEmptyPage());
549 CPPUNIT_ASSERT_EQUAL(sal_uInt16(bOdd ? 1 : 2), pPageFrm->GetVirtPageNum());
550 if (bOdd)
552 const SwPageDesc* pDesc = pPageFrm->GetPageDesc();
553 CPPUNIT_ASSERT_EQUAL(OUString("Teststyle" + OUString::number(nPageNum / 2 + 1)),
554 pDesc->GetName());
556 pPageFrm = static_cast<const SwPageFrame*>(pPageFrm->GetNext());
561 DECLARE_FILE_MAILMERGE_TEST(testTdf81782_file, "tdf78611.odt", "10-testing-addresses.ods", "testing-addresses")
563 executeMailMerge(true);
564 for (int doc = 0; doc < 10; ++doc)
566 loadMailMergeDocument( doc );
568 // get document properties
569 uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(mxComponent, uno::UNO_QUERY);
570 uno::Reference<document::XDocumentProperties> xDocumentProperties(xDocumentPropertiesSupplier->getDocumentProperties());
572 // check if properties were set
573 uno::Sequence<OUString> aKeywords(xDocumentProperties->getKeywords());
574 CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aKeywords.getLength());
575 CPPUNIT_ASSERT_EQUAL(OUString("one two"), aKeywords[0]);
577 // check title and subject
578 CPPUNIT_ASSERT_EQUAL(OUString("my title"), xDocumentProperties->getTitle());
579 CPPUNIT_ASSERT_EQUAL(OUString("my subject"), xDocumentProperties->getSubject());
583 // problem was: field content was duplicated & truncated
584 DECLARE_SHELL_MAILMERGE_TEST(testTdf81750_shell, "tdf81750.odt", "10-testing-addresses.ods", "testing-addresses")
586 // prepare unit test and run
587 executeMailMerge();
589 // reset currently opened layout of the original template,
590 // and create the layout of the document with 10 mails inside
591 dumpMMLayout();
593 // check several pages page
594 OUString aExpected("Text: Foo ");
595 CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[1]/body/txt[2]", ""));
596 CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[3]/body/txt[2]", ""));
597 CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[5]/body/txt[2]", ""));
598 CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[7]/body/txt[2]", ""));
599 CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[9]/body/txt[2]", ""));
603 DECLARE_FILE_MAILMERGE_TEST(testTdf123057_file, "pagecounttest.ott", "db_pagecounttest.ods", "Sheet1")
605 uno::Reference<beans::XPropertySet> xSect0, xSect1;
606 executeMailMerge(true);
608 for (int doc = 0; doc < 4; ++doc)
610 loadMailMergeDocument(doc);
612 // get document properties
613 uno::Reference<text::XTextSectionsSupplier> xSectionsSupplier(mxComponent, uno::UNO_QUERY_THROW);
614 uno::Reference<container::XIndexAccess> xSections(xSectionsSupplier->getTextSections(), uno::UNO_QUERY_THROW);
616 switch (doc)
618 case 0:
619 CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xSections->getCount());
620 xSect0.set(xSections->getByIndex(0), uno::UNO_QUERY_THROW);
621 xSect1.set(xSections->getByIndex(1), uno::UNO_QUERY_THROW);
623 // both sections visible, page num is 2
624 CPPUNIT_ASSERT_EQUAL(2, getPages());
625 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xSect0, "IsVisible"));
626 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xSect1, "IsVisible"));
627 break;
628 case 1:
629 CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount());
630 xSect0.set(xSections->getByIndex(0), uno::UNO_QUERY_THROW);
632 // second section removed, page num is 1
633 CPPUNIT_ASSERT_EQUAL(1, getPages());
634 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xSect0, "IsVisible"));
635 break;
636 case 2:
637 CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount());
638 xSect0.set(xSections->getByIndex(0), uno::UNO_QUERY_THROW);
640 // first section removed, page num is 1
641 CPPUNIT_ASSERT_EQUAL(1, getPages());
642 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xSect0, "IsVisible"));
643 break;
644 case 3:
645 CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount());
646 // both sections removed, page num is 1
647 CPPUNIT_ASSERT_EQUAL(1, getPages());
648 break;
653 // The document has a header with page number and total page count on page 2
654 // (which uses page style "Default Style") but doesn't have a header set
655 // for the first page (which uses page style "First Page").
656 // Fields in the header hadn't been replaced properly.
657 DECLARE_SHELL_MAILMERGE_TEST(testTdf128148, "tdf128148.odt", "4_v01.ods", "Tabelle1")
659 executeMailMerge();
660 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxMMComponent.get());
661 CPPUNIT_ASSERT(pTextDoc);
663 // 4 documents with 2 pages each => 8 pages in total
664 CPPUNIT_ASSERT_EQUAL(sal_uInt16(8), pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum());
666 SwDoc* pDocMM = pTextDoc->GetDocShell()->GetDoc();
667 uno::Reference<frame::XModel> xModel = pTextDoc->GetDocShell()->GetBaseModel();
668 uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
669 uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
670 uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
672 // All odd pages have no header, all even pages should have header with text "Page 2 of 2"
673 const SwRootFrame* pLayout = pDocMM->getIDocumentLayoutAccess().GetCurrentLayout();
674 const SwPageFrame* pPageFrm = static_cast<const SwPageFrame*>(pLayout->Lower());
675 while (pPageFrm)
677 const sal_uInt16 nPageNum = pPageFrm->GetPhyPageNum();
678 const bool bIsEvenPage = ((nPageNum % 2) == 0);
680 const OUString& sPageStyle = pPageFrm->GetPageDesc()->GetName();
681 uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(sPageStyle), uno::UNO_QUERY);
683 bool bHeaderIsOn = false;
684 xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
686 // first page for every data record shouldn't have header, second should
687 CPPUNIT_ASSERT_EQUAL(bIsEvenPage, bHeaderIsOn);
688 if (bIsEvenPage)
690 // text in header on even pages with correctly replaced fields is "Page 2 of 2"
691 uno::Reference<text::XText> xHeaderText;
692 xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
693 const OUString sHeaderText = xHeaderText->getString();
694 CPPUNIT_ASSERT_EQUAL(OUString("Page 2 of 2"), sHeaderText);
697 pPageFrm = static_cast<const SwPageFrame*>(pPageFrm->GetNext());
701 DECLARE_MAILMERGE_TEST(testGrabBag, "grabbagtest.docx", "onecell.xlsx", "Sheet1", "MS Word 2007 XML", MMTest2, 0, nullptr)
703 executeMailMerge(true);
705 loadMailMergeDocument(0, ".docx");
707 SwXTextDocument *const pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
708 CPPUNIT_ASSERT(pTextDoc);
710 CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum());
712 // check grabbag
713 uno::Reference<beans::XPropertySet> const xModel(
714 mxComponent, uno::UNO_QUERY_THROW);
715 uno::Sequence<beans::PropertyValue> aInteropGrabBag;
716 pTextDoc->getPropertyValue("InteropGrabBag") >>= aInteropGrabBag;
717 CPPUNIT_ASSERT_EQUAL(sal_Int32(12), aInteropGrabBag.getLength());
719 // check table border - comes from table style "Tabellenraster"
720 uno::Reference<text::XTextTable> const xTable(getParagraphOrTable(1, pTextDoc->getText()), uno::UNO_QUERY_THROW);
721 uno::Reference<beans::XPropertySet> const xTableProps(xTable, uno::UNO_QUERY_THROW);
722 CPPUNIT_ASSERT_EQUAL(table::TableBorder(
723 table::BorderLine(util::Color(0), 0, 18, 0), true,
724 table::BorderLine(util::Color(0), 0, 18, 0), true,
725 table::BorderLine(util::Color(0), 0, 18, 0), true,
726 table::BorderLine(util::Color(0), 0, 18, 0), true,
727 table::BorderLine(util::Color(0), 0, 18, 0), true,
728 table::BorderLine(util::Color(0), 0, 0, 0), true,
729 sal_Int16(191), true),
730 getProperty<table::TableBorder>(xTableProps, "TableBorder"));
732 // check font is Arial - comes from theme (wrong result was "" - nothing)
733 uno::Reference<text::XText> const xCell(xTable->getCellByName("A1"), uno::UNO_QUERY_THROW);
734 uno::Reference<beans::XPropertySet> const xParaA1(getParagraphOrTable(1, xCell->getText()), uno::UNO_QUERY_THROW);
735 CPPUNIT_ASSERT_EQUAL(OUString("Arial"), getProperty<OUString>(xParaA1, "CharFontName"));
738 namespace com::sun::star::table {
740 static std::ostream& operator<<(std::ostream& rStream, table::BorderLine const& rLine)
742 rStream << "BorderLine(" << rLine.Color << "," << rLine.InnerLineWidth << "," << rLine.OuterLineWidth << "," << rLine.LineDistance << ")";
743 return rStream;
746 static std::ostream& operator<<(std::ostream& rStream, table::TableBorder const& rBorder)
748 rStream << "TableBorder(\n "
749 << rBorder.TopLine << "," << static_cast<bool>(rBorder.IsTopLineValid) << ",\n "
750 << rBorder.BottomLine << "," << static_cast<bool>(rBorder.IsBottomLineValid) << ",\n "
751 << rBorder.LeftLine << "," << static_cast<bool>(rBorder.IsLeftLineValid) << ",\n "
752 << rBorder.RightLine << "," << static_cast<bool>(rBorder.IsRightLineValid) << ",\n "
753 << rBorder.HorizontalLine << "," << static_cast<bool>(rBorder.IsHorizontalLineValid) << ",\n "
754 << rBorder.VerticalLine << "," << static_cast<bool>(rBorder.IsVerticalLineValid) << ",\n "
755 << rBorder.Distance << "," << static_cast<bool>(rBorder.IsDistanceValid) << ")";
756 return rStream;
761 CPPUNIT_PLUGIN_IMPLEMENT();
762 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */