android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / qa / extras / mailmerge / mailmerge.cxx
blobb31662a312716ead2aed9502ed71e7ae1e944463
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/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>
29 #include <wrtsh.hxx>
30 #include <ndtxt.hxx>
31 #include <pagefrm.hxx>
32 #include <unoprnms.hxx>
33 #include <dbmgr.hxx>
34 #include <unotxdoc.hxx>
35 #include <docsh.hxx>
36 #include <IDocumentLayoutAccess.hxx>
37 #include <rootfrm.hxx>
39 /**
40 * Maps database URIs to the registered database names for quick lookups
42 typedef std::map<OUString, OUString> DBuriMap;
43 static DBuriMap aDBuriMap;
45 class MMTest : public SwModelTestBase
47 public:
48 MMTest();
50 virtual void tearDown() override
52 if (mxMMComponent.is())
54 if (mnCurOutputType == text::MailMergeType::SHELL)
56 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxMMComponent.get());
57 CPPUNIT_ASSERT(pTextDoc);
58 pTextDoc->GetDocShell()->DoClose();
60 else
61 mxMMComponent->dispose();
63 if (mxCurResultSet.is())
65 css::uno::Reference<css::lang::XComponent>(
66 mxCurResultSet, css::uno::UNO_QUERY_THROW)->dispose();
68 SwModelTestBase::tearDown();
71 /**
72 * Helper func used by each unit test to test the 'mail merge' code.
74 * Registers the data source, loads the original file as reference,
75 * initializes the mail merge job and its default argument sequence.
77 * The 'verify' method actually has to execute the mail merge by
78 * calling executeMailMerge() after modifying the job arguments.
80 void executeMailMergeTest( const char* filename, const char* datasource, const char* tablename,
81 char const*const filter, int selection, const char* column )
83 maMMtestFilename = filename;
84 header();
85 preTest(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 ) : "LOMM_";
92 const OUString aDBName = registerDBsource( aURI, aWorkDir );
93 initMailMergeJobAndArgs( filename, tablename, aDBName, aPrefix, aWorkDir, filter, selection, column != nullptr );
95 verify();
96 finish();
98 mnCurOutputType = 0;
101 OUString registerDBsource( const OUString &aURI, const OUString &aWorkDir )
103 OUString aDBName;
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;
111 else
113 aDBName = pos->second;
114 std::cout << "Old datasource name: '" << aDBName << "'" << std::endl;
116 CPPUNIT_ASSERT(!aDBName.isEmpty());
117 return aDBName;
120 uno::Reference< sdbc::XRowSet > getXResultFromDataset( const char* tablename, const OUString &aDBName )
122 uno::Reference< sdbc::XRowSet > xCurResultSet;
123 uno::Reference< uno::XInterface > xInstance = getMultiServiceFactory()->createInstance( "com.sun.star.sdb.RowSet" );
124 uno::Reference< beans::XPropertySet > xRowSetPropSet( xInstance, uno::UNO_QUERY );
125 assert( xRowSetPropSet.is() && "failed to get XPropertySet interface from RowSet" );
126 if (xRowSetPropSet.is())
128 xRowSetPropSet->setPropertyValue( "DataSourceName", uno::Any( aDBName ) );
129 xRowSetPropSet->setPropertyValue( "Command", uno::Any( OUString::createFromAscii(tablename) ) );
130 xRowSetPropSet->setPropertyValue( "CommandType", uno::Any( sdb::CommandType::TABLE ) );
132 uno::Reference< sdbc::XRowSet > xRowSet( xInstance, uno::UNO_QUERY );
133 if (xRowSet.is())
134 xRowSet->execute(); // build ResultSet from properties
135 xCurResultSet = xRowSet;
136 assert( xCurResultSet.is() && "failed to build ResultSet" );
138 return xCurResultSet;
141 void initMailMergeJobAndArgs( const char* filename, const char* tablename, const OUString &aDBName,
142 const OUString &aPrefix, const OUString &aWorkDir,
143 char const*const filter, int nDataSets,
144 const bool bPrefixIsColumn )
146 uno::Reference< task::XJob > xJob( getMultiServiceFactory()->createInstance( "com.sun.star.text.MailMerge" ), uno::UNO_QUERY_THROW );
147 mxJob.set( xJob );
149 mMMargs.reserve( 15 );
151 mMMargs.emplace_back( UNO_NAME_OUTPUT_TYPE, uno::Any( filter ? text::MailMergeType::FILE : text::MailMergeType::SHELL ) );
152 mMMargs.emplace_back( UNO_NAME_DOCUMENT_URL, uno::Any(
153 ( createFileURL(OUString::createFromAscii(filename)) ) ) );
154 mMMargs.emplace_back( UNO_NAME_DATA_SOURCE_NAME, uno::Any( aDBName ) );
155 mMMargs.emplace_back( UNO_NAME_OUTPUT_URL, uno::Any( aWorkDir ) );
156 if (filter)
158 mMMargs.emplace_back( UNO_NAME_FILE_NAME_PREFIX, uno::Any( aPrefix ) );
159 mMMargs.emplace_back(UNO_NAME_SAVE_FILTER, uno::Any(OUString::createFromAscii(filter)));
162 if (bPrefixIsColumn)
163 mMMargs.emplace_back( UNO_NAME_FILE_NAME_FROM_COLUMN, uno::Any( true ) );
165 if (tablename)
167 mMMargs.emplace_back( UNO_NAME_DAD_COMMAND_TYPE, uno::Any( sdb::CommandType::TABLE ) );
168 mMMargs.emplace_back( UNO_NAME_DAD_COMMAND, uno::Any( OUString::createFromAscii(tablename) ) );
171 if (nDataSets > 0)
173 mxCurResultSet = getXResultFromDataset( tablename, aDBName );
174 uno::Reference< sdbcx::XRowLocate > xCurRowLocate( mxCurResultSet, uno::UNO_QUERY );
175 mMMargs.emplace_back( UNO_NAME_RESULT_SET, uno::Any( mxCurResultSet ) );
176 std::vector< uno::Any > vResult;
177 vResult.reserve( nDataSets );
178 sal_Int32 i;
179 for (i = 0, mxCurResultSet->first(); i < nDataSets; i++, mxCurResultSet->next())
181 vResult.emplace_back( xCurRowLocate->getBookmark() );
183 mMMargs.emplace_back( UNO_NAME_SELECTION, uno::Any( comphelper::containerToSequence(vResult) ) );
188 void executeMailMerge( bool bDontLoadResult = false )
190 const uno::Sequence< beans::NamedValue > aSeqMailMergeArgs = comphelper::containerToSequence( mMMargs );
191 uno::Any res = mxJob->execute( aSeqMailMergeArgs );
193 bool bOk = true;
194 bool bMMFilenameFromColumn = false;
196 for (const beans::NamedValue& rArgument : aSeqMailMergeArgs) {
197 const OUString &rName = rArgument.Name;
198 const uno::Any &rValue = rArgument.Value;
200 // all error checking was already done by the MM job execution
201 if (rName == UNO_NAME_OUTPUT_URL)
202 bOk &= rValue >>= msMailMergeOutputURL;
203 else if (rName == UNO_NAME_FILE_NAME_PREFIX)
204 bOk &= rValue >>= msMailMergeOutputPrefix;
205 else if (rName == UNO_NAME_OUTPUT_TYPE)
206 bOk &= rValue >>= mnCurOutputType;
207 else if (rName == UNO_NAME_FILE_NAME_FROM_COLUMN)
208 bOk &= rValue >>= bMMFilenameFromColumn;
209 else if (rName == UNO_NAME_DOCUMENT_URL)
210 bOk &= rValue >>= msMailMergeDocumentURL;
213 CPPUNIT_ASSERT(bOk);
215 // MM via UNO just works with file names. If we load the file on
216 // Windows before MM uses it, MM won't work, as it's already open.
217 // Don't move the load before the mail merge execution!
218 // (see gb_CppunitTest_use_instdir_configuration)
219 createSwDoc(maMMtestFilename);
221 if (mnCurOutputType == text::MailMergeType::SHELL)
223 CPPUNIT_ASSERT(res >>= mxMMComponent);
224 CPPUNIT_ASSERT(mxMMComponent.is());
226 else
228 CPPUNIT_ASSERT_EQUAL(uno::Any(true), res);
229 if( !bMMFilenameFromColumn && !bDontLoadResult )
230 loadMailMergeDocument( 0 );
235 * Like parseExport(), but for given mail merge document.
237 xmlDocUniquePtr parseMailMergeExport(const OUString& rStreamName)
239 if (mnCurOutputType != text::MailMergeType::FILE)
240 return nullptr;
242 OUString name = msMailMergeOutputPrefix + OUString::number( 0 ) + ".odt";
243 std::unique_ptr<SvStream> pStream(parseExportStream(msMailMergeOutputURL + "/" + name, rStreamName));
245 return parseXmlStream(pStream.get());
248 void loadMailMergeDocument( const OUString &filename )
250 assert( mnCurOutputType == text::MailMergeType::FILE );
251 if (mxComponent.is())
252 mxComponent->dispose();
253 // Output name early, so in the case of a hang, the name of the hanging input file is visible.
254 std::cout << filename << ",";
255 mnStartTime = osl_getGlobalTimer();
256 mxComponent = loadFromDesktop(msMailMergeOutputURL + "/" + filename, "com.sun.star.text.TextDocument");
257 discardDumpedLayout();
258 calcLayout();
262 Loads number-th document from mail merge. Requires file output from mail merge.
264 void loadMailMergeDocument(int number, char const*const ext = ".odt")
266 OUString name;
267 if (!msMailMergeOutputPrefix.isEmpty())
268 name = msMailMergeOutputPrefix;
269 else
271 INetURLObject aURLObj;
272 aURLObj.SetSmartProtocol( INetProtocol::File );
273 aURLObj.SetSmartURL( msMailMergeDocumentURL );
274 name = aURLObj.GetBase();
276 name += OUString::number(number) + OStringToOUString(std::string_view(ext, strlen(ext)), RTL_TEXTENCODING_ASCII_US);
277 loadMailMergeDocument( name );
281 Resets currently opened layout of the original template,
282 and creates the layout of the document with N mails inside
283 (result run with text::MailMergeType::SHELL)
285 void dumpMMLayout()
287 mpXmlBuffer = xmlBufferPtr();
288 dumpLayout(mxMMComponent);
291 protected:
292 // Returns page number of the first page of a MM document inside the large MM document (used in the SHELL case).
293 int documentStartPageNumber( int document ) const;
295 uno::Reference< css::task::XJob > mxJob;
296 std::vector< beans::NamedValue > mMMargs;
297 OUString msMailMergeDocumentURL;
298 OUString msMailMergeOutputURL;
299 OUString msMailMergeOutputPrefix;
300 sal_Int16 mnCurOutputType;
301 uno::Reference< lang::XComponent > mxMMComponent;
302 uno::Reference< sdbc::XRowSet > mxCurResultSet;
303 const char* maMMtestFilename;
306 #define DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, filter, BaseClass, selection, column) \
307 class TestName : public BaseClass { \
308 protected: \
309 virtual OUString getTestName() override { return #TestName; } \
310 public: \
311 CPPUNIT_TEST_SUITE(TestName); \
312 CPPUNIT_TEST(MailMerge); \
313 CPPUNIT_TEST_SUITE_END(); \
315 void MailMerge() { \
316 executeMailMergeTest(filename, datasource, tablename, filter, selection, column); \
318 void verify() override; \
319 }; \
320 CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \
321 void TestName::verify()
323 // Will generate the resulting document in mxMMDocument.
324 #define DECLARE_SHELL_MAILMERGE_TEST(TestName, filename, datasource, tablename) \
325 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, nullptr, MMTest, 0, nullptr)
327 // Will generate documents as files, use loadMailMergeDocument().
328 #define DECLARE_FILE_MAILMERGE_TEST(TestName, filename, datasource, tablename) \
329 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, "writer8", MMTest, 0, nullptr)
331 #define DECLARE_SHELL_MAILMERGE_TEST_SELECTION(TestName, filename, datasource, tablename, selection) \
332 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, nullptr, MMTest, selection, nullptr)
334 #define DECLARE_FILE_MAILMERGE_TEST_COLUMN(TestName, filename, datasource, tablename, column) \
335 DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, "writer8", MMTest, 0, column)
337 int MMTest::documentStartPageNumber( int document ) const
338 { // See documentStartPageNumber() .
339 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
340 CPPUNIT_ASSERT(pTextDoc);
341 SwWrtShell* shell = pTextDoc->GetDocShell()->GetWrtShell();
342 IDocumentMarkAccess* marks = shell->GetDoc()->getIDocumentMarkAccess();
343 // Unfortunately, the pages are marked using UNO bookmarks, which have internals names, so they cannot be referred to by their names.
344 // Assume that there are no other UNO bookmarks than the ones used by mail merge, and that they are in the sorted order.
345 IDocumentMarkAccess::const_iterator_t mark;
346 int pos = 0;
347 for( mark = marks->getAllMarksBegin(); mark != marks->getAllMarksEnd() && pos < document; ++mark )
349 if( IDocumentMarkAccess::GetType( **mark ) == IDocumentMarkAccess::MarkType::UNO_BOOKMARK )
350 ++pos;
352 CPPUNIT_ASSERT_EQUAL(document, pos);
353 sal_uInt16 page, dummy;
354 shell->Push();
355 shell->GotoMark( *mark );
356 shell->GetPageNum( page, dummy );
357 shell->Pop(SwCursorShell::PopMode::DeleteCurrent);
358 return page;
361 MMTest::MMTest()
362 : SwModelTestBase("/sw/qa/extras/mailmerge/data/", "writer8")
363 , mnCurOutputType(0)
364 , maMMtestFilename(nullptr)
368 DECLARE_SHELL_MAILMERGE_TEST(testMultiPageAnchoredDraws, "multiple-page-anchored-draws.odt", "4_v01.ods", "Tabelle1")
370 executeMailMerge();
372 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
373 CPPUNIT_ASSERT(pTextDoc);
374 sal_uInt16 nPhysPages = pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum();
375 CPPUNIT_ASSERT_EQUAL(sal_uInt16(8), nPhysPages);
377 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxMMComponent, uno::UNO_QUERY);
378 uno::Reference<container::XIndexAccess> xDraws = xDrawPageSupplier->getDrawPage();
379 CPPUNIT_ASSERT_EQUAL(sal_Int32(8), xDraws->getCount());
381 std::set<sal_uInt16> pages;
382 uno::Reference<beans::XPropertySet> xPropertySet;
384 for (sal_Int32 i = 0; i < xDraws->getCount(); i++)
386 xPropertySet.set(xDraws->getByIndex(i), uno::UNO_QUERY);
388 text::TextContentAnchorType nAnchorType;
389 CPPUNIT_ASSERT(xPropertySet->getPropertyValue( UNO_NAME_ANCHOR_TYPE ) >>= nAnchorType);
390 CPPUNIT_ASSERT_EQUAL( text::TextContentAnchorType_AT_PAGE, nAnchorType );
392 sal_uInt16 nAnchorPageNo = {};
393 CPPUNIT_ASSERT(xPropertySet->getPropertyValue( UNO_NAME_ANCHOR_PAGE_NO ) >>= nAnchorPageNo);
394 // are all shapes are on different page numbers?
395 CPPUNIT_ASSERT(pages.insert(nAnchorPageNo).second);
399 DECLARE_FILE_MAILMERGE_TEST(testMissingDefaultLineColor, "missing-default-line-color.ott", "one-empty-address.ods", "one-empty-address")
401 executeMailMerge();
402 // The document was created by LO version which didn't write out the default value for line color
403 // (see XMLGraphicsDefaultStyle::SetDefaults()).
404 uno::Reference<beans::XPropertySet> xPropertySet(getShape(5), uno::UNO_QUERY);
405 // Lines do not have a line color.
406 CPPUNIT_ASSERT( !xPropertySet->getPropertySetInfo()->hasPropertyByName( "LineColor" ));
407 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
408 CPPUNIT_ASSERT(pTextDoc);
409 uno::Reference< lang::XMultiServiceFactory > xFact( mxComponent, uno::UNO_QUERY );
410 uno::Reference< beans::XPropertySet > xDefaults( xFact->createInstance( "com.sun.star.drawing.Defaults" ), uno::UNO_QUERY );
411 CPPUNIT_ASSERT( xDefaults.is());
412 uno::Reference< beans::XPropertySetInfo > xInfo( xDefaults->getPropertySetInfo());
413 CPPUNIT_ASSERT( xInfo->hasPropertyByName( "LineColor" ));
414 Color lineColor;
415 xDefaults->getPropertyValue( "LineColor" ) >>= lineColor;
416 // And the default value is black (wasn't copied properly by mailmerge).
417 CPPUNIT_ASSERT_EQUAL( COL_BLACK, lineColor );
418 // And check that the resulting file has the proper default.
419 xmlDocUniquePtr pXmlDoc = parseMailMergeExport( "styles.xml" );
420 CPPUNIT_ASSERT_EQUAL( OUString( "graphic" ), getXPath(pXmlDoc, "/office:document-styles/office:styles/style:default-style[1]", "family"));
421 CPPUNIT_ASSERT_EQUAL( OUString( "#000000" ), getXPath(pXmlDoc, "/office:document-styles/office:styles/style:default-style[1]/style:graphic-properties", "stroke-color"));
424 DECLARE_FILE_MAILMERGE_TEST(testSimpleMailMerge, "simple-mail-merge.odt", "10-testing-addresses.ods", "testing-addresses")
426 executeMailMerge();
427 for( int doc = 0;
428 doc < 10;
429 ++doc )
431 loadMailMergeDocument( doc );
432 CPPUNIT_ASSERT_EQUAL( 1, getPages());
433 CPPUNIT_ASSERT_EQUAL( OUString( "Fixed text." ), getRun( getParagraph( 1 ), 1 )->getString());
434 CPPUNIT_ASSERT_EQUAL( OUString( "lastname" + OUString::number( doc + 1 )), getRun( getParagraph( 2 ), 1 )->getString());
435 CPPUNIT_ASSERT_EQUAL( OUString( "Another fixed text." ), getRun( getParagraph( 3 ), 1 )->getString());
439 DECLARE_FILE_MAILMERGE_TEST(testWriterDataSource, "writer-mail-merge.odt", "10-testing-addresses-writer.odt", "testing-addresses-writer")
441 // This failed as the .odt data source was mapped to the jdbc: protocol.
442 executeMailMerge();
443 for (int doc = 0; doc < 10; ++doc)
445 loadMailMergeDocument(doc);
446 CPPUNIT_ASSERT_EQUAL(1, getPages());
447 CPPUNIT_ASSERT_EQUAL(OUString("Fixed text."), getRun(getParagraph(1), 1)->getString());
448 CPPUNIT_ASSERT_EQUAL(OUString("lastname" + OUString::number(doc + 1)), getRun(getParagraph(2), 1)->getString());
449 CPPUNIT_ASSERT_EQUAL(OUString("Another fixed text."), getRun(getParagraph(3), 1)->getString());
453 DECLARE_FILE_MAILMERGE_TEST(testWriterMergedDataSource, "writer-merged-mail-merge.odt", "10-testing-addresses-writer-merged.odt", "testing-addresses-writer-merged")
455 // This failed with com.sun.star.lang.IndexOutOfBoundsException, leading to
456 // a crash, as the last row had merged cells in
457 // 10-testing-addresses-writer-merged.odt.
458 executeMailMerge();
459 for (int doc = 0; doc < 10; ++doc)
461 loadMailMergeDocument(doc);
462 CPPUNIT_ASSERT_EQUAL(1, getPages());
463 CPPUNIT_ASSERT_EQUAL(OUString("Fixed text."), getRun(getParagraph(1), 1)->getString());
464 CPPUNIT_ASSERT_EQUAL(OUString("lastname" + OUString::number(doc + 1)), getRun(getParagraph(2), 1)->getString());
465 CPPUNIT_ASSERT_EQUAL(OUString("Another fixed text."), getRun(getParagraph(3), 1)->getString());
469 DECLARE_FILE_MAILMERGE_TEST(test2Pages, "simple-mail-merge-2pages.odt", "10-testing-addresses.ods", "testing-addresses")
471 executeMailMerge();
472 for( int doc = 0;
473 doc < 10;
474 ++doc )
476 loadMailMergeDocument( doc );
477 OUString lastname = "lastname" + OUString::number( doc + 1 );
478 OUString firstname = "firstname" + OUString::number( doc + 1 );
479 CPPUNIT_ASSERT_EQUAL( 2, getPages());
480 CPPUNIT_ASSERT_EQUAL( OUString( "Fixed text." ), getRun( getParagraph( 1 ), 1 )->getString());
481 CPPUNIT_ASSERT_EQUAL( lastname, getRun( getParagraph( 2 ), 1 )->getString());
482 CPPUNIT_ASSERT_EQUAL( OUString( "Another fixed text." ), getRun( getParagraph( 3 ), 1 )->getString());
483 CPPUNIT_ASSERT_EQUAL( OUString(), getRun( getParagraph( 4 ), 1 )->getString()); // empty para at the end of page 1
484 CPPUNIT_ASSERT_EQUAL( OUString( "Second page." ), getRun( getParagraph( 5 ), 1 )->getString());
485 CPPUNIT_ASSERT_EQUAL( firstname, getRun( getParagraph( 6 ), 1 )->getString());
486 // Also verify the layout.
487 CPPUNIT_ASSERT_EQUAL( lastname, parseDump("/root/page[1]/body/txt[2]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
488 CPPUNIT_ASSERT_EQUAL( OUString( "Fixed text." ), parseDump("/root/page[1]/body/txt[1]", ""));
489 CPPUNIT_ASSERT_EQUAL( OUString(), parseDump("/root/page[1]/body/txt[4]", ""));
490 CPPUNIT_ASSERT_EQUAL( OUString( "Second page." ), parseDump("/root/page[2]/body/txt[1]", ""));
491 CPPUNIT_ASSERT_EQUAL( firstname, parseDump("/root/page[2]/body/txt[2]/SwParaPortion/SwLineLayout/SwFieldPortion", "expand"));
495 DECLARE_SHELL_MAILMERGE_TEST(testPageBoundariesSimpleMailMerge, "simple-mail-merge.odt", "10-testing-addresses.ods", "testing-addresses")
497 // This is like the test above, but this one uses the create-single-document-containing-everything-generated approach,
498 // and verifies that boundaries of the generated sub-documents are correct inside that document.
499 // These boundaries are done using "documentStartPageNumber<number>" UNO bookmarks (see also
500 // documentStartPageNumber() ).
501 executeMailMerge();
502 // Here getPages() works on the source document, so get pages of the resulting one.
503 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
504 CPPUNIT_ASSERT(pTextDoc);
505 CPPUNIT_ASSERT_EQUAL( sal_uInt16( 19 ), pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum()); // 10 pages, but each sub-document starts on odd page number
506 for( int doc = 0;
507 doc < 10;
508 ++doc )
510 CPPUNIT_ASSERT_EQUAL( doc * 2 + 1, documentStartPageNumber( doc ));
514 DECLARE_SHELL_MAILMERGE_TEST(testPageBoundaries2Pages, "simple-mail-merge-2pages.odt", "10-testing-addresses.ods", "testing-addresses")
516 executeMailMerge();
517 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
518 CPPUNIT_ASSERT(pTextDoc);
519 CPPUNIT_ASSERT_EQUAL( sal_uInt16( 20 ), pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum()); // 20 pages, each sub-document starts on odd page number
520 for( int doc = 0;
521 doc < 10;
522 ++doc )
524 CPPUNIT_ASSERT_EQUAL( doc * 2 + 1, documentStartPageNumber( doc ));
528 DECLARE_SHELL_MAILMERGE_TEST(testTdf89214, "tdf89214.odt", "10-testing-addresses.ods", "testing-addresses")
530 executeMailMerge();
532 uno::Reference<text::XTextDocument> xTextDocument(mxMMComponent, uno::UNO_QUERY);
533 uno::Reference<text::XTextRange> xParagraph(getParagraphOrTable(3, xTextDocument->getText()), uno::UNO_QUERY);
534 // Make sure that we assert the right paragraph.
535 CPPUNIT_ASSERT_EQUAL(OUString("a"), xParagraph->getString());
536 // This paragraph had a bullet numbering, make sure that the list id is not empty.
537 CPPUNIT_ASSERT(!getProperty<OUString>(xParagraph, "ListId").isEmpty());
540 DECLARE_SHELL_MAILMERGE_TEST(testTdf90230, "empty.odt", "10-testing-addresses.ods", "testing-addresses")
542 // MM of an empty document caused an assertion in the SwContentIndexReg dtor.
543 executeMailMerge();
546 DECLARE_SHELL_MAILMERGE_TEST(testTdf92623, "tdf92623.odt", "10-testing-addresses.ods", "testing-addresses")
548 // Copying bookmarks for MM was broken because of the StartOfContent node copy
549 // copied marks were off by one
550 executeMailMerge();
552 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
553 CPPUNIT_ASSERT(pTextDoc);
554 IDocumentMarkAccess const *pIDMA = pTextDoc->GetDocShell()->GetDoc()->getIDocumentMarkAccess();
555 // There is just one mark...
556 CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pIDMA->getAllMarksCount());
557 CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pIDMA->getBookmarksCount());
558 IDocumentMarkAccess::const_iterator_t mark = pIDMA->getAllMarksBegin();
559 // and it's a TEXT_FIELDMARK
560 CPPUNIT_ASSERT_EQUAL( sal_Int32(IDocumentMarkAccess::MarkType::TEXT_FIELDMARK),
561 sal_Int32(IDocumentMarkAccess::GetType( **mark )) );
562 SwNodeOffset src_pos = (*mark)->GetMarkPos().GetNodeIndex();
564 // Get the size of the document in nodes
565 SwDoc *doc = pTextDoc->GetDocShell()->GetDoc();
566 SwNodeOffset size = doc->GetNodes().GetEndOfContent().GetIndex() - doc->GetNodes().GetEndOfExtras().GetIndex();
567 CPPUNIT_ASSERT_EQUAL( SwNodeOffset(13), size );
568 size -= SwNodeOffset(2); // For common start and end nodes
570 // Iterate over all field marks in the target document and check that they
571 // are positioned at a multitude of the document size
572 SwXTextDocument* pMMTextDoc = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
573 CPPUNIT_ASSERT(pMMTextDoc);
574 pIDMA = pMMTextDoc->GetDocShell()->GetDoc()->getIDocumentMarkAccess();
575 // The target document has the duplicated amount of bookmarks
576 // as the helping uno bookmark from the mail merge is left in the doc
577 // TODO should be fixed!
578 CPPUNIT_ASSERT_EQUAL(sal_Int32(20), pIDMA->getAllMarksCount());
579 std::set<SwNodeOffset> pages;
580 sal_Int32 countFieldMarks = 0;
581 for( mark = pIDMA->getAllMarksBegin(); mark != pIDMA->getAllMarksEnd(); ++mark )
583 IDocumentMarkAccess::MarkType markType = IDocumentMarkAccess::GetType( **mark );
584 if( markType == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK )
586 SwNodeOffset pos = (*mark)->GetMarkPos().GetNodeIndex() - src_pos;
587 CPPUNIT_ASSERT_EQUAL(SwNodeOffset(0), pos % size);
588 CPPUNIT_ASSERT(pages.insert(pos).second);
589 countFieldMarks++;
591 else // see previous TODO
592 CPPUNIT_ASSERT_EQUAL( sal_Int32(IDocumentMarkAccess::MarkType::UNO_BOOKMARK), sal_Int32(markType) );
594 CPPUNIT_ASSERT_EQUAL(sal_Int32(10), countFieldMarks);
597 DECLARE_SHELL_MAILMERGE_TEST(testBookmarkCondition, "bookmarkcondition.fodt", "bookmarkcondition.ods", "data")
599 executeMailMerge();
601 dumpMMLayout();
602 xmlDocUniquePtr pLayout(
603 xmlParseMemory(reinterpret_cast<const char*>(xmlBufferContent(mpXmlBuffer)),
604 xmlBufferLength(mpXmlBuffer)));
606 // check that conditions on sections and bookmarks are evaluated the same
607 assertXPath(pLayout, "/root/page", 7);
608 assertXPath(pLayout, "/root/page[1]/body/section", 1);
609 assertXPath(pLayout, "/root/page[1]/body/section[1]/txt[1]/SwParaPortion/SwLineLayout", "portion", u"In den Bergen war es anstrengend.");
610 assertXPath(pLayout, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout", "portion", u"Mein Urlaub war anstrengend . ");
611 assertXPath(pLayout, "/root/page[3]/body/section", 1);
612 assertXPath(pLayout, "/root/page[3]/body/section[1]/txt[1]/SwParaPortion/SwLineLayout", "portion", u"In Barcelona war es schön.");
613 assertXPath(pLayout, "/root/page[3]/body/txt[5]/SwParaPortion/SwLineLayout", "portion", u"Mein Urlaub war schön . ");
614 assertXPath(pLayout, "/root/page[5]/body/section", 1);
615 assertXPath(pLayout, "/root/page[5]/body/section[1]/txt[1]/SwParaPortion/SwLineLayout", "portion", "In Paris war es erlebnisreich.");
616 assertXPath(pLayout, "/root/page[5]/body/txt[5]/SwParaPortion/SwLineLayout", "portion", u"Mein Urlaub war erlebnisreich . ");
617 assertXPath(pLayout, "/root/page[7]/body/section", 3);
618 assertXPath(pLayout, "/root/page[7]/body/section[1]/txt[1]/SwParaPortion/SwLineLayout", "portion", u"In den Bergen war es anstrengend.");
619 assertXPath(pLayout, "/root/page[7]/body/section[2]/txt[1]/SwParaPortion/SwLineLayout", "portion", u"In Barcelona war es schön.");
620 assertXPath(pLayout, "/root/page[7]/body/section[3]/txt[1]/SwParaPortion/SwLineLayout", "portion", u"In Paris war es erlebnisreich.");
621 assertXPath(pLayout, "/root/page[7]/body/txt[5]/SwParaPortion/SwLineLayout", "portion", u"Mein Urlaub war anstrengend schön erlebnisreich . ");
624 DECLARE_SHELL_MAILMERGE_TEST_SELECTION(testTdf95292, "linked-labels.odt", "10-testing-addresses.ods", "testing-addresses", 5)
626 // A document with two labels merged with 5 datasets should result in three pages
627 executeMailMerge();
629 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>( mxComponent.get() );
630 CPPUNIT_ASSERT( pTextDoc );
631 SwWrtShell *pWrtShell = pTextDoc->GetDocShell()->GetWrtShell();
632 CPPUNIT_ASSERT( pWrtShell->IsLabelDoc() );
634 pTextDoc = dynamic_cast<SwXTextDocument *>( mxMMComponent.get() );
635 CPPUNIT_ASSERT( pTextDoc );
636 pWrtShell = pTextDoc->GetDocShell()->GetWrtShell();
637 CPPUNIT_ASSERT( !pWrtShell->IsLabelDoc() );
638 CPPUNIT_ASSERT_EQUAL( sal_uInt16( 5 ), pWrtShell->GetPhyPageNum() );
641 DECLARE_SHELL_MAILMERGE_TEST(test_sections_first_last, "sections_first_last.odt", "10-testing-addresses.ods", "testing-addresses")
643 // A document with a leading, middle and trailing section
644 // Originally we were losing the trailing section during merge
645 executeMailMerge();
647 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
648 CPPUNIT_ASSERT(pTextDoc);
650 // Get the size of the document in nodes
651 SwDoc *pDoc = pTextDoc->GetDocShell()->GetDoc();
652 SwNodeOffset nSize = pDoc->GetNodes().GetEndOfContent().GetIndex() - pDoc->GetNodes().GetEndOfExtras().GetIndex();
653 nSize -= SwNodeOffset(2); // The common start and end node
654 CPPUNIT_ASSERT_EQUAL( SwNodeOffset(13), nSize );
656 SwXTextDocument* pTextDocMM = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
657 CPPUNIT_ASSERT(pTextDocMM);
659 SwDoc *pDocMM = pTextDocMM->GetDocShell()->GetDoc();
660 SwNodeOffset nSizeMM = pDocMM->GetNodes().GetEndOfContent().GetIndex() - pDocMM->GetNodes().GetEndOfExtras().GetIndex();
661 nSizeMM -= SwNodeOffset(2);
662 CPPUNIT_ASSERT_EQUAL( SwNodeOffset(10) * nSize, nSizeMM );
664 CPPUNIT_ASSERT_EQUAL( sal_uInt16(19), pDocMM->GetDocShell()->GetWrtShell()->GetPhyPageNum() );
666 // All even pages should be empty, all sub-documents have two pages
667 const SwRootFrame* pLayout = pDocMM->getIDocumentLayoutAccess().GetCurrentLayout();
668 const SwPageFrame* pPageFrm = static_cast<const SwPageFrame*>( pLayout->Lower() );
669 while ( pPageFrm )
671 bool bOdd = (1 == (pPageFrm->GetPhyPageNum() % 2));
672 CPPUNIT_ASSERT_EQUAL( !bOdd, pPageFrm->IsEmptyPage() );
673 CPPUNIT_ASSERT_EQUAL( sal_uInt16( bOdd ? 1 : 2 ), pPageFrm->GetVirtPageNum() );
674 pPageFrm = static_cast<const SwPageFrame*>( pPageFrm->GetNext() );
678 DECLARE_FILE_MAILMERGE_TEST_COLUMN(testDirMailMerge, "simple-mail-merge.odt", "10-testing-addresses.ods", "testing-addresses", "Filename")
680 executeMailMerge();
681 for( int doc = 1;
682 doc <= 10;
683 ++doc )
685 OUString filename = "sub/lastname" + OUString::number( doc )
686 + " firstname" + OUString::number( doc ) + ".odt";
687 loadMailMergeDocument( filename );
688 CPPUNIT_ASSERT_EQUAL( 1, getPages());
689 CPPUNIT_ASSERT_EQUAL( OUString( "Fixed text." ), getRun( getParagraph( 1 ), 1 )->getString());
690 CPPUNIT_ASSERT_EQUAL( OUString( "lastname" + OUString::number( doc )), getRun( getParagraph( 2 ), 1 )->getString());
691 CPPUNIT_ASSERT_EQUAL( OUString( "Another fixed text." ), getRun( getParagraph( 3 ), 1 )->getString());
695 DECLARE_FILE_MAILMERGE_TEST(testTdf102010, "empty.odt", "10-testing-addresses.ods", "testing-addresses")
697 // Create "correct" automatic filename for non-user-supplied-prefix
698 for (auto aNamedValueIter = mMMargs.begin(); aNamedValueIter != mMMargs.end();)
700 if ( aNamedValueIter->Name == UNO_NAME_FILE_NAME_PREFIX )
701 aNamedValueIter = mMMargs.erase( aNamedValueIter );
702 else
704 std::cout << aNamedValueIter->Name << ": " << aNamedValueIter->Value << std::endl;
705 ++aNamedValueIter;
708 mMMargs.emplace_back( UNO_NAME_SAVE_AS_SINGLE_FILE, uno::Any( true ) );
710 // Generate correct mail merge result filename
711 executeMailMerge();
712 // Don't overwrite previous result
713 executeMailMerge( true );
714 loadMailMergeDocument( 1 );
717 DECLARE_SHELL_MAILMERGE_TEST(testTdf118113, "tdf118113.odt", "tdf118113.ods", "testing-addresses")
719 executeMailMerge();
721 // The document contains a text box anchored to the page and a conditionally hidden
722 // section that is only shown for one of the 4 recipients, namely the 3rd record.
723 // In case the hidden section is shown, the page count is 3 for a single data entry, otherwise 1.
724 // Previously, the page number was calculated incorrectly which led to the
725 // text box being anchored to the wrong page.
727 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
728 CPPUNIT_ASSERT(pTextDoc);
729 // 3 documents with 1 page size each + 1 document with 3 pages
730 // + an additional page after each of the first 3 documents to make
731 // sure that each document starts on an odd page number
732 sal_uInt16 nPhysPages = pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum();
733 CPPUNIT_ASSERT_EQUAL(sal_uInt16(9), nPhysPages);
735 // verify that there is a text box for each data record
736 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxMMComponent, uno::UNO_QUERY);
737 uno::Reference<container::XIndexAccess> xDraws = xDrawPageSupplier->getDrawPage();
738 CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xDraws->getCount());
740 // verify the text box for each data record is anchored to the first page of the given data record's pages
741 std::vector<sal_uInt16> expectedPageNumbers {1, 3, 5, 9};
742 uno::Reference<beans::XPropertySet> xPropertySet;
743 for (sal_Int32 i = 0; i < xDraws->getCount(); i++)
745 xPropertySet.set(xDraws->getByIndex(i), uno::UNO_QUERY);
747 text::TextContentAnchorType nAnchorType;
748 CPPUNIT_ASSERT(xPropertySet->getPropertyValue( UNO_NAME_ANCHOR_TYPE ) >>= nAnchorType);
749 CPPUNIT_ASSERT_EQUAL( text::TextContentAnchorType_AT_PAGE, nAnchorType );
751 sal_uInt16 nAnchorPageNo = {};
752 CPPUNIT_ASSERT(xPropertySet->getPropertyValue( UNO_NAME_ANCHOR_PAGE_NO ) >>= nAnchorPageNo);
754 CPPUNIT_ASSERT_EQUAL(expectedPageNumbers.at(i), nAnchorPageNo);
759 namespace
761 constexpr char const* const EmptyValuesLegacyData[][8]
762 = { { "Heading", "Title: ", "First Name: firstname1", "Last Name: lastname1",
763 "Title: First Name: firstname1", "First Name: firstname1 Last Name: lastname1",
764 "Title: First Name: firstname1 Last Name: lastname1", "Trailing text" },
765 { "Heading", "Title: title2", "First Name: ", "Last Name: lastname2",
766 "Title: title2 First Name: ", "First Name: Last Name: lastname2",
767 "Title: title2 First Name: Last Name: lastname2", "Trailing text" },
768 { "Heading", "Title: title3", "First Name: firstname3", "Last Name: ",
769 "Title: title3 First Name: firstname3", "First Name: firstname3 Last Name: ",
770 "Title: title3 First Name: firstname3 Last Name: ", "Trailing text" },
771 { "Heading", "Title: ", "First Name: ", "Last Name: lastname4",
772 "Title: First Name: ", "First Name: Last Name: lastname4",
773 "Title: First Name: Last Name: lastname4", "Trailing text" },
774 { "Heading", "Title: title5", "First Name: ", "Last Name: ", "Title: title5 First Name: ",
775 "First Name: Last Name: ", "Title: title5 First Name: Last Name: ", "Trailing text" } };
776 constexpr char const* const EmptyValuesNewData[][8]
777 = { { "Heading", "First Name: firstname1", "Last Name: lastname1",
778 "Title: First Name: firstname1", "First Name: firstname1 Last Name: lastname1",
779 "Title: First Name: firstname1 Last Name: lastname1", "Trailing text" },
780 { "Heading", "Title: title2", "Last Name: lastname2",
781 "Title: title2 First Name: ", "First Name: Last Name: lastname2",
782 "Title: title2 First Name: Last Name: lastname2", "Trailing text" },
783 { "Heading", "Title: title3", "First Name: firstname3",
784 "Title: title3 First Name: firstname3", "First Name: firstname3 Last Name: ",
785 "Title: title3 First Name: firstname3 Last Name: ", "Trailing text" },
786 { "Heading", "Last Name: lastname4", "First Name: Last Name: lastname4",
787 "Title: First Name: Last Name: lastname4", "Trailing text" },
788 { "Heading", "Title: title5", "Title: title5 First Name: ",
789 "Title: title5 First Name: Last Name: ", "Trailing text" } };
792 // The following four tests (testEmptyValuesLegacyODT, testEmptyValuesNewODT, (testEmptyValuesLegacyFODT, testEmptyValuesNewFODT)
793 // check that for native documents without "EmptyDbFieldHidesPara" compatibility option, all paragraphs are exported visible,
794 // while for documents with the option enabled, the paragraphs with all Database fields having empty values are hidden.
796 DECLARE_FILE_MAILMERGE_TEST(testEmptyValuesLegacyODT, "tdf35798-legacy.odt", "5-with-blanks.ods",
797 "names")
799 executeMailMerge();
800 for (int doc = 0; doc < 5; ++doc)
802 loadMailMergeDocument(doc);
803 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
804 CPPUNIT_ASSERT(pTextDoc);
805 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
806 pDoc->RemoveInvisibleContent();
807 CPPUNIT_ASSERT_EQUAL(1, getPages());
808 for (int i = 0; i < 8; ++i)
810 auto xPara = getParagraph(i+1);
811 CPPUNIT_ASSERT_EQUAL_MESSAGE(
812 OString("in doc " + OString::number(doc) + " paragraph " + OString::number(i + 1))
813 .getStr(),
814 OUString::createFromAscii(EmptyValuesLegacyData[doc][i]), xPara->getString());
816 CPPUNIT_ASSERT_EQUAL(8, getParagraphs());
820 DECLARE_FILE_MAILMERGE_TEST(testEmptyValuesNewODT, "tdf35798-new.odt", "5-with-blanks.ods",
821 "names")
823 executeMailMerge();
824 for (int doc = 0; doc < 5; ++doc)
826 loadMailMergeDocument(doc);
827 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
828 CPPUNIT_ASSERT(pTextDoc);
829 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
830 pDoc->RemoveInvisibleContent();
831 CPPUNIT_ASSERT_EQUAL(1, getPages());
832 int i;
833 for (i = 0; i < 8; ++i)
835 const char* pExpected = EmptyValuesNewData[doc][i];
836 if (!pExpected)
837 break;
838 auto xPara = getParagraph(i + 1);
839 CPPUNIT_ASSERT_EQUAL_MESSAGE(
840 OString("in doc " + OString::number(doc) + " paragraph " + OString::number(i + 1))
841 .getStr(),
842 OUString::createFromAscii(pExpected), xPara->getString());
844 CPPUNIT_ASSERT_EQUAL(i, getParagraphs());
848 DECLARE_FILE_MAILMERGE_TEST(testEmptyValuesLegacyFODT, "tdf35798-legacy.fodt", "5-with-blanks.ods",
849 "names")
851 executeMailMerge();
852 for (int doc = 0; doc < 5; ++doc)
854 loadMailMergeDocument(doc);
855 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
856 CPPUNIT_ASSERT(pTextDoc);
857 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
858 pDoc->RemoveInvisibleContent();
859 CPPUNIT_ASSERT_EQUAL(1, getPages());
860 for (int i = 0; i < 8; ++i)
862 auto xPara = getParagraph(i + 1);
863 CPPUNIT_ASSERT_EQUAL_MESSAGE(
864 OString("in doc " + OString::number(doc) + " paragraph " + OString::number(i + 1))
865 .getStr(),
866 OUString::createFromAscii(EmptyValuesLegacyData[doc][i]), xPara->getString());
868 CPPUNIT_ASSERT_EQUAL(8, getParagraphs());
872 DECLARE_FILE_MAILMERGE_TEST(testEmptyValuesNewFODT, "tdf35798-new.fodt", "5-with-blanks.ods",
873 "names")
875 executeMailMerge();
876 for (int doc = 0; doc < 5; ++doc)
878 loadMailMergeDocument(doc);
879 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
880 CPPUNIT_ASSERT(pTextDoc);
881 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
882 pDoc->RemoveInvisibleContent();
883 CPPUNIT_ASSERT_EQUAL(1, getPages());
884 int i;
885 for (i = 0; i < 8; ++i)
887 const char* pExpected = EmptyValuesNewData[doc][i];
888 if (!pExpected)
889 break;
890 auto xPara = getParagraph(i + 1);
891 CPPUNIT_ASSERT_EQUAL_MESSAGE(
892 OString("in doc " + OString::number(doc) + " paragraph " + OString::number(i + 1))
893 .getStr(),
894 OUString::createFromAscii(pExpected), xPara->getString());
896 CPPUNIT_ASSERT_EQUAL(i, getParagraphs());
900 DECLARE_SHELL_MAILMERGE_TEST(testTdf118845, "tdf118845.fodt", "4_v01.ods", "Tabelle1")
902 executeMailMerge();
904 // Both male and female greetings were shown, thus each page had 3 paragraphs
906 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxMMComponent.get());
907 CPPUNIT_ASSERT(pTextDoc);
908 sal_uInt16 nPhysPages = pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum();
909 CPPUNIT_ASSERT_EQUAL(sal_uInt16(7), nPhysPages); // 4 pages, each odd, and 3 blanks
911 uno::Reference<text::XTextDocument> xTextDocument(mxMMComponent, uno::UNO_QUERY);
912 CPPUNIT_ASSERT_EQUAL(8, getParagraphs(xTextDocument->getText()));
914 uno::Reference<text::XTextRange> xParagraph(getParagraphOrTable(1, xTextDocument->getText()),
915 uno::UNO_QUERY);
916 CPPUNIT_ASSERT_EQUAL(OUString("Dear Mrs. Mustermann1,"), xParagraph->getString());
918 xParagraph.set(getParagraphOrTable(2, xTextDocument->getText()), uno::UNO_QUERY);
919 CPPUNIT_ASSERT_EQUAL(OUString(""), xParagraph->getString());
921 xParagraph.set(getParagraphOrTable(3, xTextDocument->getText()), uno::UNO_QUERY);
922 CPPUNIT_ASSERT_EQUAL(OUString("Dear Mr. Mustermann2,"), xParagraph->getString());
924 xParagraph.set(getParagraphOrTable(4, xTextDocument->getText()), uno::UNO_QUERY);
925 CPPUNIT_ASSERT_EQUAL(OUString(""), xParagraph->getString());
927 xParagraph.set(getParagraphOrTable(5, xTextDocument->getText()), uno::UNO_QUERY);
928 CPPUNIT_ASSERT_EQUAL(OUString("Dear Mrs. Mustermann3,"), xParagraph->getString());
930 xParagraph.set(getParagraphOrTable(6, xTextDocument->getText()), uno::UNO_QUERY);
931 CPPUNIT_ASSERT_EQUAL(OUString(""), xParagraph->getString());
933 xParagraph.set(getParagraphOrTable(7, xTextDocument->getText()), uno::UNO_QUERY);
934 CPPUNIT_ASSERT_EQUAL(OUString("Dear Mr. Mustermann4,"), xParagraph->getString());
936 xParagraph.set(getParagraphOrTable(8, xTextDocument->getText()), uno::UNO_QUERY);
937 CPPUNIT_ASSERT_EQUAL(OUString(""), xParagraph->getString());
940 DECLARE_SHELL_MAILMERGE_TEST(testTdf62364, "tdf62364.odt", "10-testing-addresses.ods", "testing-addresses")
942 // prepare unit test and run
943 executeMailMerge();
944 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxMMComponent.get());
945 CPPUNIT_ASSERT(pTextDoc);
946 CPPUNIT_ASSERT_EQUAL( sal_uInt16( 19 ), pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum()); // 10 pages, but each sub-document starts on odd page number
948 // check: each page (one page is one sub doc) has 4 paragraphs:
949 // - 1st and 2nd are regular paragraphs
950 // - 3rd and 4th are inside list
951 const bool nodeInList[4] = { false, false, true, true };
953 const auto & rNodes = pTextDoc->GetDocShell()->GetDoc()->GetNodes();
954 for (int pageIndex=0; pageIndex<10; pageIndex++)
956 for (int nodeIndex = 0; nodeIndex<4; nodeIndex++)
958 SwNode* aNode = rNodes[SwNodeOffset(9 + pageIndex * 4 + nodeIndex)];
959 CPPUNIT_ASSERT_EQUAL(true, aNode->IsTextNode());
961 const SwTextNode* pTextNode = aNode->GetTextNode();
962 CPPUNIT_ASSERT(pTextNode);
963 CPPUNIT_ASSERT_EQUAL(nodeInList[nodeIndex], pTextNode->GetSwAttrSet().HasItem(RES_PARATR_LIST_ID));
969 CPPUNIT_PLUGIN_IMPLEMENT();
970 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */