From 267c6f2ac71f92999e969232431ba04678e7437e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 07:54:39 +0200 Subject: Adding upstream version 4:24.2.0. Signed-off-by: Daniel Baumann --- sw/qa/extras/mailmerge/mailmerge2.cxx | 750 ++++++++++++++++++++++++++++++++++ 1 file changed, 750 insertions(+) create mode 100644 sw/qa/extras/mailmerge/mailmerge2.cxx (limited to 'sw/qa/extras/mailmerge/mailmerge2.cxx') diff --git a/sw/qa/extras/mailmerge/mailmerge2.cxx b/sw/qa/extras/mailmerge/mailmerge2.cxx new file mode 100644 index 0000000000..4a58d07b4f --- /dev/null +++ b/sw/qa/extras/mailmerge/mailmerge2.cxx @@ -0,0 +1,750 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Maps database URIs to the registered database names for quick lookups + */ +typedef std::map DBuriMap; +static DBuriMap aDBuriMap; + +class MMTest2 : public SwModelTestBase +{ +public: + MMTest2(); + + virtual void tearDown() override + { + if (mxSwTextDocument.is()) + { + if (mnCurOutputType == text::MailMergeType::SHELL) + mxSwTextDocument->GetDocShell()->DoClose(); + else + mxSwTextDocument->dispose(); + } + if (mxCurResultSet.is()) + { + css::uno::Reference( + mxCurResultSet, css::uno::UNO_QUERY_THROW)->dispose(); + } + SwModelTestBase::tearDown(); + } + + /** + * Helper func used by each unit test to test the 'mail merge' code. + * + * Registers the data source, loads the original file as reference, + * initializes the mail merge job and its default argument sequence. + * + * The 'verify' method actually has to execute the mail merge by + * calling executeMailMerge() after modifying the job arguments. + */ + void executeMailMergeTest( const char* filename, const char* datasource, const char* tablename, + char const*const filter, int selection, const char* column ) + { + maMMTest2Filename = filename; + header(); + + utl::TempFileNamed aTempDir(nullptr, true); + aTempDir.EnableKillingFile(); + const OUString aWorkDir = aTempDir.GetURL(); + const OUString aURI( createFileURL(OUString::createFromAscii(datasource)) ); + const OUString aPrefix = column ? OUString::createFromAscii( column ) : "LOMM_"; + const OUString aDBName = registerDBsource( aURI, aWorkDir ); + initMailMergeJobAndArgs( filename, tablename, aDBName, aPrefix, aWorkDir, filter, selection, column != nullptr ); + + verify(); + finish(); + + mnCurOutputType = 0; + } + + OUString registerDBsource( const OUString &aURI, const OUString &aWorkDir ) + { + OUString aDBName; + DBuriMap::const_iterator pos = aDBuriMap.find( aURI ); + if (pos == aDBuriMap.end()) + { + aDBName = SwDBManager::LoadAndRegisterDataSource( aURI, &aWorkDir ); + aDBuriMap.insert( std::pair< OUString, OUString >( aURI, aDBName ) ); + std::cout << "New datasource name: '" << aDBName << "'" << std::endl; + } + else + { + aDBName = pos->second; + std::cout << "Old datasource name: '" << aDBName << "'" << std::endl; + } + CPPUNIT_ASSERT(!aDBName.isEmpty()); + return aDBName; + } + + uno::Reference< sdbc::XRowSet > getXResultFromDataset( const char* tablename, const OUString &aDBName ) + { + uno::Reference< sdbc::XRowSet > xCurResultSet; + uno::Reference< uno::XInterface > xInstance = getMultiServiceFactory()->createInstance( "com.sun.star.sdb.RowSet" ); + uno::Reference< beans::XPropertySet > xRowSetPropSet( xInstance, uno::UNO_QUERY ); + assert( xRowSetPropSet.is() && "failed to get XPropertySet interface from RowSet" ); + if (xRowSetPropSet.is()) + { + xRowSetPropSet->setPropertyValue( "DataSourceName", uno::Any( aDBName ) ); + xRowSetPropSet->setPropertyValue( "Command", uno::Any( OUString::createFromAscii(tablename) ) ); + xRowSetPropSet->setPropertyValue( "CommandType", uno::Any( sdb::CommandType::TABLE ) ); + + uno::Reference< sdbc::XRowSet > xRowSet( xInstance, uno::UNO_QUERY ); + if (xRowSet.is()) + xRowSet->execute(); // build ResultSet from properties + xCurResultSet = xRowSet; + assert( xCurResultSet.is() && "failed to build ResultSet" ); + } + return xCurResultSet; + } + + void initMailMergeJobAndArgs( const char* filename, const char* tablename, const OUString &aDBName, + const OUString &aPrefix, const OUString &aWorkDir, + char const*const filter, int nDataSets, + const bool bPrefixIsColumn ) + { + uno::Reference< task::XJob > xJob( getMultiServiceFactory()->createInstance( "com.sun.star.text.MailMerge" ), uno::UNO_QUERY_THROW ); + mxJob.set( xJob ); + + mMMargs.reserve( 15 ); + + mMMargs.emplace_back( UNO_NAME_OUTPUT_TYPE, uno::Any( filter ? text::MailMergeType::FILE : text::MailMergeType::SHELL ) ); + mMMargs.emplace_back( UNO_NAME_DOCUMENT_URL, uno::Any( + ( createFileURL(OUString::createFromAscii(filename)) ) ) ); + mMMargs.emplace_back( UNO_NAME_DATA_SOURCE_NAME, uno::Any( aDBName ) ); + mMMargs.emplace_back( UNO_NAME_OUTPUT_URL, uno::Any( aWorkDir ) ); + if (filter) + { + mMMargs.emplace_back( UNO_NAME_FILE_NAME_PREFIX, uno::Any( aPrefix ) ); + mMMargs.emplace_back(UNO_NAME_SAVE_FILTER, uno::Any(OUString::createFromAscii(filter))); + } + + if (bPrefixIsColumn) + mMMargs.emplace_back( UNO_NAME_FILE_NAME_FROM_COLUMN, uno::Any( true ) ); + + if (tablename) + { + mMMargs.emplace_back( UNO_NAME_DAD_COMMAND_TYPE, uno::Any( sdb::CommandType::TABLE ) ); + mMMargs.emplace_back( UNO_NAME_DAD_COMMAND, uno::Any( OUString::createFromAscii(tablename) ) ); + } + + if (nDataSets > 0) + { + mxCurResultSet = getXResultFromDataset( tablename, aDBName ); + uno::Reference< sdbcx::XRowLocate > xCurRowLocate( mxCurResultSet, uno::UNO_QUERY ); + mMMargs.emplace_back( UNO_NAME_RESULT_SET, uno::Any( mxCurResultSet ) ); + std::vector< uno::Any > vResult; + vResult.reserve( nDataSets ); + sal_Int32 i; + for (i = 0, mxCurResultSet->first(); i < nDataSets; i++, mxCurResultSet->next()) + { + vResult.emplace_back( xCurRowLocate->getBookmark() ); + } + mMMargs.emplace_back( UNO_NAME_SELECTION, uno::Any( comphelper::containerToSequence(vResult) ) ); + } + + } + + void executeMailMerge( bool bDontLoadResult = false ) + { + const uno::Sequence< beans::NamedValue > aSeqMailMergeArgs = comphelper::containerToSequence( mMMargs ); + uno::Any res = mxJob->execute( aSeqMailMergeArgs ); + + bool bOk = true; + bool bMMFilenameFromColumn = false; + + for (const beans::NamedValue& rArgument : aSeqMailMergeArgs) { + const OUString &rName = rArgument.Name; + const uno::Any &rValue = rArgument.Value; + + // all error checking was already done by the MM job execution + if (rName == UNO_NAME_OUTPUT_URL) + bOk &= rValue >>= msMailMergeOutputURL; + else if (rName == UNO_NAME_FILE_NAME_PREFIX) + bOk &= rValue >>= msMailMergeOutputPrefix; + else if (rName == UNO_NAME_OUTPUT_TYPE) + bOk &= rValue >>= mnCurOutputType; + else if (rName == UNO_NAME_FILE_NAME_FROM_COLUMN) + bOk &= rValue >>= bMMFilenameFromColumn; + else if (rName == UNO_NAME_DOCUMENT_URL) + bOk &= rValue >>= msMailMergeDocumentURL; + } + + CPPUNIT_ASSERT(bOk); + + // MM via UNO just works with file names. If we load the file on + // Windows before MM uses it, MM won't work, as it's already open. + // Don't move the load before the mail merge execution! + // (see gb_CppunitTest_use_instdir_configuration) + createSwDoc(maMMTest2Filename); + + if (mnCurOutputType == text::MailMergeType::SHELL) + { + uno::Reference< lang::XComponent > xTmp; + CPPUNIT_ASSERT(res >>= xTmp); + mxSwTextDocument = dynamic_cast(xTmp.get()); + CPPUNIT_ASSERT(mxSwTextDocument.is()); + } + else + { + CPPUNIT_ASSERT_EQUAL(uno::Any(true), res); + if( !bMMFilenameFromColumn && !bDontLoadResult ) + loadMailMergeDocument( 0 ); + } + } + + void loadMailMergeDocument( const OUString &filename ) + { + assert( mnCurOutputType == text::MailMergeType::FILE ); + if (mxComponent.is()) + mxComponent->dispose(); + // Output name early, so in the case of a hang, the name of the hanging input file is visible. + std::cout << filename << ","; + mnStartTime = osl_getGlobalTimer(); + mxComponent = loadFromDesktop(msMailMergeOutputURL + "/" + filename, "com.sun.star.text.TextDocument"); + discardDumpedLayout(); + calcLayout(); + } + + /** + Loads number-th document from mail merge. Requires file output from mail merge. + */ + void loadMailMergeDocument(int number, char const*const ext = ".odt") + { + OUString name; + if (!msMailMergeOutputPrefix.isEmpty()) + name = msMailMergeOutputPrefix; + else + { + INetURLObject aURLObj; + aURLObj.SetSmartProtocol( INetProtocol::File ); + aURLObj.SetSmartURL( msMailMergeDocumentURL ); + name = aURLObj.GetBase(); + } + name += OUString::number(number) + OStringToOUString(std::string_view(ext, strlen(ext)), RTL_TEXTENCODING_ASCII_US); + loadMailMergeDocument( name ); + } + + /** + Resets currently opened layout of the original template, + and creates the layout of the document with N mails inside + (result run with text::MailMergeType::SHELL) + */ + void dumpMMLayout() + { + mpXmlBuffer = xmlBufferPtr(); + dumpLayout(static_cast(mxSwTextDocument.get())); + } + +protected: + uno::Reference< css::task::XJob > mxJob; + std::vector< beans::NamedValue > mMMargs; + OUString msMailMergeDocumentURL; + OUString msMailMergeOutputURL; + OUString msMailMergeOutputPrefix; + sal_Int16 mnCurOutputType; + rtl::Reference< SwXTextDocument > mxSwTextDocument; + uno::Reference< sdbc::XRowSet > mxCurResultSet; + const char* maMMTest2Filename; +}; + +#define DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, filter, BaseClass, selection, column) \ + class TestName : public BaseClass { \ + public: \ + CPPUNIT_TEST_SUITE(TestName); \ + CPPUNIT_TEST(MailMerge); \ + CPPUNIT_TEST_SUITE_END(); \ + \ + void MailMerge() { \ + executeMailMergeTest(filename, datasource, tablename, filter, selection, column); \ + } \ + void verify() override; \ + }; \ + CPPUNIT_TEST_SUITE_REGISTRATION(TestName); \ + void TestName::verify() + +// Will generate the resulting document in mxMMDocument. +#define DECLARE_SHELL_MAILMERGE_TEST(TestName, filename, datasource, tablename) \ + DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, nullptr, MMTest2, 0, nullptr) + +// Will generate documents as files, use loadMailMergeDocument(). +#define DECLARE_FILE_MAILMERGE_TEST(TestName, filename, datasource, tablename) \ + DECLARE_MAILMERGE_TEST(TestName, filename, datasource, tablename, "writer8", MMTest2, 0, nullptr) + +MMTest2::MMTest2() + : SwModelTestBase("/sw/qa/extras/mailmerge/data/", "writer8") + , mnCurOutputType(0) + , maMMTest2Filename(nullptr) +{ +} + +DECLARE_SHELL_MAILMERGE_TEST(tdf125522_shell, "tdf125522.odt", "10-testing-addresses.ods", "testing-addresses") +{ + // prepare unit test and run + executeMailMerge(); + + // reset currently opened layout of the original template, + // and create the layout of the document with 10 mails inside + dumpMMLayout(); + + // there should be no any text frame in output + CPPUNIT_ASSERT(mxSwTextDocument); + + const auto & rNodes = mxSwTextDocument->GetDocShell()->GetDoc()->GetNodes(); + for (SwNodeOffset nodeIndex(0); nodeIndexStartOfSectionNode()) + { + CPPUNIT_ASSERT(!aNode->StartOfSectionNode()->GetFlyFormat()); + } + } +} + +DECLARE_SHELL_MAILMERGE_TEST(testTd78611_shell, "tdf78611.odt", "10-testing-addresses.ods", "testing-addresses") +{ + // prepare unit test and run + executeMailMerge(); + + // reset currently opened layout of the original template, + // and create the layout of the document with 10 mails inside + dumpMMLayout(); + + // check: each page (one page is one sub doc) has different paragraphs and header paragraphs. + // All header paragraphs should have numbering. + + // check first page + CPPUNIT_ASSERT_EQUAL( OUString("1"), parseDump("/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); + CPPUNIT_ASSERT_EQUAL( OUString("1.1"), parseDump("/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); + CPPUNIT_ASSERT_EQUAL( OUString("1.2"), parseDump("/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); + + // check some other pages + CPPUNIT_ASSERT_EQUAL( OUString("1"), parseDump("/root/page[3]/body/txt[6]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); + CPPUNIT_ASSERT_EQUAL( OUString("1.1"), parseDump("/root/page[5]/body/txt[8]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); + CPPUNIT_ASSERT_EQUAL( OUString("1.2"), parseDump("/root/page[7]/body/txt[10]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); +} + + +DECLARE_FILE_MAILMERGE_TEST(testTd78611_file, "tdf78611.odt", "10-testing-addresses.ods", "testing-addresses") +{ + executeMailMerge(true); + for (int doc = 0; doc < 10; ++doc) + { + loadMailMergeDocument( doc ); + CPPUNIT_ASSERT_EQUAL( OUString("1"), parseDump("/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); + CPPUNIT_ASSERT_EQUAL( OUString("1.1"), parseDump("/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); + CPPUNIT_ASSERT_EQUAL( OUString("1.2"), parseDump("/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr)); + } +} + +DECLARE_SHELL_MAILMERGE_TEST(testTdf122156_shell, "linked-with-condition.odt", "5-with-blanks.ods", + "names") +{ + // A document with a linked section hidden on an "empty field" condition + // For combined documents, hidden sections are removed completely + executeMailMerge(); + CPPUNIT_ASSERT(mxSwTextDocument); + // 5 documents 1 page each, starting at odd page numbers => 9 + CPPUNIT_ASSERT_EQUAL(sal_uInt16(9), mxSwTextDocument->GetDocShell()->GetWrtShell()->GetPhyPageNum()); + uno::Reference xSections(mxSwTextDocument->getTextSections(), + uno::UNO_QUERY_THROW); + // 2 out of 5 dataset records have empty "Title" field => no sections in respective documents + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xSections->getCount()); +} + +DECLARE_FILE_MAILMERGE_TEST(testTdf122156_file, "linked-with-condition.odt", "5-with-blanks.ods", + "names") +{ + // A document with a linked section hidden on an "empty field" condition + // For separate documents, the sections are removed + executeMailMerge(); + { + loadMailMergeDocument(0); + uno::Reference xSectionsSupplier(mxComponent, + uno::UNO_QUERY_THROW); + uno::Reference xSections(xSectionsSupplier->getTextSections(), + uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount()); + } + { + loadMailMergeDocument(1); + uno::Reference xSectionsSupplier(mxComponent, + uno::UNO_QUERY_THROW); + uno::Reference xSections(xSectionsSupplier->getTextSections(), + uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + uno::Reference xSect(xSections->getByIndex(0), uno::UNO_QUERY_THROW); + // Record 2 has non-empty "Title" field => section is shown + CPPUNIT_ASSERT_EQUAL(true, getProperty(xSect, "IsVisible")); + } + { + loadMailMergeDocument(2); + uno::Reference xSectionsSupplier(mxComponent, + uno::UNO_QUERY_THROW); + uno::Reference xSections(xSectionsSupplier->getTextSections(), + uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + uno::Reference xSect(xSections->getByIndex(0), uno::UNO_QUERY_THROW); + // Record 3 has non-empty "Title" field => section is shown + CPPUNIT_ASSERT_EQUAL(true, getProperty(xSect, "IsVisible")); + } + { + loadMailMergeDocument(3); + uno::Reference xSectionsSupplier(mxComponent, + uno::UNO_QUERY_THROW); + uno::Reference xSections(xSectionsSupplier->getTextSections(), + uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount()); + } + { + loadMailMergeDocument(4); + uno::Reference xSectionsSupplier(mxComponent, + uno::UNO_QUERY_THROW); + uno::Reference xSections(xSectionsSupplier->getTextSections(), + uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + uno::Reference xSect(xSections->getByIndex(0), uno::UNO_QUERY_THROW); + // Record 5 has non-empty "Title" field => section is shown + CPPUNIT_ASSERT_EQUAL(true, getProperty(xSect, "IsVisible")); + } +} + +DECLARE_SHELL_MAILMERGE_TEST(exportDirectToPDF_shell, "linked-with-condition.odt", "5-with-blanks.ods", + "names") +{ + executeMailMerge(); + + CPPUNIT_ASSERT(mxSwTextDocument.is()); + + uno::Reference xController(mxSwTextDocument->getCurrentController()); + CPPUNIT_ASSERT(xController.is()); + + uno::Reference xSupplier(xController, uno::UNO_QUERY); + CPPUNIT_ASSERT(xSupplier.is()); + + uno::Reference xPageCursor(xSupplier->getViewCursor(), uno::UNO_QUERY); + CPPUNIT_ASSERT(xPageCursor.is()); + + xPageCursor->jumpToFirstPage(); + CPPUNIT_ASSERT_EQUAL(sal_Int16(1), xPageCursor->getPage()); + + uno::Reference xFrame(xController->getFrame()); + CPPUNIT_ASSERT(xFrame.is()); + + uno::Reference xDispatchProvider(xFrame, uno::UNO_QUERY); + CPPUNIT_ASSERT(xDispatchProvider.is()); + + util::URL aURL; + aURL.Complete = ".uno:ExportDirectToPDF"; + { + uno::Reference xParser(css::util::URLTransformer::create( + comphelper::getProcessComponentContext())); + CPPUNIT_ASSERT(xParser.is()); + xParser->parseStrict(aURL); + } + + uno::Reference xDispatch = xDispatchProvider->queryDispatch(aURL, OUString(), 0); + CPPUNIT_ASSERT(xDispatch.is()); + + const OUString sExportTo(msMailMergeOutputURL + "/ExportDirectToPDF.pdf"); + uno::Sequence aArgs { + comphelper::makePropertyValue("SynchronMode", true), + comphelper::makePropertyValue("URL", sExportTo) + }; + + xDispatch->dispatch(aURL, aArgs); + CPPUNIT_ASSERT(comphelper::DirectoryHelper::fileExists(sExportTo)); + + SvFileStream aPDFFile(sExportTo, StreamMode::READ); + SvMemoryStream aMemory; + aMemory.WriteStream(aPDFFile); + std::shared_ptr pPDFium = vcl::pdf::PDFiumLibrary::get(); + if (!pPDFium) + return; + + std::unique_ptr pPdfDocument + = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString()); + CPPUNIT_ASSERT(pPdfDocument); + CPPUNIT_ASSERT_EQUAL(5, pPdfDocument->getPageCount()); + + std::unique_ptr pPdfPage = pPdfDocument->openPage(0); + CPPUNIT_ASSERT(pPdfPage); + CPPUNIT_ASSERT_EQUAL(4, pPdfPage->getObjectCount()); +} + +DECLARE_SHELL_MAILMERGE_TEST(testTdf121168, "section_ps.odt", "4_v01.ods", "Tabelle1") +{ + // A document starting with a section on a page with non-default page style with header + executeMailMerge(); + CPPUNIT_ASSERT(mxSwTextDocument); + // 4 documents 1 page each, starting at odd page numbers => 7 + CPPUNIT_ASSERT_EQUAL(sal_uInt16(7), mxSwTextDocument->GetDocShell()->GetWrtShell()->GetPhyPageNum()); + + SwDoc* pDocMM = mxSwTextDocument->GetDocShell()->GetDoc(); + SwNodeOffset nSizeMM = pDocMM->GetNodes().GetEndOfContent().GetIndex() + - pDocMM->GetNodes().GetEndOfExtras().GetIndex() - 2; + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(16), nSizeMM); + + // All even pages should be empty, all sub-documents have one page + const SwRootFrame* pLayout = pDocMM->getIDocumentLayoutAccess().GetCurrentLayout(); + const SwPageFrame* pPageFrm = static_cast(pLayout->Lower()); + while (pPageFrm) + { + sal_uInt16 nPageNum = pPageFrm->GetPhyPageNum(); + bool bOdd = (1 == (nPageNum % 2)); + CPPUNIT_ASSERT_EQUAL(!bOdd, pPageFrm->IsEmptyPage()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(bOdd ? 1 : 2), pPageFrm->GetVirtPageNum()); + if (bOdd) + { + const SwPageDesc* pDesc = pPageFrm->GetPageDesc(); + CPPUNIT_ASSERT_EQUAL(OUString("Teststyle" + OUString::number(nPageNum / 2 + 1)), + pDesc->GetName()); + } + pPageFrm = static_cast(pPageFrm->GetNext()); + } +} + + +DECLARE_FILE_MAILMERGE_TEST(testTdf81782_file, "tdf78611.odt", "10-testing-addresses.ods", "testing-addresses") +{ + executeMailMerge(true); + for (int doc = 0; doc < 10; ++doc) + { + loadMailMergeDocument( doc ); + + // get document properties + uno::Reference xDocumentPropertiesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xDocumentProperties(xDocumentPropertiesSupplier->getDocumentProperties()); + + // check if properties were set + uno::Sequence aKeywords(xDocumentProperties->getKeywords()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aKeywords.getLength()); + CPPUNIT_ASSERT_EQUAL(OUString("one two"), aKeywords[0]); + + // check title and subject + CPPUNIT_ASSERT_EQUAL(OUString("my title"), xDocumentProperties->getTitle()); + CPPUNIT_ASSERT_EQUAL(OUString("my subject"), xDocumentProperties->getSubject()); + } +} + +// problem was: field content was duplicated & truncated +DECLARE_SHELL_MAILMERGE_TEST(testTdf81750_shell, "tdf81750.odt", "10-testing-addresses.ods", "testing-addresses") +{ + // prepare unit test and run + executeMailMerge(); + + // reset currently opened layout of the original template, + // and create the layout of the document with 10 mails inside + dumpMMLayout(); + + // check several pages page + OUString aExpected("Text: Foo "); + CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[1]/body/txt[2]"_ostr, ""_ostr)); + CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[3]/body/txt[2]"_ostr, ""_ostr)); + CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[5]/body/txt[2]"_ostr, ""_ostr)); + CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[7]/body/txt[2]"_ostr, ""_ostr)); + CPPUNIT_ASSERT_EQUAL( aExpected, parseDump("/root/page[9]/body/txt[2]"_ostr, ""_ostr)); +} + + +DECLARE_FILE_MAILMERGE_TEST(testTdf123057_file, "pagecounttest.ott", "db_pagecounttest.ods", "Sheet1") +{ + uno::Reference xSect0, xSect1; + executeMailMerge(true); + + for (int doc = 0; doc < 4; ++doc) + { + loadMailMergeDocument(doc); + + // get document properties + uno::Reference xSectionsSupplier(mxComponent, uno::UNO_QUERY_THROW); + uno::Reference xSections(xSectionsSupplier->getTextSections(), uno::UNO_QUERY_THROW); + + switch (doc) + { + case 0: + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xSections->getCount()); + xSect0.set(xSections->getByIndex(0), uno::UNO_QUERY_THROW); + xSect1.set(xSections->getByIndex(1), uno::UNO_QUERY_THROW); + + // both sections visible, page num is 2 + CPPUNIT_ASSERT_EQUAL(2, getPages()); + CPPUNIT_ASSERT_EQUAL(true, getProperty(xSect0, "IsVisible")); + CPPUNIT_ASSERT_EQUAL(true, getProperty(xSect1, "IsVisible")); + break; + case 1: + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + xSect0.set(xSections->getByIndex(0), uno::UNO_QUERY_THROW); + + // second section removed, page num is 1 + CPPUNIT_ASSERT_EQUAL(1, getPages()); + CPPUNIT_ASSERT_EQUAL(true, getProperty(xSect0, "IsVisible")); + break; + case 2: + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + xSect0.set(xSections->getByIndex(0), uno::UNO_QUERY_THROW); + + // first section removed, page num is 1 + CPPUNIT_ASSERT_EQUAL(1, getPages()); + CPPUNIT_ASSERT_EQUAL(true, getProperty(xSect0, "IsVisible")); + break; + case 3: + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount()); + // both sections removed, page num is 1 + CPPUNIT_ASSERT_EQUAL(1, getPages()); + break; + } + } +} + +// The document has a header with page number and total page count on page 2 +// (which uses page style "Default Style") but doesn't have a header set +// for the first page (which uses page style "First Page"). +// Fields in the header hadn't been replaced properly. +DECLARE_SHELL_MAILMERGE_TEST(testTdf128148, "tdf128148.odt", "4_v01.ods", "Tabelle1") +{ + executeMailMerge(); + CPPUNIT_ASSERT(mxSwTextDocument); + + // 4 documents with 2 pages each => 8 pages in total + CPPUNIT_ASSERT_EQUAL(sal_uInt16(8), mxSwTextDocument->GetDocShell()->GetWrtShell()->GetPhyPageNum()); + + SwDoc* pDocMM = mxSwTextDocument->GetDocShell()->GetDoc(); + uno::Reference xModel = mxSwTextDocument->GetDocShell()->GetBaseModel(); + uno::Reference xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + + // All odd pages have no header, all even pages should have header with text "Page 2 of 2" + const SwRootFrame* pLayout = pDocMM->getIDocumentLayoutAccess().GetCurrentLayout(); + const SwPageFrame* pPageFrm = static_cast(pLayout->Lower()); + while (pPageFrm) + { + const sal_uInt16 nPageNum = pPageFrm->GetPhyPageNum(); + const bool bIsEvenPage = ((nPageNum % 2) == 0); + + const OUString& sPageStyle = pPageFrm->GetPageDesc()->GetName(); + uno::Reference xPageStyle(xStyleFamily->getByName(sPageStyle), uno::UNO_QUERY); + + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + + // first page for every data record shouldn't have header, second should + CPPUNIT_ASSERT_EQUAL(bIsEvenPage, bHeaderIsOn); + if (bIsEvenPage) + { + // text in header on even pages with correctly replaced fields is "Page 2 of 2" + uno::Reference xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + const OUString sHeaderText = xHeaderText->getString(); + CPPUNIT_ASSERT_EQUAL(OUString("Page 2 of 2"), sHeaderText); + } + + pPageFrm = static_cast(pPageFrm->GetNext()); + } +} + +DECLARE_MAILMERGE_TEST(testGrabBag, "grabbagtest.docx", "onecell.xlsx", "Sheet1", "MS Word 2007 XML", MMTest2, 0, nullptr) +{ + executeMailMerge(true); + + loadMailMergeDocument(0, ".docx"); + + SwXTextDocument *const pTextDoc = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pTextDoc); + + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pTextDoc->GetDocShell()->GetWrtShell()->GetPhyPageNum()); + + // check grabbag + uno::Reference const xModel( + mxComponent, uno::UNO_QUERY_THROW); + uno::Sequence aInteropGrabBag; + pTextDoc->getPropertyValue("InteropGrabBag") >>= aInteropGrabBag; + CPPUNIT_ASSERT_EQUAL(sal_Int32(12), aInteropGrabBag.getLength()); + + // check table border - comes from table style "Tabellenraster" + uno::Reference const xTable(getParagraphOrTable(1, pTextDoc->getText()), uno::UNO_QUERY_THROW); + uno::Reference const xTableProps(xTable, uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(table::TableBorder( + table::BorderLine(util::Color(0), 0, 18, 0), true, + table::BorderLine(util::Color(0), 0, 18, 0), true, + table::BorderLine(util::Color(0), 0, 18, 0), true, + table::BorderLine(util::Color(0), 0, 18, 0), true, + table::BorderLine(util::Color(0), 0, 18, 0), true, + table::BorderLine(util::Color(0), 0, 0, 0), true, + sal_Int16(191), true), + getProperty(xTableProps, "TableBorder")); + + // check font is Arial - comes from theme (wrong result was "" - nothing) + uno::Reference const xCell(xTable->getCellByName("A1"), uno::UNO_QUERY_THROW); + uno::Reference const xParaA1(getParagraphOrTable(1, xCell->getText()), uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(OUString("Arial"), getProperty(xParaA1, "CharFontName")); +} + +namespace com::sun::star::table { + +static std::ostream& operator<<(std::ostream& rStream, table::BorderLine const& rLine) +{ + rStream << "BorderLine(" << rLine.Color << "," << rLine.InnerLineWidth << "," << rLine.OuterLineWidth << "," << rLine.LineDistance << ")"; + return rStream; +} + +static std::ostream& operator<<(std::ostream& rStream, table::TableBorder const& rBorder) +{ + rStream << "TableBorder(\n " + << rBorder.TopLine << "," << static_cast(rBorder.IsTopLineValid) << ",\n " + << rBorder.BottomLine << "," << static_cast(rBorder.IsBottomLineValid) << ",\n " + << rBorder.LeftLine << "," << static_cast(rBorder.IsLeftLineValid) << ",\n " + << rBorder.RightLine << "," << static_cast(rBorder.IsRightLineValid) << ",\n " + << rBorder.HorizontalLine << "," << static_cast(rBorder.IsHorizontalLineValid) << ",\n " + << rBorder.VerticalLine << "," << static_cast(rBorder.IsVerticalLineValid) << ",\n " + << rBorder.Distance << "," << static_cast(rBorder.IsDistanceValid) << ")"; + return rStream; +} + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3