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/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>
38 #include <pagefrm.hxx>
39 #include <unoprnms.hxx>
41 #include <unotxdoc.hxx>
43 #include <IDocumentLayoutAccess.hxx>
44 #include <rootfrm.hxx>
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
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();
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();
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
;
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 );
108 OUString
registerDBsource( const OUString
&aURI
, const OUString
&aWorkDir
)
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
;
120 aDBName
= pos
->second
;
121 std::cout
<< "Old datasource name: '" << aDBName
<< "'" << std::endl
;
123 CPPUNIT_ASSERT(!aDBName
.isEmpty());
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
);
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
);
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
) );
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
, uno::Any( OUString::createFromAscii(tablename
) ) );
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
);
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
);
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
;
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());
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();
255 Loads number-th document from mail merge. Requires file output from mail merge.
257 void loadMailMergeDocument(int number
, char const*const ext
= ".odt")
260 if (!msMailMergeOutputPrefix
.isEmpty())
261 name
= msMailMergeOutputPrefix
;
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)
280 mpXmlBuffer
= xmlBufferPtr();
281 dumpLayout(mxMMComponent
);
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 { \
299 virtual OUString getTestName() override { return #TestName; } \
301 CPPUNIT_TEST_SUITE(TestName); \
302 CPPUNIT_TEST(MailMerge); \
303 CPPUNIT_TEST_SUITE_END(); \
306 executeMailMergeTest(filename, datasource, tablename, filter, selection, column); \
308 void verify() override; \
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)
322 : SwModelTestBase("/sw/qa/extras/mailmerge/data/", "writer8")
324 , maMMTest2Filename(nullptr)
328 DECLARE_SHELL_MAILMERGE_TEST(tdf125522_shell
, "tdf125522.odt", "10-testing-addresses.ods", "testing-addresses")
330 // prepare unit test and run
333 // reset currently opened layout of the original template,
334 // and create the layout of the document with 10 mails inside
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
357 // reset currently opened layout of the original template,
358 // and create the layout of the document with 10 mails inside
361 // check: each page (one page is one sub doc) has different paragraphs and header paragraphs.
362 // All header paragraphs should have numbering.
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",
391 // A document with a linked section hidden on an "empty field" condition
392 // For combined documents, hidden sections are removed completely
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",
409 // A document with a linked section hidden on an "empty field" condition
410 // For separate documents, the sections are removed
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",
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());
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();
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
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());
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());
552 const SwPageDesc
* pDesc
= pPageFrm
->GetPageDesc();
553 CPPUNIT_ASSERT_EQUAL(OUString("Teststyle" + OUString::number(nPageNum
/ 2 + 1)),
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
589 // reset currently opened layout of the original template,
590 // and create the layout of the document with 10 mails inside
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
);
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"));
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"));
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"));
645 CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections
->getCount());
646 // both sections removed, page num is 1
647 CPPUNIT_ASSERT_EQUAL(1, getPages());
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")
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());
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
);
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());
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
<< ")";
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
) << ")";
761 CPPUNIT_PLUGIN_IMPLEMENT();
762 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */