Add Marathi autocorrect
[LibreOffice.git] / sw / source / uibase / dbui / dbmgr.cxx
blob022104934d8c98d46a068ff9774f069c64eda42c
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <cassert>
24 #include <unotxdoc.hxx>
25 #include <sfx2/app.hxx>
26 #include <com/sun/star/sdb/CommandType.hpp>
27 #include <com/sun/star/sdb/XDocumentDataSource.hpp>
28 #include <com/sun/star/lang/DisposedException.hpp>
29 #include <com/sun/star/lang/XEventListener.hpp>
30 #include <com/sun/star/uri/UriReferenceFactory.hpp>
31 #include <com/sun/star/uri/VndSunStarPkgUrlReferenceFactory.hpp>
32 #include <com/sun/star/util/NumberFormatter.hpp>
33 #include <com/sun/star/sdb/DatabaseContext.hpp>
34 #include <com/sun/star/sdb/TextConnectionSettings.hpp>
35 #include <com/sun/star/sdb/XCompletedConnection.hpp>
36 #include <com/sun/star/sdb/XCompletedExecution.hpp>
37 #include <com/sun/star/container/XChild.hpp>
38 #include <com/sun/star/text/MailMergeEvent.hpp>
39 #include <com/sun/star/frame/XStorable.hpp>
40 #include <com/sun/star/task/InteractionHandler.hpp>
41 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
42 #include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
43 #include <com/sun/star/beans/XPropertySet.hpp>
44 #include <vcl/errinf.hxx>
45 #include <vcl/print.hxx>
46 #include <vcl/scheduler.hxx>
47 #include <sfx2/fcontnr.hxx>
48 #include <sfx2/filedlghelper.hxx>
49 #include <sfx2/viewfrm.hxx>
50 #include <dbconfig.hxx>
51 #include <unotools/tempfile.hxx>
52 #include <unotools/pathoptions.hxx>
53 #include <svl/numformat.hxx>
54 #include <svl/zforlist.hxx>
55 #include <svl/stritem.hxx>
56 #include <sfx2/docfile.hxx>
57 #include <sfx2/docfilt.hxx>
58 #include <sfx2/progress.hxx>
59 #include <sfx2/dispatch.hxx>
60 #include <cmdid.h>
61 #include <swmodule.hxx>
62 #include <view.hxx>
63 #include <docsh.hxx>
64 #include <edtwin.hxx>
65 #include <wrtsh.hxx>
66 #include <fldbas.hxx>
67 #include <dbui.hxx>
68 #include <dbmgr.hxx>
69 #include <doc.hxx>
70 #include <IDocumentLinksAdministration.hxx>
71 #include <IDocumentFieldsAccess.hxx>
72 #include <IDocumentUndoRedo.hxx>
73 #include <swwait.hxx>
74 #include <swunohelper.hxx>
75 #include <strings.hrc>
76 #include <mmconfigitem.hxx>
77 #include <com/sun/star/sdbc/XRowSet.hpp>
78 #include <com/sun/star/sdbcx/XTablesSupplier.hpp>
79 #include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
80 #include <com/sun/star/sdb/XQueriesSupplier.hpp>
81 #include <com/sun/star/sdb/XColumn.hpp>
82 #include <com/sun/star/sdbc/DataType.hpp>
83 #include <com/sun/star/sdbc/ResultSetType.hpp>
84 #include <com/sun/star/sdbc/SQLException.hpp>
85 #include <com/sun/star/mail/MailAttachment.hpp>
86 #include <comphelper/processfactory.hxx>
87 #include <comphelper/property.hxx>
88 #include <comphelper/propertyvalue.hxx>
89 #include <comphelper/storagehelper.hxx>
90 #include <comphelper/string.hxx>
91 #include <comphelper/types.hxx>
92 #include <mailmergehelper.hxx>
93 #include <maildispatcher.hxx>
94 #include <svtools/htmlcfg.hxx>
95 #include <i18nlangtag/languagetag.hxx>
96 #include <com/sun/star/util/XNumberFormatTypes.hpp>
97 #include <svl/numuno.hxx>
98 #include <connectivity/dbtools.hxx>
99 #include <connectivity/dbconversion.hxx>
100 #include <unotools/charclass.hxx>
101 #include <comphelper/diagnose_ex.hxx>
103 #include <unomailmerge.hxx>
104 #include <sfx2/event.hxx>
105 #include <svx/dataaccessdescriptor.hxx>
106 #include <rtl/textenc.h>
107 #include <rtl/tencinfo.h>
108 #include <cppuhelper/implbase.hxx>
109 #include <ndindex.hxx>
110 #include <swevent.hxx>
111 #include <sal/log.hxx>
112 #include <swabstdlg.hxx>
113 #include <vector>
114 #include <section.hxx>
115 #include <rootfrm.hxx>
116 #include <calc.hxx>
117 #include <dbfld.hxx>
118 #include <IDocumentState.hxx>
119 #include <imaildsplistener.hxx>
120 #include <iodetect.hxx>
121 #include <IDocumentDeviceAccess.hxx>
123 #include <memory>
124 #include <mutex>
125 #include <comphelper/propertysequence.hxx>
127 using namespace ::com::sun::star;
128 using namespace sw;
130 namespace {
132 void lcl_emitEvent(SfxEventHintId nEventId, sal_Int32 nStrId, SfxObjectShell* pDocShell)
134 SfxGetpApp()->NotifyEvent(SfxEventHint(nEventId,
135 SwDocShell::GetEventName(nStrId),
136 pDocShell));
139 // Construct vnd.sun.star.pkg:// URL
140 OUString ConstructVndSunStarPkgUrl(const OUString& rMainURL, std::u16string_view rStreamRelPath)
142 const auto& xContext(comphelper::getProcessComponentContext());
143 auto xUri = css::uri::UriReferenceFactory::create(xContext)->parse(rMainURL);
144 assert(xUri.is());
145 xUri = css::uri::VndSunStarPkgUrlReferenceFactory::create(xContext)
146 ->createVndSunStarPkgUrlReference(xUri);
147 assert(xUri.is());
148 return xUri->getUriReference() + "/"
149 + INetURLObject::encode(
150 rStreamRelPath, INetURLObject::PART_FPATH,
151 INetURLObject::EncodeMechanism::All);
156 std::vector<std::pair<SwDocShell*, OUString>> SwDBManager::s_aUncommittedRegistrations;
158 namespace {
160 enum class SwDBNextRecord { NEXT, FIRST };
164 static bool lcl_ToNextRecord( SwDSParam* pParam, const SwDBNextRecord action = SwDBNextRecord::NEXT );
166 namespace {
168 enum class WorkingDocType { SOURCE, TARGET, COPY };
172 static SfxObjectShell* lcl_CreateWorkingDocument(
173 const WorkingDocType aType, const SwWrtShell &rSourceWrtShell,
174 const vcl::Window *pSourceWindow,
175 SwDBManager** const ppDBManager,
176 SwView** const pView, SwWrtShell** const pWrtShell, rtl::Reference<SwDoc>* const pDoc );
178 static bool lcl_getCountFromResultSet( sal_Int32& rCount, const SwDSParam* pParam )
180 rCount = pParam->aSelection.getLength();
181 if ( rCount > 0 )
182 return true;
184 uno::Reference<beans::XPropertySet> xPrSet(pParam->xResultSet, uno::UNO_QUERY);
185 if ( xPrSet.is() )
189 bool bFinal = false;
190 uno::Any aFinal = xPrSet->getPropertyValue(u"IsRowCountFinal"_ustr);
191 aFinal >>= bFinal;
192 if(!bFinal)
194 pParam->xResultSet->last();
195 pParam->xResultSet->first();
197 uno::Any aCount = xPrSet->getPropertyValue(u"RowCount"_ustr);
198 if( aCount >>= rCount )
199 return true;
201 catch(const uno::Exception&)
205 return false;
208 class SwDBManager::ConnectionDisposedListener_Impl
209 : public cppu::WeakImplHelper< lang::XEventListener >
211 private:
212 SwDBManager * m_pDBManager;
214 virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;
216 public:
217 explicit ConnectionDisposedListener_Impl(SwDBManager& rMgr);
219 void Dispose() { m_pDBManager = nullptr; }
223 namespace {
225 /// Listens to removed data sources, and if it's one that's embedded into this document, triggers embedding removal.
226 class SwDataSourceRemovedListener : public cppu::WeakImplHelper<sdb::XDatabaseRegistrationsListener>
228 uno::Reference<sdb::XDatabaseContext> m_xDatabaseContext;
229 SwDBManager* m_pDBManager;
231 public:
232 explicit SwDataSourceRemovedListener(SwDBManager& rDBManager);
233 virtual ~SwDataSourceRemovedListener() override;
234 virtual void SAL_CALL registeredDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent) override;
235 virtual void SAL_CALL revokedDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent) override;
236 virtual void SAL_CALL changedDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent) override;
237 virtual void SAL_CALL disposing(const lang::EventObject& rObject) override;
238 void Dispose();
243 SwDataSourceRemovedListener::SwDataSourceRemovedListener(SwDBManager& rDBManager)
244 : m_pDBManager(&rDBManager)
246 const uno::Reference<uno::XComponentContext>& xComponentContext(comphelper::getProcessComponentContext());
247 m_xDatabaseContext = sdb::DatabaseContext::create(xComponentContext);
248 m_xDatabaseContext->addDatabaseRegistrationsListener(this);
251 SwDataSourceRemovedListener::~SwDataSourceRemovedListener()
253 if (m_xDatabaseContext.is())
254 m_xDatabaseContext->removeDatabaseRegistrationsListener(this);
257 void SAL_CALL SwDataSourceRemovedListener::registeredDatabaseLocation(const sdb::DatabaseRegistrationEvent& /*rEvent*/)
261 void SAL_CALL SwDataSourceRemovedListener::revokedDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent)
263 if (!m_pDBManager || m_pDBManager->getEmbeddedName().isEmpty())
264 return;
266 SwDoc* pDoc = m_pDBManager->getDoc();
267 if (!pDoc)
268 return;
270 SwDocShell* pDocShell = pDoc->GetDocShell();
271 if (!pDocShell)
272 return;
274 const OUString sTmpName = ConstructVndSunStarPkgUrl(
275 pDocShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE),
276 m_pDBManager->getEmbeddedName());
278 if (sTmpName != rEvent.OldLocation)
279 return;
281 // The revoked database location is inside this document, then remove the
282 // embedding, as otherwise it would be back on the next reload of the
283 // document.
284 pDocShell->GetStorage()->removeElement(m_pDBManager->getEmbeddedName());
285 m_pDBManager->setEmbeddedName(OUString(), *pDocShell);
288 void SAL_CALL SwDataSourceRemovedListener::changedDatabaseLocation(const sdb::DatabaseRegistrationEvent& rEvent)
290 if (rEvent.OldLocation != rEvent.NewLocation)
291 revokedDatabaseLocation(rEvent);
294 void SwDataSourceRemovedListener::disposing(const lang::EventObject& /*rObject*/)
296 m_xDatabaseContext.clear();
299 void SwDataSourceRemovedListener::Dispose()
301 m_pDBManager = nullptr;
304 struct SwDBManager::SwDBManager_Impl
306 std::unique_ptr<SwDSParam> pMergeData;
307 VclPtr<AbstractMailMergeDlg> pMergeDialog;
308 rtl::Reference<SwDBManager::ConnectionDisposedListener_Impl> m_xDisposeListener;
309 rtl::Reference<SwDataSourceRemovedListener> m_xDataSourceRemovedListener;
310 std::mutex m_aAllEmailSendMutex;
311 uno::Reference< mail::XMailMessage> m_xLastMessage;
313 explicit SwDBManager_Impl(SwDBManager& rDBManager)
314 : m_xDisposeListener(new ConnectionDisposedListener_Impl(rDBManager))
317 ~SwDBManager_Impl()
319 m_xDisposeListener->Dispose();
320 if (m_xDataSourceRemovedListener.is())
321 m_xDataSourceRemovedListener->Dispose();
325 static void lcl_InitNumberFormatter(SwDSParam& rParam, uno::Reference<sdbc::XDataSource> const & xSource)
327 const uno::Reference<uno::XComponentContext>& xContext = ::comphelper::getProcessComponentContext();
328 rParam.xFormatter = util::NumberFormatter::create(xContext);
329 uno::Reference<beans::XPropertySet> xSourceProps(
330 (xSource.is()
331 ? xSource
332 : SwDBManager::getDataSourceAsParent(
333 rParam.xConnection, rParam.sDataSource)),
334 uno::UNO_QUERY);
335 if(!xSourceProps.is())
336 return;
338 uno::Any aFormats = xSourceProps->getPropertyValue(u"NumberFormatsSupplier"_ustr);
339 if(!aFormats.hasValue())
340 return;
342 uno::Reference<util::XNumberFormatsSupplier> xSuppl;
343 aFormats >>= xSuppl;
344 if(xSuppl.is())
346 uno::Reference< beans::XPropertySet > xSettings = xSuppl->getNumberFormatSettings();
347 uno::Any aNull = xSettings->getPropertyValue(u"NullDate"_ustr);
348 aNull >>= rParam.aNullDate;
349 if(rParam.xFormatter.is())
350 rParam.xFormatter->attachNumberFormatsSupplier(xSuppl);
354 static bool lcl_MoveAbsolute(SwDSParam* pParam, tools::Long nAbsPos)
356 bool bRet = false;
359 if(pParam->aSelection.hasElements())
361 if(pParam->aSelection.getLength() <= nAbsPos)
363 pParam->bEndOfDB = true;
364 bRet = false;
366 else
368 pParam->nSelectionIndex = nAbsPos;
369 sal_Int32 nPos = 0;
370 pParam->aSelection.getConstArray()[ pParam->nSelectionIndex ] >>= nPos;
371 pParam->bEndOfDB = !pParam->xResultSet->absolute( nPos );
372 bRet = !pParam->bEndOfDB;
375 else if(pParam->bScrollable)
377 bRet = pParam->xResultSet->absolute( nAbsPos );
379 else
381 OSL_FAIL("no absolute positioning available");
384 catch(const uno::Exception&)
387 return bRet;
390 static void lcl_GetColumnCnt(SwDSParam *pParam,
391 const uno::Reference< beans::XPropertySet > &rColumnProps,
392 LanguageType nLanguage, OUString &rResult, double* pNumber)
394 SwDBFormatData aFormatData;
395 if(!pParam->xFormatter.is())
397 uno::Reference<sdbc::XDataSource> xSource = SwDBManager::getDataSourceAsParent(
398 pParam->xConnection,pParam->sDataSource);
399 lcl_InitNumberFormatter(*pParam, xSource );
401 aFormatData.aNullDate = pParam->aNullDate;
402 aFormatData.xFormatter = pParam->xFormatter;
404 aFormatData.aLocale = LanguageTag( nLanguage ).getLocale();
406 rResult = SwDBManager::GetDBField( rColumnProps, aFormatData, pNumber);
409 static bool lcl_GetColumnCnt(SwDSParam* pParam, const OUString& rColumnName,
410 LanguageType nLanguage, OUString& rResult, double* pNumber)
412 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( pParam->xResultSet, uno::UNO_QUERY );
413 uno::Reference<container::XNameAccess> xCols;
416 xCols = xColsSupp->getColumns();
418 catch(const lang::DisposedException&)
421 if(!xCols.is() || !xCols->hasByName(rColumnName))
422 return false;
423 uno::Any aCol = xCols->getByName(rColumnName);
424 uno::Reference< beans::XPropertySet > xColumnProps;
425 aCol >>= xColumnProps;
426 lcl_GetColumnCnt( pParam, xColumnProps, nLanguage, rResult, pNumber );
427 return true;
430 // import data
431 bool SwDBManager::Merge( const SwMergeDescriptor& rMergeDesc )
433 assert( !m_bInMerge && !m_pImpl->pMergeData && "merge already activated!" );
435 SfxObjectShellLock xWorkObjSh;
436 SwWrtShell *pWorkShell = nullptr;
437 rtl::Reference<SwDoc> pWorkDoc;
438 SwDBManager *pWorkDocOrigDBManager = nullptr;
440 switch( rMergeDesc.nMergeType )
442 case DBMGR_MERGE_PRINTER:
443 case DBMGR_MERGE_EMAIL:
444 case DBMGR_MERGE_FILE:
445 case DBMGR_MERGE_SHELL:
447 SwDocShell *pSourceDocSh = rMergeDesc.rSh.GetView().GetDocShell();
448 if( pSourceDocSh->IsModified() )
450 pWorkDocOrigDBManager = this;
451 xWorkObjSh = lcl_CreateWorkingDocument(
452 WorkingDocType::SOURCE, rMergeDesc.rSh, nullptr,
453 &pWorkDocOrigDBManager, nullptr, &pWorkShell, &pWorkDoc );
455 [[fallthrough]];
458 default:
459 if( !xWorkObjSh.Is() )
460 pWorkShell = &rMergeDesc.rSh;
461 break;
464 SwDBData aData;
465 aData.nCommandType = sdb::CommandType::TABLE;
466 uno::Reference<sdbc::XResultSet> xResSet;
467 uno::Sequence<uno::Any> aSelection;
468 uno::Reference< sdbc::XConnection> xConnection;
470 aData.sDataSource = rMergeDesc.rDescriptor.getDataSource();
471 rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::Command] >>= aData.sCommand;
472 rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::CommandType] >>= aData.nCommandType;
474 if ( rMergeDesc.rDescriptor.has(svx::DataAccessDescriptorProperty::Cursor) )
475 rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::Cursor] >>= xResSet;
476 if ( rMergeDesc.rDescriptor.has(svx::DataAccessDescriptorProperty::Selection) )
477 rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::Selection] >>= aSelection;
478 if ( rMergeDesc.rDescriptor.has(svx::DataAccessDescriptorProperty::Connection) )
479 rMergeDesc.rDescriptor[svx::DataAccessDescriptorProperty::Connection] >>= xConnection;
481 if((aData.sDataSource.isEmpty() || aData.sCommand.isEmpty()) && !xResSet.is())
483 return false;
486 m_pImpl->pMergeData.reset(new SwDSParam(aData, xResSet, aSelection));
487 SwDSParam* pTemp = FindDSData(aData, false);
488 if(pTemp)
489 *pTemp = *m_pImpl->pMergeData;
490 else
492 // calls from the calculator may have added a connection with an invalid commandtype
493 //"real" data base connections added here have to re-use the already available
494 //DSData and set the correct CommandType
495 aData.nCommandType = -1;
496 pTemp = FindDSData(aData, false);
497 if(pTemp)
498 *pTemp = *m_pImpl->pMergeData;
499 else
501 m_DataSourceParams.push_back(std::make_unique<SwDSParam>(*m_pImpl->pMergeData));
504 uno::Reference<lang::XComponent> xComponent(m_DataSourceParams.back()->xConnection, uno::UNO_QUERY);
505 if(xComponent.is())
506 xComponent->addEventListener(m_pImpl->m_xDisposeListener);
508 catch(const uno::Exception&)
513 if(!m_pImpl->pMergeData->xConnection.is())
514 m_pImpl->pMergeData->xConnection = xConnection;
515 // add an XEventListener
517 lcl_ToNextRecord(m_pImpl->pMergeData.get(), SwDBNextRecord::FIRST);
519 uno::Reference<sdbc::XDataSource> xSource = SwDBManager::getDataSourceAsParent(xConnection,aData.sDataSource);
521 lcl_InitNumberFormatter(*m_pImpl->pMergeData, xSource);
523 assert(pWorkShell);
524 pWorkShell->ChgDBData(aData);
525 m_bInMerge = true;
527 if (IsInitDBFields())
529 // with database fields without DB-Name, use DB-Name from Doc
530 std::vector<OUString> aDBNames;
531 aDBNames.emplace_back();
532 SwDBData aInsertData = pWorkShell->GetDBData();
533 OUString sDBName = aInsertData.sDataSource
534 + OUStringChar(DB_DELIM) + aInsertData.sCommand
535 + OUStringChar(DB_DELIM)
536 + OUString::number(aInsertData.nCommandType);
537 pWorkShell->ChangeDBFields( aDBNames, sDBName);
538 SetInitDBFields(false);
541 bool bRet = true;
542 switch(rMergeDesc.nMergeType)
544 case DBMGR_MERGE:
545 pWorkShell->StartAllAction();
546 pWorkShell->SwViewShell::UpdateFields( true );
547 pWorkShell->SetModified();
548 pWorkShell->EndAllAction();
549 break;
551 case DBMGR_MERGE_PRINTER:
552 case DBMGR_MERGE_EMAIL:
553 case DBMGR_MERGE_FILE:
554 case DBMGR_MERGE_SHELL:
555 // save files and send them as e-Mail if required
556 bRet = MergeMailFiles(pWorkShell, rMergeDesc);
557 break;
559 default:
560 // insert selected entries
561 // (was: InsertRecord)
562 ImportFromConnection(pWorkShell);
563 break;
566 m_pImpl->pMergeData.reset();
568 if( xWorkObjSh.Is() )
570 pWorkDoc->SetDBManager( pWorkDocOrigDBManager );
571 xWorkObjSh->DoClose();
574 m_bInMerge = false;
576 return bRet;
579 void SwDBManager::ImportFromConnection( SwWrtShell* pSh )
581 if(!m_pImpl->pMergeData || m_pImpl->pMergeData->bEndOfDB)
582 return;
584 pSh->StartAllAction();
585 pSh->StartUndo();
586 bool bGroupUndo(pSh->DoesGroupUndo());
587 pSh->DoGroupUndo(false);
589 if( pSh->HasSelection() )
590 pSh->DelRight();
592 std::optional<SwWait> oWait;
595 sal_uInt32 i = 0;
596 do {
598 ImportDBEntry(pSh);
599 if( 10 == ++i )
600 oWait.emplace( *pSh->GetView().GetDocShell(), true);
602 } while(ToNextMergeRecord());
605 pSh->DoGroupUndo(bGroupUndo);
606 pSh->EndUndo();
607 pSh->EndAllAction();
610 void SwDBManager::ImportDBEntry(SwWrtShell* pSh)
612 if(!m_pImpl->pMergeData || m_pImpl->pMergeData->bEndOfDB)
613 return;
615 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY );
616 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
617 OUStringBuffer sStr;
618 uno::Sequence<OUString> aColNames = xCols->getElementNames();
619 const OUString* pColNames = aColNames.getConstArray();
620 tools::Long nLength = aColNames.getLength();
621 for(tools::Long i = 0; i < nLength; i++)
623 uno::Any aCol = xCols->getByName(pColNames[i]);
624 uno::Reference< beans::XPropertySet > xColumnProp;
625 aCol >>= xColumnProp;
626 SwDBFormatData aDBFormat;
627 sStr.append(GetDBField( xColumnProp, aDBFormat));
628 if (i < nLength - 1)
629 sStr.append("\t");
631 pSh->SwEditShell::Insert2(sStr.makeStringAndClear());
632 pSh->SwFEShell::SplitNode(); // line feed
635 bool SwDBManager::GetTableNames(weld::ComboBox& rBox, const OUString& rDBName)
637 bool bRet = false;
638 OUString sOldTableName(rBox.get_active_text());
639 rBox.clear();
640 SwDSParam* pParam = FindDSConnection(rDBName, false);
641 uno::Reference< sdbc::XConnection> xConnection;
642 if (pParam && pParam->xConnection.is())
643 xConnection = pParam->xConnection;
644 else
646 if ( !rDBName.isEmpty() )
647 xConnection = RegisterConnection( rDBName );
649 if (xConnection.is())
651 uno::Reference<sdbcx::XTablesSupplier> xTSupplier(xConnection, uno::UNO_QUERY);
652 if(xTSupplier.is())
654 uno::Reference<container::XNameAccess> xTables = xTSupplier->getTables();
655 const uno::Sequence<OUString> aTables = xTables->getElementNames();
656 for (const OUString& rTable : aTables)
657 rBox.append(u"0"_ustr, rTable);
659 uno::Reference<sdb::XQueriesSupplier> xQSupplier(xConnection, uno::UNO_QUERY);
660 if(xQSupplier.is())
662 uno::Reference<container::XNameAccess> xQueries = xQSupplier->getQueries();
663 const uno::Sequence<OUString> aQueries = xQueries->getElementNames();
664 for (const OUString& rQuery : aQueries)
665 rBox.append(u"1"_ustr, rQuery);
667 if (!sOldTableName.isEmpty())
668 rBox.set_active_text(sOldTableName);
669 bRet = true;
671 return bRet;
674 // fill Listbox with column names of a database
675 void SwDBManager::GetColumnNames(weld::ComboBox& rBox,
676 const OUString& rDBName, const OUString& rTableName)
678 SwDBData aData;
679 aData.sDataSource = rDBName;
680 aData.sCommand = rTableName;
681 aData.nCommandType = -1;
682 SwDSParam* pParam = FindDSData(aData, false);
683 uno::Reference< sdbc::XConnection> xConnection;
684 if(pParam && pParam->xConnection.is())
685 xConnection = pParam->xConnection;
686 else
688 xConnection = RegisterConnection( rDBName );
690 GetColumnNames(rBox, xConnection, rTableName);
693 void SwDBManager::GetColumnNames(weld::ComboBox& rBox,
694 uno::Reference< sdbc::XConnection> const & xConnection,
695 const OUString& rTableName)
697 rBox.clear();
698 uno::Reference< sdbcx::XColumnsSupplier> xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName);
699 if(xColsSupp.is())
701 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
702 const uno::Sequence<OUString> aColNames = xCols->getElementNames();
703 for (const OUString& rColName : aColNames)
705 rBox.append_text(rColName);
707 ::comphelper::disposeComponent( xColsSupp );
711 SwDBManager::SwDBManager(SwDoc* pDoc)
712 : m_aMergeStatus( MergeStatus::Ok )
713 , m_bInitDBFields(false)
714 , m_bInMerge(false)
715 , m_bMergeSilent(false)
716 , m_pImpl(new SwDBManager_Impl(*this))
717 , m_pMergeEvtSrc(nullptr)
718 , m_pDoc(pDoc)
722 void SwDBManager::ImplDestroy()
724 RevokeLastRegistrations();
726 // copy required, m_DataSourceParams can be modified while disposing components
727 std::vector<uno::Reference<sdbc::XConnection>> aCopiedConnections;
728 for (const auto & pParam : m_DataSourceParams)
730 if(pParam->xConnection.is())
732 aCopiedConnections.push_back(pParam->xConnection);
735 for (const auto & xConnection : aCopiedConnections)
739 uno::Reference<lang::XComponent> xComp(xConnection, uno::UNO_QUERY);
740 if(xComp.is())
741 xComp->dispose();
743 catch(const uno::RuntimeException&)
745 //may be disposed already since multiple entries may have used the same connection
750 SwDBManager::~SwDBManager()
752 suppress_fun_call_w_exception(ImplDestroy());
755 static void lcl_RemoveSectionLinks( SwWrtShell& rWorkShell )
757 //reset all links of the sections of synchronized labels
758 size_t nSections = rWorkShell.GetSectionFormatCount();
759 for (size_t nSection = 0; nSection < nSections; ++nSection)
761 SwSectionData aSectionData( *rWorkShell.GetSectionFormat( nSection ).GetSection() );
762 if( aSectionData.GetType() == SectionType::FileLink )
764 aSectionData.SetType( SectionType::Content );
765 aSectionData.SetLinkFileName( OUString() );
766 rWorkShell.UpdateSection( nSection, aSectionData );
769 rWorkShell.SetLabelDoc( false );
772 static void lcl_SaveDebugDoc( SfxObjectShell *xTargetDocShell,
773 const char *name, int no = 0 )
775 static OUString sTempDirURL;
776 if( sTempDirURL.isEmpty() )
778 SvtPathOptions aPathOpt;
779 utl::TempFileNamed aTempDir( &aPathOpt.GetTempPath(), true );
780 if( aTempDir.IsValid() )
782 INetURLObject aTempDirURL( aTempDir.GetURL() );
783 sTempDirURL = aTempDirURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
784 SAL_INFO( "sw.mailmerge", "Dump directory: " << sTempDirURL );
787 if( sTempDirURL.isEmpty() )
788 return;
790 OUString basename = OUString::createFromAscii( name );
791 if (no > 0)
792 basename += OUString::number(no) + "-";
793 // aTempFile is not deleted, but that seems to be intentional
794 utl::TempFileNamed aTempFile( basename, true, u".odt", &sTempDirURL );
795 INetURLObject aTempFileURL( aTempFile.GetURL() );
796 SfxMedium aDstMed(
797 aTempFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
798 StreamMode::STD_READWRITE );
799 bool bAnyError = !xTargetDocShell->DoSaveAs( aDstMed );
800 // xObjectShell->DoSaveCompleted crashes the mail merge unit tests, so skip it
801 bAnyError |= (ERRCODE_NONE != xTargetDocShell->GetErrorIgnoreWarning());
802 if( bAnyError )
803 SAL_WARN( "sw.mailmerge", "Error saving: " << aTempFile.GetURL() );
804 else
805 SAL_INFO( "sw.mailmerge", "Saved doc as: " << aTempFile.GetURL() );
808 static bool lcl_SaveDoc(
809 const INetURLObject* pFileURL,
810 const std::shared_ptr<const SfxFilter>& pStoreToFilter,
811 const OUString* pStoreToFilterOptions,
812 const uno::Sequence< beans::PropertyValue >* pSaveToFilterData,
813 const bool bIsPDFexport,
814 SfxObjectShell* xObjectShell,
815 SwWrtShell& rWorkShell,
816 OUString * const decodedURL = nullptr )
818 OUString url = pFileURL->GetMainURL( INetURLObject::DecodeMechanism::NONE );
819 if( decodedURL )
820 (*decodedURL) = url;
822 SfxMedium* pDstMed = new SfxMedium( url, StreamMode::STD_READWRITE );
823 pDstMed->SetFilter( pStoreToFilter );
824 if( pStoreToFilterOptions )
825 pDstMed->GetItemSet().Put( SfxStringItem(SID_FILE_FILTEROPTIONS,
826 *pStoreToFilterOptions));
827 if( pSaveToFilterData->hasElements() )
828 pDstMed->GetItemSet().Put( SfxUnoAnyItem(SID_FILTER_DATA,
829 uno::Any(*pSaveToFilterData)));
831 // convert fields to text if we are exporting to PDF.
832 // this prevents a second merge while updating the fields
833 // in SwXTextDocument::getRendererCount()
834 if( bIsPDFexport )
835 rWorkShell.ConvertFieldsToText();
837 bool bAnyError = !xObjectShell->DoSaveAs(*pDstMed);
838 // Actually this should be a bool... so in case of email and individual
839 // files, where this is set, we skip the recently used handling
840 bAnyError |= !xObjectShell->DoSaveCompleted( pDstMed, !decodedURL );
841 bAnyError |= (ERRCODE_NONE != xObjectShell->GetErrorIgnoreWarning());
842 if( bAnyError )
844 // error message ??
845 ErrorHandler::HandleError( xObjectShell->GetErrorIgnoreWarning() );
847 return !bAnyError;
850 static void lcl_PreparePrinterOptions(
851 const uno::Sequence< beans::PropertyValue >& rInPrintOptions,
852 uno::Sequence< beans::PropertyValue >& rOutPrintOptions)
854 // printing should be done synchronously otherwise the document
855 // might already become invalid during the process
857 rOutPrintOptions = { comphelper::makePropertyValue(u"Wait"_ustr, true) };
859 // copy print options
860 sal_Int32 nIndex = 1;
861 for( const beans::PropertyValue& rOption : rInPrintOptions)
863 if( rOption.Name == "CopyCount" || rOption.Name == "FileName"
864 || rOption.Name == "Collate" || rOption.Name == "Pages"
865 || rOption.Name == "Wait" || rOption.Name == "PrinterName" )
867 // add an option
868 rOutPrintOptions.realloc( nIndex + 1 );
869 auto pOutPrintOptions = rOutPrintOptions.getArray();
870 pOutPrintOptions[ nIndex ].Name = rOption.Name;
871 pOutPrintOptions[ nIndex++ ].Value = rOption.Value ;
876 static void lcl_PrepareSaveFilterDataOptions(
877 const uno::Sequence< beans::PropertyValue >& rInSaveFilterDataptions,
878 uno::Sequence< beans::PropertyValue >& rOutSaveFilterDataOptions,
879 const OUString& sPassword)
881 rOutSaveFilterDataOptions
882 = { comphelper::makePropertyValue(u"EncryptFile"_ustr, true),
883 comphelper::makePropertyValue(u"DocumentOpenPassword"_ustr, sPassword) };
885 // copy other options
886 sal_Int32 nIndex = 2;
887 for( const beans::PropertyValue& rOption : rInSaveFilterDataptions)
889 rOutSaveFilterDataOptions.realloc( nIndex + 1 );
890 auto pOutSaveFilterDataOptions = rOutSaveFilterDataOptions.getArray();
891 pOutSaveFilterDataOptions[ nIndex ].Name = rOption.Name;
892 pOutSaveFilterDataOptions[ nIndex++ ].Value = rOption.Value ;
898 static SfxObjectShell* lcl_CreateWorkingDocument(
899 // input
900 const WorkingDocType aType, const SwWrtShell &rSourceWrtShell,
901 // optional input
902 const vcl::Window *pSourceWindow,
903 // optional in and output to swap the DB manager
904 SwDBManager** const ppDBManager,
905 // optional output
906 SwView** const pView, SwWrtShell** const pWrtShell, rtl::Reference<SwDoc>* const pDoc )
908 const SwDoc *pSourceDoc = rSourceWrtShell.GetDoc();
909 SfxObjectShellRef xWorkObjectShell = pSourceDoc->CreateCopy( true, (aType == WorkingDocType::TARGET) );
910 SfxViewFrame* pWorkFrame = SfxViewFrame::LoadHiddenDocument( *xWorkObjectShell, SFX_INTERFACE_NONE );
912 if( pSourceWindow )
914 // the created window has to be located at the same position as the source window
915 vcl::Window& rTargetWindow = pWorkFrame->GetFrame().GetWindow();
916 rTargetWindow.SetPosPixel( pSourceWindow->GetPosPixel() );
919 SwView* pWorkView = static_cast< SwView* >( pWorkFrame->GetViewShell() );
921 if (SwWrtShell* pWorkWrtShell = pWorkView->GetWrtShellPtr())
923 pWorkWrtShell->GetViewOptions()->SetIdle( false );
924 pWorkView->AttrChangedNotify(nullptr);// in order for SelectShell to be called
925 SwDoc* pWorkDoc = pWorkWrtShell->GetDoc();
926 pWorkDoc->GetIDocumentUndoRedo().DoUndo( false );
927 pWorkDoc->ReplaceDocumentProperties( *pSourceDoc );
929 // import print settings
930 const SwPrintData &rPrintData = pSourceDoc->getIDocumentDeviceAccess().getPrintData();
931 pWorkDoc->getIDocumentDeviceAccess().setPrintData(rPrintData);
932 const JobSetup *pJobSetup = pSourceDoc->getIDocumentDeviceAccess().getJobsetup();
933 if (pJobSetup)
934 pWorkDoc->getIDocumentDeviceAccess().setJobsetup(*pJobSetup);
936 if( aType == WorkingDocType::TARGET )
938 assert( !ppDBManager );
939 pWorkDoc->SetInMailMerge( true );
940 pWorkWrtShell->SetLabelDoc( false );
942 else
944 // We have to swap the DBmanager of the new doc, so we also need input
945 assert(ppDBManager && *ppDBManager);
946 SwDBManager *pWorkDBManager = pWorkDoc->GetDBManager();
947 pWorkDoc->SetDBManager( *ppDBManager );
948 *ppDBManager = pWorkDBManager;
950 if( aType == WorkingDocType::SOURCE )
952 // the GetDBData call constructs the data, if it's missing - kind of const...
953 pWorkWrtShell->ChgDBData( const_cast<SwDoc*>(pSourceDoc)->GetDBData() );
954 // some DocumentSettings are currently not copied by SwDoc::CreateCopy
955 pWorkWrtShell->SetLabelDoc( rSourceWrtShell.IsLabelDoc() );
956 pWorkDoc->getIDocumentState().ResetModified();
958 else
959 pWorkDoc->getIDocumentLinksAdministration().EmbedAllLinks();
962 if( pView ) *pView = pWorkView;
963 if( pWrtShell ) *pWrtShell = pWorkWrtShell;
964 if( pDoc ) *pDoc = pWorkDoc;
967 return xWorkObjectShell.get();
970 static rtl::Reference<SwMailMessage> lcl_CreateMailFromDoc(
971 const SwMergeDescriptor &rMergeDescriptor,
972 const OUString &sFileURL, const OUString &sMailRecipient,
973 const OUString &sMailBodyMimeType, rtl_TextEncoding sMailEncoding,
974 const OUString &sAttachmentMimeType )
976 rtl::Reference<SwMailMessage> pMessage = new SwMailMessage;
977 if( rMergeDescriptor.pMailMergeConfigItem->IsMailReplyTo() )
978 pMessage->setReplyToAddress(rMergeDescriptor.pMailMergeConfigItem->GetMailReplyTo());
979 pMessage->addRecipient( sMailRecipient );
980 pMessage->SetSenderAddress( rMergeDescriptor.pMailMergeConfigItem->GetMailAddress() );
982 OUStringBuffer sBody;
983 if( rMergeDescriptor.bSendAsAttachment )
985 sBody = rMergeDescriptor.sMailBody;
986 mail::MailAttachment aAttach;
987 aAttach.Data = new SwMailTransferable( sFileURL,
988 rMergeDescriptor.sAttachmentName, sAttachmentMimeType );
989 aAttach.ReadableName = rMergeDescriptor.sAttachmentName;
990 pMessage->addAttachment( aAttach );
992 else
994 //read in the temporary file and use it as mail body
995 SfxMedium aMedium( sFileURL, StreamMode::READ );
996 SvStream* pInStream = aMedium.GetInStream();
997 assert( pInStream && "no output file created?" );
998 if( !pInStream )
999 return pMessage;
1001 pInStream->SetStreamCharSet( sMailEncoding );
1002 OStringBuffer sLine;
1003 while ( pInStream->ReadLine( sLine ) )
1005 sBody.append(OStringToOUString( sLine, sMailEncoding ) + "\n");
1008 pMessage->setSubject( rMergeDescriptor.sSubject );
1009 uno::Reference< datatransfer::XTransferable> xBody =
1010 new SwMailTransferable( sBody.makeStringAndClear(), sMailBodyMimeType );
1011 pMessage->setBody( xBody );
1013 for( const OUString& sCcRecipient : rMergeDescriptor.aCopiesTo )
1014 pMessage->addCcRecipient( sCcRecipient );
1015 for( const OUString& sBccRecipient : rMergeDescriptor.aBlindCopiesTo )
1016 pMessage->addBccRecipient( sBccRecipient );
1018 return pMessage;
1021 class SwDBManager::MailDispatcherListener_Impl : public IMailDispatcherListener
1023 SwDBManager &m_rDBManager;
1025 public:
1026 explicit MailDispatcherListener_Impl( SwDBManager &rDBManager )
1027 : m_rDBManager( rDBManager ) {}
1029 virtual void idle() override {}
1031 virtual void mailDelivered( uno::Reference< mail::XMailMessage> xMessage ) override
1033 std::unique_lock aGuard( m_rDBManager.m_pImpl->m_aAllEmailSendMutex );
1034 if ( m_rDBManager.m_pImpl->m_xLastMessage == xMessage )
1035 m_rDBManager.m_pImpl->m_xLastMessage.clear();
1038 virtual void mailDeliveryError( ::rtl::Reference<MailDispatcher> xMailDispatcher,
1039 uno::Reference< mail::XMailMessage>, const OUString& ) override
1041 std::unique_lock aGuard( m_rDBManager.m_pImpl->m_aAllEmailSendMutex );
1042 m_rDBManager.m_aMergeStatus = MergeStatus::Error;
1043 m_rDBManager.m_pImpl->m_xLastMessage.clear();
1044 xMailDispatcher->stop();
1049 * Please have a look at the README in the same directory, before you make
1050 * larger changes in this function!
1052 bool SwDBManager::MergeMailFiles(SwWrtShell* pSourceShell,
1053 const SwMergeDescriptor& rMergeDescriptor)
1055 // deconstruct mail merge type for better readability.
1056 // uppercase naming is intentional!
1057 const bool bMT_EMAIL = rMergeDescriptor.nMergeType == DBMGR_MERGE_EMAIL;
1058 const bool bMT_SHELL = rMergeDescriptor.nMergeType == DBMGR_MERGE_SHELL;
1059 const bool bMT_PRINTER = rMergeDescriptor.nMergeType == DBMGR_MERGE_PRINTER;
1060 const bool bMT_FILE = rMergeDescriptor.nMergeType == DBMGR_MERGE_FILE;
1062 //check if the doc is synchronized and contains at least one linked section
1063 const bool bSynchronizedDoc = pSourceShell->IsLabelDoc() && pSourceShell->GetSectionFormatCount() > 1;
1064 const bool bNeedsTempFiles = ( bMT_EMAIL || bMT_FILE );
1065 const bool bIsMergeSilent = IsMergeSilent();
1067 bool bCheckSingleFile_ = rMergeDescriptor.bCreateSingleFile;
1068 OUString sPrefix_ = rMergeDescriptor.sPrefix;
1069 if( bMT_EMAIL )
1071 assert( !rMergeDescriptor.bPrefixIsFilename );
1072 assert(!bCheckSingleFile_);
1073 bCheckSingleFile_ = false;
1075 else if( bMT_SHELL || bMT_PRINTER )
1077 assert(bCheckSingleFile_);
1078 bCheckSingleFile_ = true;
1079 assert(sPrefix_.isEmpty());
1080 sPrefix_.clear();
1082 const bool bCreateSingleFile = bCheckSingleFile_;
1083 const OUString sDescriptorPrefix = sPrefix_;
1085 // Setup for dumping debugging documents
1086 static const sal_Int32 nMaxDumpDocs = []() {
1087 if (const char* sEnv = getenv("SW_DEBUG_MAILMERGE_DOCS"))
1088 return OUString(sEnv, strlen(sEnv), osl_getThreadTextEncoding()).toInt32();
1089 else
1090 return sal_Int32(0);
1091 }();
1093 ::rtl::Reference< MailDispatcher > xMailDispatcher;
1094 ::rtl::Reference< IMailDispatcherListener > xMailListener;
1095 OUString sMailBodyMimeType;
1096 rtl_TextEncoding sMailEncoding = ::osl_getThreadTextEncoding();
1098 uno::Reference< beans::XPropertySet > xColumnProp;
1099 uno::Reference< beans::XPropertySet > xPasswordColumnProp;
1101 // Check for (mandatory) email or (optional) filename column
1102 SwDBFormatData aColumnDBFormat;
1103 bool bColumnName = !rMergeDescriptor.sDBcolumn.isEmpty();
1104 bool bPasswordColumnName = !rMergeDescriptor.sDBPasswordColumn.isEmpty();
1106 if( ! bColumnName )
1108 if( bMT_EMAIL )
1109 return false;
1111 else
1113 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY );
1114 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
1115 if( !xCols->hasByName( rMergeDescriptor.sDBcolumn ) )
1116 return false;
1117 uno::Any aCol = xCols->getByName( rMergeDescriptor.sDBcolumn );
1118 aCol >>= xColumnProp;
1120 if(bPasswordColumnName)
1122 aCol = xCols->getByName( rMergeDescriptor.sDBPasswordColumn );
1123 aCol >>= xPasswordColumnProp;
1126 aColumnDBFormat.xFormatter = m_pImpl->pMergeData->xFormatter;
1127 aColumnDBFormat.aNullDate = m_pImpl->pMergeData->aNullDate;
1129 if( bMT_EMAIL )
1131 // Reset internal mail accounting data
1133 std::unique_lock aGuard(m_pImpl->m_aAllEmailSendMutex);
1134 m_pImpl->m_xLastMessage.clear();
1137 xMailDispatcher.set( new MailDispatcher(rMergeDescriptor.xSmtpServer) );
1138 xMailListener = new MailDispatcherListener_Impl( *this );
1139 xMailDispatcher->addListener( xMailListener );
1140 if(!rMergeDescriptor.bSendAsAttachment && rMergeDescriptor.bSendAsHTML)
1142 sMailBodyMimeType = "text/html; charset=utf-8";
1143 sMailEncoding = RTL_TEXTENCODING_UTF8;
1145 else
1146 sMailBodyMimeType = "text/plain; charset=UTF-8; format=flowed";
1150 SwDocShell *pSourceDocSh = pSourceShell->GetView().GetDocShell();
1152 // setup the output format
1153 std::shared_ptr<const SfxFilter> pStoreToFilter = SwIoSystem::GetFileFilter(
1154 pSourceDocSh->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE));
1155 SfxFilterContainer* pFilterContainer = SwDocShell::Factory().GetFilterContainer();
1156 const OUString* pStoreToFilterOptions = nullptr;
1158 // if a save_to filter is set then use it - otherwise use the default
1159 if( bMT_EMAIL && !rMergeDescriptor.bSendAsAttachment )
1161 OUString sExtension = rMergeDescriptor.bSendAsHTML ? u"html"_ustr : u"txt"_ustr;
1162 pStoreToFilter = pFilterContainer->GetFilter4Extension(sExtension, SfxFilterFlags::EXPORT);
1164 else if( !rMergeDescriptor.sSaveToFilter.isEmpty())
1166 std::shared_ptr<const SfxFilter> pFilter =
1167 pFilterContainer->GetFilter4FilterName( rMergeDescriptor.sSaveToFilter );
1168 if(pFilter)
1170 pStoreToFilter = std::move(pFilter);
1171 if(!rMergeDescriptor.sSaveToFilterOptions.isEmpty())
1172 pStoreToFilterOptions = &rMergeDescriptor.sSaveToFilterOptions;
1175 const bool bIsPDFexport = pStoreToFilter && pStoreToFilter->GetFilterName() == "writer_pdf_Export";
1176 const bool bIsMultiFile = bMT_FILE && !bCreateSingleFile;
1178 m_aMergeStatus = MergeStatus::Ok;
1180 // in case of creating a single resulting file this has to be created here
1181 SwView* pTargetView = rMergeDescriptor.pMailMergeConfigItem ?
1182 rMergeDescriptor.pMailMergeConfigItem->GetTargetView() : nullptr;
1183 SwWrtShell* pTargetShell = nullptr;
1184 SfxObjectShellRef xTargetDocShell;
1185 rtl::Reference<SwDoc> pTargetDoc;
1187 std::unique_ptr< utl::TempFileNamed > aTempFile;
1188 sal_uInt16 nStartingPageNo = 0;
1190 std::shared_ptr<weld::GenericDialogController> xProgressDlg;
1194 vcl::Window *pSourceWindow = nullptr;
1195 if( !bIsMergeSilent )
1197 // construct the process dialog
1198 pSourceWindow = &pSourceShell->GetView().GetEditWin();
1199 if (!bMT_PRINTER)
1200 xProgressDlg = std::make_shared<CreateMonitor>(pSourceWindow->GetFrameWeld());
1201 else
1203 xProgressDlg = std::make_shared<PrintMonitor>(pSourceWindow->GetFrameWeld());
1204 static_cast<PrintMonitor*>(xProgressDlg.get())->set_title(
1205 pSourceDocSh->GetTitle(22));
1207 weld::DialogController::runAsync(xProgressDlg, [this, &xProgressDlg](sal_Int32 nResult){
1208 if (nResult == RET_CANCEL)
1209 MergeCancel();
1210 xProgressDlg.reset();
1213 Application::Reschedule( true );
1216 if( bCreateSingleFile && !pTargetView )
1218 // create a target docshell to put the merged document into
1219 xTargetDocShell = lcl_CreateWorkingDocument( WorkingDocType::TARGET,
1220 *pSourceShell, bMT_SHELL ? pSourceWindow : nullptr,
1221 nullptr, &pTargetView, &pTargetShell, &pTargetDoc );
1223 if (nMaxDumpDocs)
1224 lcl_SaveDebugDoc( xTargetDocShell.get(), "MergeDoc" );
1226 else if( pTargetView )
1228 pTargetShell = pTargetView->GetWrtShellPtr();
1229 if (pTargetShell)
1231 pTargetDoc = pTargetShell->GetDoc();
1232 xTargetDocShell = pTargetView->GetDocShell();
1236 if( bCreateSingleFile )
1238 // determine the page style and number used at the start of the source document
1239 pSourceShell->SttEndDoc(true);
1240 nStartingPageNo = pSourceShell->GetVirtPageNum();
1243 // Progress, to prohibit KeyInputs
1244 SfxProgress aProgress(pSourceDocSh, OUString(), 1);
1246 // lock all dispatchers
1247 SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst(pSourceDocSh);
1248 while (pViewFrame)
1250 pViewFrame->GetDispatcher()->Lock(true);
1251 pViewFrame = SfxViewFrame::GetNext(*pViewFrame, pSourceDocSh);
1254 sal_Int32 nDocNo = 1;
1256 // For single file mode, the number of pages in the target document so far, which is used
1257 // by AppendDoc() to adjust position of page-bound objects. Getting this information directly
1258 // from the target doc would require repeated layouts of the doc, which is expensive, but
1259 // it can be manually computed from the source documents (for which we do layouts, so the page
1260 // count is known, and there is a blank page between each of them in the target document).
1261 int targetDocPageCount = 0;
1263 if( !bIsMergeSilent && !bMT_PRINTER )
1265 sal_Int32 nRecordCount = 1;
1266 lcl_getCountFromResultSet( nRecordCount, m_pImpl->pMergeData.get() );
1268 // Synchronized docs don't auto-advance the record set, but there is a
1269 // "security" check, which will always advance the record set, if there
1270 // is no "next record" field in a synchronized doc => nRecordPerDoc > 0
1271 sal_Int32 nRecordPerDoc = pSourceShell->GetDoc()
1272 ->getIDocumentFieldsAccess().GetRecordsPerDocument();
1273 if ( bSynchronizedDoc && (nRecordPerDoc > 1) )
1274 --nRecordPerDoc;
1275 assert( nRecordPerDoc > 0 );
1277 sal_Int32 nMaxDocs = nRecordCount / nRecordPerDoc;
1278 if ( 0 != nRecordCount % nRecordPerDoc )
1279 nMaxDocs += 1;
1280 static_cast<CreateMonitor*>(xProgressDlg.get())->SetTotalCount(nMaxDocs);
1283 sal_Int32 nStartRow, nEndRow;
1284 bool bFreezedLayouts = false;
1285 // to collect temporary email files
1286 std::vector< OUString> aFilesToRemove;
1288 // The SfxObjectShell will be closed explicitly later but
1289 // it is more safe to use SfxObjectShellLock here
1290 SfxObjectShellLock xWorkDocSh;
1291 SwView* pWorkView = nullptr;
1292 rtl::Reference<SwDoc> pWorkDoc;
1293 SwDBManager* pWorkDocOrigDBManager = nullptr;
1294 SwWrtShell* pWorkShell = nullptr;
1295 bool bWorkDocInitialized = false;
1299 nStartRow = m_pImpl->pMergeData ? m_pImpl->pMergeData->xResultSet->getRow() : 0;
1301 OUString sColumnData;
1303 // Read the indicated data column, which should contain a valid mail
1304 // address or an optional file name
1305 if( bMT_EMAIL || bColumnName )
1307 sColumnData = GetDBField( xColumnProp, aColumnDBFormat );
1310 // create a new temporary file name - only done once in case of bCreateSingleFile
1311 if( bNeedsTempFiles && ( !bWorkDocInitialized || !bCreateSingleFile ))
1313 OUString sPrefix = sDescriptorPrefix;
1314 OUString sLeading;
1316 //#i97667# if the name is from a database field then it will be used _as is_
1317 if( bColumnName && !bMT_EMAIL )
1319 if (!sColumnData.isEmpty())
1320 sLeading = sColumnData;
1321 else
1322 sLeading = "_";
1324 else
1326 INetURLObject aEntry( sPrefix );
1327 sLeading = aEntry.GetBase();
1328 aEntry.removeSegment();
1329 sPrefix = aEntry.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1332 OUString sExt(comphelper::string::stripStart(pStoreToFilter->GetDefaultExtension(), '*'));
1333 aTempFile.reset( new utl::TempFileNamed(sLeading, sColumnData.isEmpty(), sExt, &sPrefix, true) );
1334 if( !aTempFile->IsValid() )
1336 ErrorHandler::HandleError( ERRCODE_IO_NOTSUPPORTED );
1337 m_aMergeStatus = MergeStatus::Error;
1341 uno::Sequence< beans::PropertyValue > aSaveToFilterDataOptions( rMergeDescriptor.aSaveToFilterData );
1343 if( bMT_EMAIL || bPasswordColumnName )
1345 OUString sPasswordColumnData = GetDBField( xPasswordColumnProp, aColumnDBFormat );
1346 lcl_PrepareSaveFilterDataOptions( rMergeDescriptor.aSaveToFilterData, aSaveToFilterDataOptions, sPasswordColumnData );
1349 if( IsMergeOk() )
1351 std::unique_ptr< INetURLObject > aTempFileURL;
1352 if( bNeedsTempFiles )
1353 aTempFileURL.reset( new INetURLObject(aTempFile->GetURL()));
1354 if( !bIsMergeSilent ) {
1355 if( !bMT_PRINTER )
1356 static_cast<CreateMonitor*>(xProgressDlg.get())->SetCurrentPosition(nDocNo);
1357 else {
1358 PrintMonitor *pPrintMonDlg = static_cast<PrintMonitor*>(xProgressDlg.get());
1359 pPrintMonDlg->m_xPrinter->set_label(bNeedsTempFiles
1360 ? aTempFileURL->GetBase() : pSourceDocSh->GetTitle( 2));
1361 OUString sStat = SwResId(STR_STATSTR_LETTER) + " " + OUString::number( nDocNo );
1362 pPrintMonDlg->m_xPrintInfo->set_label(sStat);
1364 //TODO xProgressDlg->queue_draw();
1367 Scheduler::ProcessEventsToIdle();
1369 // Create a copy of the source document and work with that one instead of the source.
1370 // If we're not in the single file mode (which requires modifying the document for the merging),
1371 // it is enough to do this just once. Currently PDF also has to be treated special.
1372 if( !bWorkDocInitialized || bCreateSingleFile || bIsPDFexport || bIsMultiFile )
1374 assert( !xWorkDocSh.Is());
1375 pWorkDocOrigDBManager = this;
1376 xWorkDocSh = lcl_CreateWorkingDocument( WorkingDocType::COPY,
1377 *pSourceShell, nullptr, &pWorkDocOrigDBManager,
1378 &pWorkView, &pWorkShell, &pWorkDoc );
1379 if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) )
1380 lcl_SaveDebugDoc( xWorkDocSh, "WorkDoc", nDocNo );
1382 // #i69458# lock fields to prevent access to the result set while calculating layout
1383 // tdf#92324: and do not unlock: keep document locked during printing to avoid
1384 // ExpFields update during printing, generation of preview, etc.
1385 pWorkShell->LockExpFields();
1386 pWorkShell->CalcLayout();
1387 // tdf#121168: Now force correct page descriptions applied to page frames. Without
1388 // this, e.g., page frames starting with sections could have page descriptions set
1389 // wrong. This would lead to wrong page styles applied in SwDoc::AppendDoc below.
1390 pWorkShell->GetViewOptions()->SetIdle(true);
1391 for (auto aLayout : pWorkShell->GetDoc()->GetAllLayouts())
1393 aLayout->FreezeLayout(false);
1394 aLayout->AllCheckPageDescs();
1398 lcl_emitEvent(SfxEventHintId::SwEventFieldMerge, STR_SW_EVENT_FIELD_MERGE, xWorkDocSh);
1400 // tdf#92324: Allow ExpFields update only by explicit instruction to avoid
1401 // database cursor movement on any other fields update, for example during
1402 // print preview and other operations
1403 if ( pWorkShell->IsExpFieldsLocked() )
1404 pWorkShell->UnlockExpFields();
1405 pWorkShell->SwViewShell::UpdateFields();
1406 pWorkShell->LockExpFields();
1408 lcl_emitEvent(SfxEventHintId::SwEventFieldMergeFinished, STR_SW_EVENT_FIELD_MERGE_FINISHED, xWorkDocSh);
1410 // also emit MailMergeEvent on XInterface if possible
1411 const SwXMailMerge *pEvtSrc = GetMailMergeEvtSrc();
1412 if(pEvtSrc)
1414 rtl::Reference< SwXMailMerge > xRef(
1415 const_cast<SwXMailMerge*>(pEvtSrc) );
1416 text::MailMergeEvent aEvt( static_cast<text::XMailMergeBroadcaster*>(xRef.get()), xWorkDocSh->GetModel() );
1417 SolarMutexReleaser rel;
1418 xRef->LaunchMailMergeEvent( aEvt );
1421 // working copy is merged - prepare final steps depending on merge options
1423 if( bCreateSingleFile )
1425 assert( pTargetShell && "no target shell available!" );
1427 // prepare working copy and target to append
1429 pWorkDoc->RemoveInvisibleContent();
1430 // remove of invisible content has influence on page count and so on fields for page count,
1431 // therefore layout has to be updated before fields are converted to text
1432 pWorkShell->CalcLayout();
1433 pWorkShell->ConvertFieldsToText();
1434 pWorkShell->SetNumberingRestart();
1435 if( bSynchronizedDoc )
1437 lcl_RemoveSectionLinks( *pWorkShell );
1440 if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) )
1441 lcl_SaveDebugDoc( xWorkDocSh, "WorkDoc", nDocNo );
1443 // append the working document to the target document
1444 if( targetDocPageCount % 2 == 1 )
1445 ++targetDocPageCount; // Docs always start on odd pages (so offset must be even).
1446 SwNodeIndex appendedDocStart = pTargetDoc->AppendDoc( *pWorkDoc,
1447 nStartingPageNo, !bWorkDocInitialized, targetDocPageCount, nDocNo);
1448 targetDocPageCount += pWorkShell->GetPageCnt();
1450 if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) )
1451 lcl_SaveDebugDoc( xTargetDocShell.get(), "MergeDoc" );
1453 if (bMT_SHELL)
1455 SwDocMergeInfo aMergeInfo;
1456 // Name of the mark is actually irrelevant, UNO bookmarks have internals names.
1457 aMergeInfo.startPageInTarget = pTargetDoc->getIDocumentMarkAccess()->makeMark(
1458 SwPaM(appendedDocStart), u""_ustr, IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
1459 ::sw::mark::InsertMode::New);
1460 aMergeInfo.nDBRow = nStartRow;
1461 rMergeDescriptor.pMailMergeConfigItem->AddMergedDocument( aMergeInfo );
1464 else
1466 assert( bNeedsTempFiles );
1467 assert( pWorkShell->IsExpFieldsLocked() );
1469 if (bIsMultiFile && pWorkDoc->HasInvisibleContent())
1471 pWorkDoc->RemoveInvisibleContent();
1472 pWorkShell->CalcLayout();
1473 pWorkShell->ConvertFieldsToText();
1476 // fields are locked, so it's fine to
1477 // restore the old / empty DB manager for save
1478 pWorkDoc->SetDBManager( pWorkDocOrigDBManager );
1480 // save merged document
1481 OUString sFileURL;
1482 if( !lcl_SaveDoc( aTempFileURL.get(), pStoreToFilter, pStoreToFilterOptions,
1483 &aSaveToFilterDataOptions, bIsPDFexport,
1484 xWorkDocSh, *pWorkShell, &sFileURL ) )
1486 m_aMergeStatus = MergeStatus::Error;
1489 // back to the MM DB manager
1490 pWorkDoc->SetDBManager( this );
1492 if( bMT_EMAIL && !IsMergeError() )
1494 // schedule file for later removal
1495 aFilesToRemove.push_back( sFileURL );
1497 if( !SwMailMergeHelper::CheckMailAddress( sColumnData ) )
1499 OSL_FAIL("invalid e-Mail address in database column");
1501 else
1503 uno::Reference< mail::XMailMessage > xMessage = lcl_CreateMailFromDoc(
1504 rMergeDescriptor, sFileURL, sColumnData, sMailBodyMimeType,
1505 sMailEncoding, pStoreToFilter->GetMimeType() );
1506 if( xMessage.is() )
1508 std::unique_lock aGuard( m_pImpl->m_aAllEmailSendMutex );
1509 m_pImpl->m_xLastMessage.set( xMessage );
1510 xMailDispatcher->enqueueMailMessage( xMessage );
1511 if( !xMailDispatcher->isStarted() )
1512 xMailDispatcher->start();
1517 if( bCreateSingleFile || bIsPDFexport || bIsMultiFile)
1519 pWorkDoc->SetDBManager( pWorkDocOrigDBManager );
1520 pWorkDoc.clear();
1521 xWorkDocSh->DoClose();
1522 xWorkDocSh = nullptr;
1526 bWorkDocInitialized = true;
1527 nDocNo++;
1528 nEndRow = m_pImpl->pMergeData ? m_pImpl->pMergeData->xResultSet->getRow() : 0;
1530 // Freeze the layouts of the target document after the first inserted
1531 // sub-document, to get the correct PageDesc.
1532 if(!bFreezedLayouts && bCreateSingleFile)
1534 for ( auto aLayout : pTargetShell->GetDoc()->GetAllLayouts() )
1535 aLayout->FreezeLayout(true);
1536 bFreezedLayouts = true;
1538 } while( IsMergeOk() &&
1539 ((bSynchronizedDoc && (nStartRow != nEndRow)) ? IsValidMergeRecord() : ToNextMergeRecord()));
1541 if ( xWorkDocSh.Is() && pWorkView->GetWrtShell().IsExpFieldsLocked() )
1543 // Unlock document fields after merge complete
1544 pWorkView->GetWrtShell().UnlockExpFields();
1547 if( !bCreateSingleFile )
1549 if( bMT_PRINTER )
1550 Printer::FinishPrintJob( pWorkView->GetPrinterController());
1551 if( !bIsPDFexport )
1553 if (pWorkDoc)
1554 pWorkDoc->SetDBManager(pWorkDocOrigDBManager);
1555 if (xWorkDocSh.Is())
1556 xWorkDocSh->DoClose();
1559 else if( IsMergeOk() ) // && bCreateSingleFile
1561 Application::Reschedule( true );
1563 // sw::DocumentLayoutManager::CopyLayoutFormat() did not generate
1564 // unique fly names, do it here once.
1565 pTargetDoc->SetInMailMerge(false);
1566 pTargetDoc->SetAllUniqueFlyNames();
1568 // Unfreeze target document layouts and correct all PageDescs.
1569 SAL_INFO( "sw.pageframe", "(MergeMailFiles pTargetShell->CalcLayout in" );
1570 pTargetShell->CalcLayout();
1571 SAL_INFO( "sw.pageframe", "MergeMailFiles pTargetShell->CalcLayout out)" );
1572 pTargetShell->GetViewOptions()->SetIdle( true );
1573 pTargetDoc->GetIDocumentUndoRedo().DoUndo( true );
1574 for ( auto aLayout : pTargetShell->GetDoc()->GetAllLayouts() )
1576 aLayout->FreezeLayout(false);
1577 aLayout->AllCheckPageDescs();
1580 Application::Reschedule( true );
1582 if( IsMergeOk() && bMT_FILE )
1584 // save merged document
1585 assert( aTempFile );
1586 INetURLObject aTempFileURL;
1587 if (sDescriptorPrefix.isEmpty() || !rMergeDescriptor.bPrefixIsFilename)
1588 aTempFileURL.SetURL( aTempFile->GetURL() );
1589 else
1591 aTempFileURL.SetURL(sDescriptorPrefix);
1592 // remove the unneeded temporary file
1593 aTempFile->EnableKillingFile();
1595 if( !lcl_SaveDoc( &aTempFileURL, pStoreToFilter,
1596 pStoreToFilterOptions, &rMergeDescriptor.aSaveToFilterData,
1597 bIsPDFexport, xTargetDocShell.get(), *pTargetShell ) )
1599 m_aMergeStatus = MergeStatus::Error;
1602 else if( IsMergeOk() && bMT_PRINTER )
1604 // print the target document
1605 uno::Sequence< beans::PropertyValue > aOptions( rMergeDescriptor.aPrintOptions );
1606 lcl_PreparePrinterOptions( rMergeDescriptor.aPrintOptions, aOptions );
1607 pTargetView->ExecPrint( aOptions, bIsMergeSilent, false/*bPrintAsync*/ );
1611 // we also show canceled documents, as long as there was no error
1612 if( !IsMergeError() && bMT_SHELL )
1613 // leave docshell available for caller (e.g. MM wizard)
1614 rMergeDescriptor.pMailMergeConfigItem->SetTargetView( pTargetView );
1615 else if( xTargetDocShell.is() )
1616 xTargetDocShell->DoClose();
1618 Application::Reschedule( true );
1620 if (xProgressDlg)
1622 xProgressDlg->response(RET_OK);
1625 // unlock all dispatchers
1626 pViewFrame = SfxViewFrame::GetFirst(pSourceDocSh);
1627 while (pViewFrame)
1629 pViewFrame->GetDispatcher()->Lock(false);
1630 pViewFrame = SfxViewFrame::GetNext(*pViewFrame, pSourceDocSh);
1633 SwModule::get()->SetView(&pSourceShell->GetView());
1635 if( xMailDispatcher.is() )
1637 if( IsMergeOk() )
1639 // TODO: Instead of polling via an AutoTimer, post an Idle event,
1640 // if the main loop has been made thread-safe.
1641 AutoTimer aEmailDispatcherPollTimer("sw::SwDBManager aEmailDispatcherPollTimer");
1642 aEmailDispatcherPollTimer.SetTimeout( 500 );
1643 aEmailDispatcherPollTimer.Start();
1644 while( IsMergeOk() && m_pImpl->m_xLastMessage.is() && !Application::IsQuit())
1645 Application::Yield();
1646 aEmailDispatcherPollTimer.Stop();
1648 xMailDispatcher->stop();
1649 xMailDispatcher->shutdown();
1652 // remove the temporary files
1653 // has to be done after xMailDispatcher is finished, as mails may be
1654 // delivered as message attachments!
1655 for( const OUString &sFileURL : aFilesToRemove )
1656 SWUnoHelper::UCB_DeleteFile( sFileURL );
1658 catch (const uno::Exception&)
1660 if (xProgressDlg)
1662 xProgressDlg->response(RET_CANCEL);
1666 return !IsMergeError();
1669 void SwDBManager::MergeCancel()
1671 if (m_aMergeStatus < MergeStatus::Cancel)
1672 m_aMergeStatus = MergeStatus::Cancel;
1675 // determine the column's Numberformat and transfer to the forwarded Formatter,
1676 // if applicable.
1677 sal_uInt32 SwDBManager::GetColumnFormat( const OUString& rDBName,
1678 const OUString& rTableName,
1679 const OUString& rColNm,
1680 SvNumberFormatter* pNFormatr,
1681 LanguageType nLanguage )
1683 sal_uInt32 nRet = 0;
1684 if(pNFormatr)
1686 uno::Reference< sdbc::XDataSource> xSource;
1687 uno::Reference< sdbc::XConnection> xConnection;
1688 bool bUseMergeData = false;
1689 uno::Reference< sdbcx::XColumnsSupplier> xColsSupp;
1690 bool bDisposeConnection = false;
1691 if(m_pImpl->pMergeData &&
1692 ((m_pImpl->pMergeData->sDataSource == rDBName && m_pImpl->pMergeData->sCommand == rTableName) ||
1693 (rDBName.isEmpty() && rTableName.isEmpty())))
1695 xConnection = m_pImpl->pMergeData->xConnection;
1696 xSource = SwDBManager::getDataSourceAsParent(xConnection,rDBName);
1697 bUseMergeData = true;
1698 xColsSupp.set(m_pImpl->pMergeData->xResultSet, css::uno::UNO_QUERY);
1700 if(!xConnection.is())
1702 SwDBData aData;
1703 aData.sDataSource = rDBName;
1704 aData.sCommand = rTableName;
1705 aData.nCommandType = -1;
1706 SwDSParam* pParam = FindDSData(aData, false);
1707 if(pParam && pParam->xConnection.is())
1709 xConnection = pParam->xConnection;
1710 xColsSupp.set(pParam->xResultSet, css::uno::UNO_QUERY);
1712 else
1714 xConnection = RegisterConnection( rDBName );
1715 bDisposeConnection = true;
1717 if(bUseMergeData)
1718 m_pImpl->pMergeData->xConnection = xConnection;
1720 bool bDispose = !xColsSupp.is();
1721 if(bDispose)
1723 xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName);
1725 if(xColsSupp.is())
1727 uno::Reference<container::XNameAccess> xCols;
1730 xCols = xColsSupp->getColumns();
1732 catch (const uno::Exception&)
1734 TOOLS_WARN_EXCEPTION("sw.mailmerge", "Exception in getColumns()");
1736 if(!xCols.is() || !xCols->hasByName(rColNm))
1737 return nRet;
1738 uno::Any aCol = xCols->getByName(rColNm);
1739 uno::Reference< beans::XPropertySet > xColumn;
1740 aCol >>= xColumn;
1741 nRet = GetColumnFormat(xSource, xConnection, xColumn, pNFormatr, nLanguage);
1742 if(bDispose)
1744 ::comphelper::disposeComponent( xColsSupp );
1746 if(bDisposeConnection)
1748 ::comphelper::disposeComponent( xConnection );
1751 else
1752 nRet = pNFormatr->GetFormatIndex( NF_NUMBER_STANDARD, LANGUAGE_SYSTEM );
1754 return nRet;
1757 sal_uInt32 SwDBManager::GetColumnFormat( uno::Reference< sdbc::XDataSource> const & xSource_in,
1758 uno::Reference< sdbc::XConnection> const & xConnection,
1759 uno::Reference< beans::XPropertySet> const & xColumn,
1760 SvNumberFormatter* pNFormatr,
1761 LanguageType nLanguage )
1763 auto xSource = xSource_in;
1765 // set the NumberFormat in the doc if applicable
1766 sal_uInt32 nRet = 0;
1768 if(!xSource.is())
1770 uno::Reference<container::XChild> xChild(xConnection, uno::UNO_QUERY);
1771 if ( xChild.is() )
1772 xSource.set(xChild->getParent(), uno::UNO_QUERY);
1774 if(xSource.is() && xConnection.is() && xColumn.is() && pNFormatr)
1776 rtl::Reference<SvNumberFormatsSupplierObj> pNumFormat = new SvNumberFormatsSupplierObj( pNFormatr );
1777 uno::Reference< util::XNumberFormats > xDocNumberFormats = pNumFormat->getNumberFormats();
1778 uno::Reference< util::XNumberFormatTypes > xDocNumberFormatTypes(xDocNumberFormats, uno::UNO_QUERY);
1780 css::lang::Locale aLocale( LanguageTag( nLanguage ).getLocale());
1782 //get the number formatter of the data source
1783 uno::Reference<beans::XPropertySet> xSourceProps(xSource, uno::UNO_QUERY);
1784 uno::Reference< util::XNumberFormats > xNumberFormats;
1785 if(xSourceProps.is())
1787 uno::Any aFormats = xSourceProps->getPropertyValue(u"NumberFormatsSupplier"_ustr);
1788 if(aFormats.hasValue())
1790 uno::Reference<util::XNumberFormatsSupplier> xSuppl;
1791 aFormats >>= xSuppl;
1792 if(xSuppl.is())
1794 xNumberFormats = xSuppl->getNumberFormats();
1798 bool bUseDefault = true;
1801 uno::Any aFormatKey = xColumn->getPropertyValue(u"FormatKey"_ustr);
1802 if(aFormatKey.hasValue())
1804 sal_Int32 nFormat = 0;
1805 aFormatKey >>= nFormat;
1806 if(xNumberFormats.is())
1810 uno::Reference<beans::XPropertySet> xNumProps = xNumberFormats->getByKey( nFormat );
1811 uno::Any aFormatString = xNumProps->getPropertyValue(u"FormatString"_ustr);
1812 uno::Any aLocaleVal = xNumProps->getPropertyValue(u"Locale"_ustr);
1813 OUString sFormat;
1814 aFormatString >>= sFormat;
1815 lang::Locale aLoc;
1816 aLocaleVal >>= aLoc;
1817 nFormat = xDocNumberFormats->queryKey( sFormat, aLoc, false );
1818 if(NUMBERFORMAT_ENTRY_NOT_FOUND == sal::static_int_cast< sal_uInt32, sal_Int32>(nFormat))
1819 nFormat = xDocNumberFormats->addNew( sFormat, aLoc );
1821 nRet = nFormat;
1822 bUseDefault = false;
1824 catch (const uno::Exception&)
1826 TOOLS_WARN_EXCEPTION("sw.mailmerge", "illegal number format key");
1831 catch(const uno::Exception&)
1833 SAL_WARN("sw.mailmerge", "no FormatKey property found");
1835 if(bUseDefault)
1836 nRet = dbtools::getDefaultNumberFormat(xColumn, xDocNumberFormatTypes, aLocale);
1838 return nRet;
1841 sal_Int32 SwDBManager::GetColumnType( const OUString& rDBName,
1842 const OUString& rTableName,
1843 const OUString& rColNm )
1845 sal_Int32 nRet = sdbc::DataType::SQLNULL;
1846 SwDBData aData;
1847 aData.sDataSource = rDBName;
1848 aData.sCommand = rTableName;
1849 aData.nCommandType = -1;
1850 SwDSParam* pParam = FindDSData(aData, false);
1851 uno::Reference< sdbc::XConnection> xConnection;
1852 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp;
1853 bool bDispose = false;
1854 if(pParam && pParam->xConnection.is())
1856 xConnection = pParam->xConnection;
1857 xColsSupp.set( pParam->xResultSet, uno::UNO_QUERY );
1859 else
1861 xConnection = RegisterConnection( rDBName );
1863 if( !xColsSupp.is() )
1865 xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName);
1866 bDispose = true;
1868 if(xColsSupp.is())
1870 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
1871 if(xCols->hasByName(rColNm))
1873 uno::Any aCol = xCols->getByName(rColNm);
1874 uno::Reference<beans::XPropertySet> xCol;
1875 aCol >>= xCol;
1876 uno::Any aType = xCol->getPropertyValue(u"Type"_ustr);
1877 aType >>= nRet;
1879 if(bDispose)
1880 ::comphelper::disposeComponent( xColsSupp );
1882 return nRet;
1885 uno::Reference< sdbc::XConnection> SwDBManager::GetConnection(const OUString& rDataSource,
1886 uno::Reference<sdbc::XDataSource>& rxSource, const SwView *pView)
1888 uno::Reference< sdbc::XConnection> xConnection;
1889 const uno::Reference< uno::XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
1892 uno::Reference<sdb::XCompletedConnection> xComplConnection(dbtools::getDataSource(rDataSource, xContext), uno::UNO_QUERY);
1893 if ( !xComplConnection )
1894 return xConnection;
1895 rxSource.set(xComplConnection, uno::UNO_QUERY);
1896 weld::Window* pWindow = pView ? pView->GetFrameWeld() : nullptr;
1897 uno::Reference< task::XInteractionHandler > xHandler( task::InteractionHandler::createWithParent(xContext, pWindow ? pWindow->GetXWindow() : nullptr) );
1898 if (!xHandler)
1899 return xConnection;
1900 xConnection = xComplConnection->connectWithCompletion( xHandler );
1902 catch(const uno::Exception&)
1906 return xConnection;
1909 uno::Reference< sdbcx::XColumnsSupplier> SwDBManager::GetColumnSupplier(uno::Reference<sdbc::XConnection> const & xConnection,
1910 const OUString& rTableOrQuery,
1911 SwDBSelect eTableOrQuery)
1913 uno::Reference< sdbcx::XColumnsSupplier> xRet;
1916 if(eTableOrQuery == SwDBSelect::UNKNOWN)
1918 //search for a table with the given command name
1919 uno::Reference<sdbcx::XTablesSupplier> xTSupplier(xConnection, uno::UNO_QUERY);
1920 if(xTSupplier.is())
1922 uno::Reference<container::XNameAccess> xTables = xTSupplier->getTables();
1923 eTableOrQuery = xTables->hasByName(rTableOrQuery) ?
1924 SwDBSelect::TABLE : SwDBSelect::QUERY;
1927 sal_Int32 nCommandType = SwDBSelect::TABLE == eTableOrQuery ?
1928 sdb::CommandType::TABLE : sdb::CommandType::QUERY;
1929 uno::Reference< lang::XMultiServiceFactory > xMgr( ::comphelper::getProcessServiceFactory() );
1930 uno::Reference<sdbc::XRowSet> xRowSet(xMgr->createInstance(u"com.sun.star.sdb.RowSet"_ustr), uno::UNO_QUERY);
1932 OUString sDataSource;
1933 uno::Reference<sdbc::XDataSource> xSource = SwDBManager::getDataSourceAsParent(xConnection, sDataSource);
1934 uno::Reference<beans::XPropertySet> xSourceProperties(xSource, uno::UNO_QUERY);
1935 if(xSourceProperties.is())
1937 xSourceProperties->getPropertyValue(u"Name"_ustr) >>= sDataSource;
1940 uno::Reference<beans::XPropertySet> xRowProperties(xRowSet, uno::UNO_QUERY);
1941 xRowProperties->setPropertyValue(u"DataSourceName"_ustr, uno::Any(sDataSource));
1942 xRowProperties->setPropertyValue(u"Command"_ustr, uno::Any(rTableOrQuery));
1943 xRowProperties->setPropertyValue(u"CommandType"_ustr, uno::Any(nCommandType));
1944 xRowProperties->setPropertyValue(u"FetchSize"_ustr, uno::Any(sal_Int32(10)));
1945 xRowProperties->setPropertyValue(u"ActiveConnection"_ustr, uno::Any(xConnection));
1946 xRowSet->execute();
1947 xRet.set( xRowSet, uno::UNO_QUERY );
1949 catch (const uno::Exception&)
1951 TOOLS_WARN_EXCEPTION("sw.mailmerge", "Exception in SwDBManager::GetColumnSupplier");
1954 return xRet;
1957 OUString SwDBManager::GetDBField(uno::Reference<beans::XPropertySet> const & xColumnProps,
1958 const SwDBFormatData& rDBFormatData,
1959 double* pNumber)
1961 uno::Reference< sdb::XColumn > xColumn(xColumnProps, uno::UNO_QUERY);
1962 OUString sRet;
1963 assert( xColumn.is() && "SwDBManager::::ImportDBField: illegal arguments" );
1964 if(!xColumn.is())
1965 return sRet;
1967 uno::Any aType = xColumnProps->getPropertyValue(u"Type"_ustr);
1968 sal_Int32 eDataType = sdbc::DataType::SQLNULL;
1969 aType >>= eDataType;
1970 switch(eDataType)
1972 case sdbc::DataType::CHAR:
1973 case sdbc::DataType::VARCHAR:
1974 case sdbc::DataType::LONGVARCHAR:
1977 sRet = xColumn->getString();
1978 sRet = sRet.replace( '\xb', '\n' ); // MSWord uses \xb as a newline
1980 catch(const sdbc::SQLException&)
1983 break;
1984 case sdbc::DataType::BIT:
1985 case sdbc::DataType::BOOLEAN:
1986 case sdbc::DataType::TINYINT:
1987 case sdbc::DataType::SMALLINT:
1988 case sdbc::DataType::INTEGER:
1989 case sdbc::DataType::BIGINT:
1990 case sdbc::DataType::FLOAT:
1991 case sdbc::DataType::REAL:
1992 case sdbc::DataType::DOUBLE:
1993 case sdbc::DataType::NUMERIC:
1994 case sdbc::DataType::DECIMAL:
1995 case sdbc::DataType::DATE:
1996 case sdbc::DataType::TIME:
1997 case sdbc::DataType::TIMESTAMP:
2002 sRet = dbtools::DBTypeConversion::getFormattedValue(
2003 xColumnProps,
2004 rDBFormatData.xFormatter,
2005 rDBFormatData.aLocale,
2006 rDBFormatData.aNullDate);
2007 if (pNumber)
2009 double fVal = xColumn->getDouble();
2010 if(!xColumn->wasNull())
2012 *pNumber = fVal;
2016 catch (const uno::Exception&)
2018 TOOLS_WARN_EXCEPTION("sw.mailmerge", "");
2022 break;
2025 return sRet;
2028 // checks if a desired data source table or query is open
2029 bool SwDBManager::IsDataSourceOpen(const OUString& rDataSource,
2030 const OUString& rTableOrQuery, bool bMergeShell)
2032 if(m_pImpl->pMergeData)
2034 return ((rDataSource == m_pImpl->pMergeData->sDataSource
2035 && rTableOrQuery == m_pImpl->pMergeData->sCommand)
2036 || (rDataSource.isEmpty() && rTableOrQuery.isEmpty()))
2037 && m_pImpl->pMergeData->xResultSet.is();
2039 else if(!bMergeShell)
2041 SwDBData aData;
2042 aData.sDataSource = rDataSource;
2043 aData.sCommand = rTableOrQuery;
2044 aData.nCommandType = -1;
2045 SwDSParam* pFound = FindDSData(aData, false);
2046 return (pFound && pFound->xResultSet.is());
2048 return false;
2051 // read column data at a specified position
2052 bool SwDBManager::GetColumnCnt(const OUString& rSourceName, const OUString& rTableName,
2053 const OUString& rColumnName, sal_uInt32 nAbsRecordId,
2054 LanguageType nLanguage,
2055 OUString& rResult, double* pNumber)
2057 bool bRet = false;
2058 SwDSParam* pFound = nullptr;
2059 //check if it's the merge data source
2060 if(m_pImpl->pMergeData &&
2061 rSourceName == m_pImpl->pMergeData->sDataSource &&
2062 rTableName == m_pImpl->pMergeData->sCommand)
2064 pFound = m_pImpl->pMergeData.get();
2066 else
2068 SwDBData aData;
2069 aData.sDataSource = rSourceName;
2070 aData.sCommand = rTableName;
2071 aData.nCommandType = -1;
2072 pFound = FindDSData(aData, false);
2074 if (!pFound)
2075 return false;
2076 //check validity of supplied record Id
2077 if(pFound->aSelection.hasElements())
2079 //the destination has to be an element of the selection
2080 bool bFound = std::any_of(std::cbegin(pFound->aSelection), std::cend(pFound->aSelection),
2081 [nAbsRecordId](const uno::Any& rSelection) {
2082 sal_Int32 nSelection = 0;
2083 rSelection >>= nSelection;
2084 return nSelection == static_cast<sal_Int32>(nAbsRecordId);
2086 if(!bFound)
2087 return false;
2089 if( pFound->HasValidRecord() )
2091 sal_Int32 nOldRow = 0;
2094 nOldRow = pFound->xResultSet->getRow();
2096 catch(const uno::Exception&)
2098 return false;
2100 //position to the desired index
2101 bool bMove = true;
2102 if ( nOldRow != static_cast<sal_Int32>(nAbsRecordId) )
2103 bMove = lcl_MoveAbsolute(pFound, nAbsRecordId);
2104 if(bMove)
2105 bRet = lcl_GetColumnCnt(pFound, rColumnName, nLanguage, rResult, pNumber);
2106 if ( nOldRow != static_cast<sal_Int32>(nAbsRecordId) )
2107 lcl_MoveAbsolute(pFound, nOldRow);
2109 return bRet;
2112 // reads the column data at the current position
2113 bool SwDBManager::GetMergeColumnCnt(const OUString& rColumnName, LanguageType nLanguage,
2114 OUString &rResult, double *pNumber)
2116 if( !IsValidMergeRecord() )
2118 rResult.clear();
2119 return false;
2122 bool bRet = lcl_GetColumnCnt(m_pImpl->pMergeData.get(), rColumnName, nLanguage, rResult, pNumber);
2123 return bRet;
2126 bool SwDBManager::ToNextMergeRecord()
2128 assert( m_pImpl->pMergeData && m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" );
2129 return lcl_ToNextRecord( m_pImpl->pMergeData.get() );
2132 bool SwDBManager::FillCalcWithMergeData( SvNumberFormatter *pDocFormatter,
2133 LanguageType nLanguage, SwCalc &rCalc )
2135 if( !IsValidMergeRecord() )
2136 return false;
2138 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY );
2139 if( !xColsSupp.is() )
2140 return false;
2143 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
2144 const uno::Sequence<OUString> aColNames = xCols->getElementNames();
2145 OUString aString;
2147 // add the "record number" variable, as SwCalc::VarLook would.
2148 rCalc.VarChange( GetAppCharClass().lowercase(
2149 SwFieldType::GetTypeStr(SwFieldTypesEnum::DatabaseSetNumber) ), GetSelectedRecordId() );
2151 for( const OUString& rColName : aColNames )
2153 // get the column type
2154 sal_Int32 nColumnType = sdbc::DataType::SQLNULL;
2155 uno::Any aCol = xCols->getByName( rColName );
2156 uno::Reference<beans::XPropertySet> xColumnProps;
2157 aCol >>= xColumnProps;
2158 uno::Any aType = xColumnProps->getPropertyValue( u"Type"_ustr );
2159 aType >>= nColumnType;
2160 double aNumber = DBL_MAX;
2162 lcl_GetColumnCnt( m_pImpl->pMergeData.get(), xColumnProps, nLanguage, aString, &aNumber );
2164 sal_uInt32 nFormat = GetColumnFormat( m_pImpl->pMergeData->sDataSource,
2165 m_pImpl->pMergeData->sCommand,
2166 rColName, pDocFormatter, nLanguage );
2167 // aNumber is overwritten by SwDBField::FormatValue, so store initial status
2168 bool colIsNumber = aNumber != DBL_MAX;
2169 bool bValidValue = SwDBField::FormatValue( pDocFormatter, aString, nFormat,
2170 aNumber, nColumnType );
2171 if( colIsNumber )
2173 if( bValidValue )
2175 SwSbxValue aValue;
2176 aValue.PutDouble( aNumber );
2177 aValue.SetDBvalue( true );
2178 SAL_INFO( "sw.ui", "'" << rColName << "': " << aNumber << " / " << aString );
2179 rCalc.VarChange( rColName, aValue );
2182 else
2184 SwSbxValue aValue;
2185 aValue.PutString( aString );
2186 aValue.SetDBvalue( true );
2187 SAL_INFO( "sw.ui", "'" << rColName << "': " << aString );
2188 rCalc.VarChange( rColName, aValue );
2193 return true;
2196 void SwDBManager::ToNextRecord(
2197 const OUString& rDataSource, const OUString& rCommand)
2199 SwDSParam* pFound = nullptr;
2200 if(m_pImpl->pMergeData &&
2201 rDataSource == m_pImpl->pMergeData->sDataSource &&
2202 rCommand == m_pImpl->pMergeData->sCommand)
2204 pFound = m_pImpl->pMergeData.get();
2206 else
2208 SwDBData aData;
2209 aData.sDataSource = rDataSource;
2210 aData.sCommand = rCommand;
2211 aData.nCommandType = -1;
2212 pFound = FindDSData(aData, false);
2214 lcl_ToNextRecord( pFound );
2217 static bool lcl_ToNextRecord( SwDSParam* pParam, const SwDBNextRecord action )
2219 bool bRet = true;
2221 assert( SwDBNextRecord::NEXT == action ||
2222 (SwDBNextRecord::FIRST == action && pParam) );
2223 if( nullptr == pParam )
2224 return false;
2226 if( action == SwDBNextRecord::FIRST )
2228 pParam->nSelectionIndex = 0;
2229 pParam->bEndOfDB = false;
2232 if( !pParam->HasValidRecord() )
2233 return false;
2237 if( pParam->aSelection.hasElements() )
2239 if( pParam->nSelectionIndex >= pParam->aSelection.getLength() )
2240 pParam->bEndOfDB = true;
2241 else
2243 sal_Int32 nPos = 0;
2244 pParam->aSelection.getConstArray()[ pParam->nSelectionIndex ] >>= nPos;
2245 pParam->bEndOfDB = !pParam->xResultSet->absolute( nPos );
2248 else if( action == SwDBNextRecord::FIRST )
2250 pParam->bEndOfDB = !pParam->xResultSet->first();
2252 else
2254 sal_Int32 nBefore = pParam->xResultSet->getRow();
2255 pParam->bEndOfDB = !pParam->xResultSet->next();
2256 if( !pParam->bEndOfDB && nBefore == pParam->xResultSet->getRow() )
2258 // next returned true but it didn't move
2259 ::dbtools::throwFunctionSequenceException( pParam->xResultSet );
2263 ++pParam->nSelectionIndex;
2264 bRet = !pParam->bEndOfDB;
2266 catch( const uno::Exception & )
2268 // we allow merging with empty databases, so don't warn on init
2269 TOOLS_WARN_EXCEPTION_IF(action == SwDBNextRecord::NEXT,
2270 "sw.mailmerge", "exception in ToNextRecord()");
2271 pParam->bEndOfDB = true;
2272 bRet = false;
2274 return bRet;
2277 // synchronized labels contain a next record field at their end
2278 // to assure that the next page can be created in mail merge
2279 // the cursor position must be validated
2280 bool SwDBManager::IsValidMergeRecord() const
2282 return( m_pImpl->pMergeData && m_pImpl->pMergeData->HasValidRecord() );
2285 sal_uInt32 SwDBManager::GetSelectedRecordId()
2287 sal_uInt32 nRet = 0;
2288 assert( m_pImpl->pMergeData &&
2289 m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" );
2290 if(!m_pImpl->pMergeData || !m_pImpl->pMergeData->xResultSet.is())
2291 return 0;
2294 nRet = m_pImpl->pMergeData->xResultSet->getRow();
2296 catch(const uno::Exception&)
2299 return nRet;
2302 bool SwDBManager::ToRecordId(sal_Int32 nSet)
2304 assert( m_pImpl->pMergeData &&
2305 m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" );
2306 if(!m_pImpl->pMergeData || !m_pImpl->pMergeData->xResultSet.is()|| nSet < 0)
2307 return false;
2308 bool bRet = false;
2309 sal_Int32 nAbsPos = nSet;
2310 assert(nAbsPos >= 0);
2311 bRet = lcl_MoveAbsolute(m_pImpl->pMergeData.get(), nAbsPos);
2312 m_pImpl->pMergeData->bEndOfDB = !bRet;
2313 return bRet;
2316 bool SwDBManager::OpenDataSource(const OUString& rDataSource, const OUString& rTableOrQuery)
2318 SwDBData aData;
2319 aData.sDataSource = rDataSource;
2320 aData.sCommand = rTableOrQuery;
2321 aData.nCommandType = -1;
2323 SwDSParam* pFound = FindDSData(aData, true);
2324 if(pFound->xResultSet.is())
2325 return true;
2326 SwDSParam* pParam = FindDSConnection(rDataSource, false);
2327 if(pParam && pParam->xConnection.is())
2328 pFound->xConnection = pParam->xConnection;
2329 if(pFound->xConnection.is())
2333 uno::Reference< sdbc::XDatabaseMetaData > xMetaData = pFound->xConnection->getMetaData();
2336 pFound->bScrollable = xMetaData
2337 ->supportsResultSetType(sal_Int32(sdbc::ResultSetType::SCROLL_INSENSITIVE));
2339 catch(const uno::Exception&)
2341 // DB driver may not be ODBC 3.0 compliant
2342 pFound->bScrollable = true;
2344 pFound->xStatement = pFound->xConnection->createStatement();
2345 OUString aQuoteChar = xMetaData->getIdentifierQuoteString();
2346 OUString sStatement = "SELECT * FROM " + aQuoteChar + rTableOrQuery + aQuoteChar;
2347 pFound->xResultSet = pFound->xStatement->executeQuery( sStatement );
2349 //after executeQuery the cursor must be positioned
2350 pFound->bEndOfDB = !pFound->xResultSet->next();
2351 ++pFound->nSelectionIndex;
2353 catch (const uno::Exception&)
2355 pFound->xResultSet = nullptr;
2356 pFound->xStatement = nullptr;
2357 pFound->xConnection = nullptr;
2360 return pFound->xResultSet.is();
2363 uno::Reference< sdbc::XConnection> const & SwDBManager::RegisterConnection(OUString const& rDataSource)
2365 SwDSParam* pFound = SwDBManager::FindDSConnection(rDataSource, true);
2366 uno::Reference< sdbc::XDataSource> xSource;
2367 if(!pFound->xConnection.is())
2369 SwView* pView = (m_pDoc && m_pDoc->GetDocShell()) ? m_pDoc->GetDocShell()->GetView() : nullptr;
2370 pFound->xConnection = SwDBManager::GetConnection(rDataSource, xSource, pView);
2373 uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY);
2374 if(xComponent.is())
2375 xComponent->addEventListener(m_pImpl->m_xDisposeListener);
2377 catch(const uno::Exception&)
2381 return pFound->xConnection;
2384 sal_uInt32 SwDBManager::GetSelectedRecordId(
2385 const OUString& rDataSource, const OUString& rTableOrQuery, sal_Int32 nCommandType)
2387 sal_uInt32 nRet = 0xffffffff;
2388 //check for merge data source first
2389 if(m_pImpl->pMergeData &&
2390 ((rDataSource == m_pImpl->pMergeData->sDataSource &&
2391 rTableOrQuery == m_pImpl->pMergeData->sCommand) ||
2392 (rDataSource.isEmpty() && rTableOrQuery.isEmpty())) &&
2393 (nCommandType == -1 || nCommandType == m_pImpl->pMergeData->nCommandType) &&
2394 m_pImpl->pMergeData->xResultSet.is())
2396 nRet = GetSelectedRecordId();
2398 else
2400 SwDBData aData;
2401 aData.sDataSource = rDataSource;
2402 aData.sCommand = rTableOrQuery;
2403 aData.nCommandType = nCommandType;
2404 SwDSParam* pFound = FindDSData(aData, false);
2405 if(pFound && pFound->xResultSet.is())
2408 { //if a selection array is set the current row at the result set may not be set yet
2409 if(pFound->aSelection.hasElements())
2411 sal_Int32 nSelIndex = pFound->nSelectionIndex;
2412 if(nSelIndex >= pFound->aSelection.getLength())
2413 nSelIndex = pFound->aSelection.getLength() -1;
2414 pFound->aSelection.getConstArray()[nSelIndex] >>= nRet;
2417 else
2418 nRet = pFound->xResultSet->getRow();
2420 catch(const uno::Exception&)
2425 return nRet;
2428 // close all data sources - after fields were updated
2429 void SwDBManager::CloseAll(bool bIncludingMerge)
2431 //the only thing done here is to reset the selection index
2432 //all connections stay open
2433 for (auto & pParam : m_DataSourceParams)
2435 if (bIncludingMerge || pParam.get() != m_pImpl->pMergeData.get())
2437 pParam->nSelectionIndex = 0;
2438 pParam->bEndOfDB = false;
2441 if(!m_bInMerge && pParam->xResultSet.is())
2442 pParam->xResultSet->first();
2444 catch(const uno::Exception&)
2450 SwDSParam* SwDBManager::FindDSData(const SwDBData& rData, bool bCreate)
2452 //prefer merge data if available
2453 if(m_pImpl->pMergeData &&
2454 ((rData.sDataSource == m_pImpl->pMergeData->sDataSource &&
2455 rData.sCommand == m_pImpl->pMergeData->sCommand) ||
2456 (rData.sDataSource.isEmpty() && rData.sCommand.isEmpty())) &&
2457 (rData.nCommandType == -1 || rData.nCommandType == m_pImpl->pMergeData->nCommandType ||
2458 (bCreate && m_pImpl->pMergeData->nCommandType == -1)))
2460 return m_pImpl->pMergeData.get();
2463 SwDSParam* pFound = nullptr;
2464 for (size_t nPos = m_DataSourceParams.size(); nPos; nPos--)
2466 SwDSParam* pParam = m_DataSourceParams[nPos - 1].get();
2467 if(rData.sDataSource == pParam->sDataSource &&
2468 rData.sCommand == pParam->sCommand &&
2469 (rData.nCommandType == -1 || rData.nCommandType == pParam->nCommandType ||
2470 (bCreate && pParam->nCommandType == -1)))
2472 // calls from the calculator may add a connection with an invalid commandtype
2473 //later added "real" data base connections have to re-use the already available
2474 //DSData and set the correct CommandType
2475 if(bCreate && pParam->nCommandType == -1)
2476 pParam->nCommandType = rData.nCommandType;
2477 pFound = pParam;
2478 break;
2481 if(bCreate && !pFound)
2483 pFound = new SwDSParam(rData);
2484 m_DataSourceParams.push_back(std::unique_ptr<SwDSParam>(pFound));
2487 uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY);
2488 if(xComponent.is())
2489 xComponent->addEventListener(m_pImpl->m_xDisposeListener);
2491 catch(const uno::Exception&)
2495 return pFound;
2498 SwDSParam* SwDBManager::FindDSConnection(const OUString& rDataSource, bool bCreate)
2500 //prefer merge data if available
2501 if(m_pImpl->pMergeData && rDataSource == m_pImpl->pMergeData->sDataSource )
2503 SetAsUsed(rDataSource);
2504 return m_pImpl->pMergeData.get();
2506 SwDSParam* pFound = nullptr;
2507 for (const auto & pParam : m_DataSourceParams)
2509 if(rDataSource == pParam->sDataSource)
2511 SetAsUsed(rDataSource);
2512 pFound = pParam.get();
2513 break;
2516 if(bCreate && !pFound)
2518 SwDBData aData;
2519 aData.sDataSource = rDataSource;
2520 pFound = new SwDSParam(aData);
2521 SetAsUsed(rDataSource);
2522 m_DataSourceParams.push_back(std::unique_ptr<SwDSParam>(pFound));
2525 uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY);
2526 if(xComponent.is())
2527 xComponent->addEventListener(m_pImpl->m_xDisposeListener);
2529 catch(const uno::Exception&)
2533 return pFound;
2536 const SwDBData& SwDBManager::GetAddressDBName()
2538 return SwModule::get()->GetDBConfig()->GetAddressSource();
2541 uno::Sequence<OUString> SwDBManager::GetExistingDatabaseNames()
2543 const uno::Reference<uno::XComponentContext>& xContext( ::comphelper::getProcessComponentContext() );
2544 uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext);
2545 return xDBContext->getElementNames();
2548 namespace sw
2550 DBConnURIType GetDBunoType(const INetURLObject &rURL)
2552 OUString sExt(rURL.GetFileExtension());
2553 DBConnURIType type = DBConnURIType::UNKNOWN;
2555 if (sExt == "odb")
2557 type = DBConnURIType::ODB;
2559 else if (sExt.equalsIgnoreAsciiCase("sxc")
2560 || sExt.equalsIgnoreAsciiCase("ods")
2561 || sExt.equalsIgnoreAsciiCase("xls")
2562 || sExt.equalsIgnoreAsciiCase("xlsx"))
2564 type = DBConnURIType::CALC;
2566 else if (sExt.equalsIgnoreAsciiCase("sxw") || sExt.equalsIgnoreAsciiCase("odt") || sExt.equalsIgnoreAsciiCase("doc") || sExt.equalsIgnoreAsciiCase("docx"))
2568 type = DBConnURIType::WRITER;
2570 else if (sExt.equalsIgnoreAsciiCase("dbf"))
2572 type = DBConnURIType::DBASE;
2574 else if (sExt.equalsIgnoreAsciiCase("csv") || sExt.equalsIgnoreAsciiCase("txt"))
2576 type = DBConnURIType::FLAT;
2578 #ifdef _WIN32
2579 else if (sExt.equalsIgnoreAsciiCase("accdb") || sExt.equalsIgnoreAsciiCase("accde")
2580 || sExt.equalsIgnoreAsciiCase("mdb") || sExt.equalsIgnoreAsciiCase("mde"))
2582 type = DBConnURIType::MSACE;
2584 #endif
2585 return type;
2589 namespace
2591 uno::Any GetDBunoURI(const INetURLObject &rURL, DBConnURIType& rType)
2593 uno::Any aURLAny;
2595 if (rType == DBConnURIType::UNKNOWN)
2596 rType = GetDBunoType(rURL);
2598 switch (rType) {
2599 case DBConnURIType::UNKNOWN:
2600 case DBConnURIType::ODB:
2601 break;
2602 case DBConnURIType::CALC:
2604 OUString sDBURL = "sdbc:calc:" +
2605 rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2606 aURLAny <<= sDBURL;
2608 break;
2609 case DBConnURIType::WRITER:
2611 OUString sDBURL = "sdbc:writer:" +
2612 rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2613 aURLAny <<= sDBURL;
2615 break;
2616 case DBConnURIType::DBASE:
2618 INetURLObject aUrlTmp(rURL);
2619 aUrlTmp.removeSegment();
2620 aUrlTmp.removeFinalSlash();
2621 OUString sDBURL = "sdbc:dbase:" +
2622 aUrlTmp.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2623 aURLAny <<= sDBURL;
2625 break;
2626 case DBConnURIType::FLAT:
2628 INetURLObject aUrlTmp(rURL);
2629 aUrlTmp.removeSegment();
2630 aUrlTmp.removeFinalSlash();
2631 OUString sDBURL = "sdbc:flat:" +
2632 //only the 'path' has to be added
2633 aUrlTmp.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2634 aURLAny <<= sDBURL;
2636 break;
2637 case DBConnURIType::MSACE:
2638 #ifdef _WIN32
2640 OUString sDBURL("sdbc:ado:PROVIDER=Microsoft.ACE.OLEDB.12.0;DATA SOURCE=" + rURL.PathToFileName());
2641 aURLAny <<= sDBURL;
2643 #endif
2644 break;
2646 return aURLAny;
2649 /// Returns the URL of this SwDoc.
2650 OUString getOwnURL(SfxObjectShell const * pDocShell)
2652 OUString aRet;
2654 if (!pDocShell)
2655 return aRet;
2657 const INetURLObject& rURLObject = pDocShell->GetMedium()->GetURLObject();
2658 aRet = rURLObject.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2659 return aRet;
2663 Loads a data source from file and registers it.
2665 In case of success it returns the registered name, otherwise an empty string.
2666 Optionally add a prefix to the registered DB name.
2668 OUString LoadAndRegisterDataSource_Impl(DBConnURIType type, const uno::Reference< beans::XPropertySet > *pSettings,
2669 const INetURLObject &rURL, const OUString *pDestDir, SfxObjectShell* pDocShell)
2671 OUString sExt(rURL.GetFileExtension());
2672 uno::Any aTableFilterAny;
2673 uno::Any aSuppressVersionsAny;
2674 uno::Any aInfoAny;
2675 bool bStore = true;
2676 OUString sFind;
2678 uno::Any aURLAny = GetDBunoURI(rURL, type);
2679 switch (type) {
2680 case DBConnURIType::UNKNOWN:
2681 case DBConnURIType::CALC:
2682 case DBConnURIType::WRITER:
2683 break;
2684 case DBConnURIType::ODB:
2685 bStore = false;
2686 break;
2687 case DBConnURIType::FLAT:
2688 case DBConnURIType::DBASE:
2689 //set the filter to the file name without extension
2691 uno::Sequence<OUString> aFilters { rURL.getBase(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset) };
2692 aTableFilterAny <<= aFilters;
2694 break;
2695 case DBConnURIType::MSACE:
2696 aSuppressVersionsAny <<= true;
2697 break;
2702 const uno::Reference<uno::XComponentContext>& xContext(::comphelper::getProcessComponentContext());
2703 uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext);
2705 OUString sNewName = rURL.getName(
2706 INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::Unambiguous);
2707 sal_Int32 nExtLen = sExt.getLength();
2708 sNewName = sNewName.replaceAt(sNewName.getLength() - nExtLen - 1, nExtLen + 1, u"");
2710 //find a unique name if sNewName already exists
2711 sFind = sNewName;
2712 sal_Int32 nIndex = 0;
2713 while (xDBContext->hasByName(sFind))
2714 sFind = sNewName + OUString::number(++nIndex);
2716 uno::Reference<uno::XInterface> xNewInstance;
2717 if (!bStore)
2719 //odb-file
2720 uno::Any aDataSource = xDBContext->getByName(rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE));
2721 aDataSource >>= xNewInstance;
2723 else
2725 xNewInstance = xDBContext->createInstance();
2726 uno::Reference<beans::XPropertySet> xDataProperties(xNewInstance, uno::UNO_QUERY);
2728 if (aURLAny.hasValue())
2729 xDataProperties->setPropertyValue(u"URL"_ustr, aURLAny);
2730 if (aTableFilterAny.hasValue())
2731 xDataProperties->setPropertyValue(u"TableFilter"_ustr, aTableFilterAny);
2732 if (aSuppressVersionsAny.hasValue())
2733 xDataProperties->setPropertyValue(u"SuppressVersionColumns"_ustr, aSuppressVersionsAny);
2734 if (aInfoAny.hasValue())
2735 xDataProperties->setPropertyValue(u"Info"_ustr, aInfoAny);
2737 if (DBConnURIType::FLAT == type && pSettings)
2739 uno::Any aSettings = xDataProperties->getPropertyValue(u"Settings"_ustr);
2740 uno::Reference < beans::XPropertySet > xDSSettings;
2741 aSettings >>= xDSSettings;
2742 ::comphelper::copyProperties(*pSettings, xDSSettings);
2743 xDSSettings->setPropertyValue(u"Extension"_ustr, uno::Any(sExt));
2746 uno::Reference<sdb::XDocumentDataSource> xDS(xNewInstance, uno::UNO_QUERY_THROW);
2747 uno::Reference<frame::XStorable> xStore(xDS->getDatabaseDocument(), uno::UNO_QUERY_THROW);
2748 OUString aOwnURL = getOwnURL(pDocShell);
2749 if (aOwnURL.isEmpty())
2751 // Cannot embed, as embedded data source would need the URL of the parent document.
2752 OUString sHomePath(SvtPathOptions().GetWorkPath());
2753 const OUString sTmpName = utl::CreateTempURL(sNewName, true, u".odb", pDestDir ? pDestDir : &sHomePath);
2754 xStore->storeAsURL(sTmpName, uno::Sequence<beans::PropertyValue>());
2756 else
2758 // Embed.
2759 OUString aStreamRelPath = u"EmbeddedDatabase"_ustr;
2760 uno::Reference<embed::XStorage> xStorage = pDocShell->GetStorage();
2762 // Refer to the sub-storage name in the document settings, so
2763 // we can load it again next time the file is imported.
2764 uno::Reference<lang::XMultiServiceFactory> xFactory(pDocShell->GetModel(), uno::UNO_QUERY);
2765 uno::Reference<beans::XPropertySet> xPropertySet(xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), uno::UNO_QUERY);
2766 xPropertySet->setPropertyValue(u"EmbeddedDatabaseName"_ustr, uno::Any(aStreamRelPath));
2768 // Store it only after setting the above property, so that only one data source gets registered.
2769 SwDBManager::StoreEmbeddedDataSource(xStore, xStorage, aStreamRelPath, aOwnURL);
2772 xDBContext->registerObject(sFind, xNewInstance);
2774 catch (const uno::Exception&)
2776 sFind.clear();
2778 return sFind;
2782 OUString SwDBManager::LoadAndRegisterDataSource(weld::Window* pParent, SwDocShell* pDocShell)
2784 sfx2::FileDialogHelper aDlgHelper(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, pParent);
2785 aDlgHelper.SetContext(sfx2::FileDialogHelper::WriterRegisterDataSource);
2786 uno::Reference < ui::dialogs::XFilePicker3 > xFP = aDlgHelper.GetFilePicker();
2788 OUString sFilterAll(SwResId(STR_FILTER_ALL));
2789 OUString sFilterAllData(SwResId(STR_FILTER_ALL_DATA));
2791 const std::vector<std::pair<OUString, OUString>> filters{
2792 { SwResId(STR_FILTER_SXB), "*.odb" },
2793 { SwResId(STR_FILTER_SXC), "*.ods;*.sxc" },
2794 { SwResId(STR_FILTER_SXW), "*.odt;*.sxw" },
2795 { SwResId(STR_FILTER_DBF), "*.dbf" },
2796 { SwResId(STR_FILTER_XLS), "*.xls;*.xlsx" },
2797 { SwResId(STR_FILTER_DOC), "*.doc;*.docx" },
2798 { SwResId(STR_FILTER_TXT), "*.txt" },
2799 { SwResId(STR_FILTER_CSV), "*.csv" },
2800 #ifdef _WIN32
2801 { SwResId(STR_FILTER_ACCDB), "*.accdb;*.accde;*.mdb;*.mde" },
2802 #endif
2805 OUStringBuffer sAllDataFilter;
2806 for (const auto& [name, filter] : filters)
2808 (void)name;
2809 if (!sAllDataFilter.isEmpty())
2810 sAllDataFilter.append(';');
2811 sAllDataFilter.append(filter);
2814 xFP->appendFilter( sFilterAll, u"*"_ustr );
2815 xFP->appendFilter( sFilterAllData, sAllDataFilter.makeStringAndClear());
2817 // Similar to sfx2::addExtension from sfx2/source/dialog/filtergrouping.cxx
2818 for (const auto& [name, filter] : filters)
2819 xFP->appendFilter(name + " (" + filter + ")", filter);
2821 xFP->setCurrentFilter( sFilterAll ) ;
2822 OUString sFind;
2823 if( ERRCODE_NONE == aDlgHelper.Execute() )
2825 uno::Reference< beans::XPropertySet > aSettings;
2826 const INetURLObject aURL( xFP->getSelectedFiles()[0] );
2827 const DBConnURIType type = GetDBunoType( aURL );
2829 if( DBConnURIType::FLAT == type )
2831 const uno::Reference<uno::XComponentContext>& xContext( ::comphelper::getProcessComponentContext() );
2832 uno::Reference < sdb::XTextConnectionSettings > xSettingsDlg = sdb::TextConnectionSettings::create(xContext);
2833 if( xSettingsDlg->execute() )
2834 aSettings.set( uno::Reference < beans::XPropertySet >( xSettingsDlg, uno::UNO_QUERY_THROW ) );
2836 sFind = LoadAndRegisterDataSource_Impl( type, DBConnURIType::FLAT == type ? &aSettings : nullptr, aURL, nullptr, pDocShell );
2838 s_aUncommittedRegistrations.push_back(std::pair<SwDocShell*, OUString>(pDocShell, sFind));
2840 return sFind;
2843 void SwDBManager::StoreEmbeddedDataSource(const uno::Reference<frame::XStorable>& xStorable,
2844 const uno::Reference<embed::XStorage>& xStorage,
2845 const OUString& rStreamRelPath,
2846 const OUString& rOwnURL, bool bCopyTo)
2848 // Construct vnd.sun.star.pkg:// URL for later loading, and TargetStorage/StreamRelPath for storing.
2849 OUString const sTmpName = ConstructVndSunStarPkgUrl(rOwnURL, rStreamRelPath);
2851 uno::Sequence<beans::PropertyValue> aSequence = comphelper::InitPropertySequence(
2853 {"TargetStorage", uno::Any(xStorage)},
2854 {"StreamRelPath", uno::Any(rStreamRelPath)},
2855 {"BaseURI", uno::Any(rOwnURL)}
2857 if (bCopyTo)
2858 xStorable->storeToURL(sTmpName, aSequence);
2859 else
2860 xStorable->storeAsURL(sTmpName, aSequence);
2863 OUString SwDBManager::LoadAndRegisterDataSource(std::u16string_view rURI, const OUString *pDestDir)
2865 return LoadAndRegisterDataSource_Impl( DBConnURIType::UNKNOWN, nullptr, INetURLObject(rURI), pDestDir, nullptr );
2868 namespace
2870 // tdf#117824 switch the embedded database away from using its current storage and point it to temporary storage
2871 // which allows the original storage to be deleted
2872 void switchEmbeddedDatabaseStorage(const uno::Reference<sdb::XDatabaseContext>& rDatabaseContext, const OUString& rName)
2874 uno::Reference<sdb::XDocumentDataSource> xDS(rDatabaseContext->getByName(rName), uno::UNO_QUERY);
2875 if (!xDS)
2876 return;
2877 uno::Reference<document::XStorageBasedDocument> xStorageDoc(xDS->getDatabaseDocument(), uno::UNO_QUERY);
2878 if (!xStorageDoc)
2879 return;
2880 xStorageDoc->switchToStorage(comphelper::OStorageHelper::GetTemporaryStorage());
2884 void SwDBManager::RevokeDataSource(const OUString& rName)
2886 uno::Reference<sdb::XDatabaseContext> xDatabaseContext = sdb::DatabaseContext::create(comphelper::getProcessComponentContext());
2887 if (xDatabaseContext->hasByName(rName))
2889 switchEmbeddedDatabaseStorage(xDatabaseContext, rName);
2890 xDatabaseContext->revokeObject(rName);
2894 void SwDBManager::LoadAndRegisterEmbeddedDataSource(const SwDBData& rData, const SwDocShell& rDocShell)
2896 uno::Reference<sdb::XDatabaseContext> xDatabaseContext = sdb::DatabaseContext::create(comphelper::getProcessComponentContext());
2898 OUString sDataSource = rData.sDataSource;
2900 // Fallback, just in case the document would contain an embedded data source, but no DB fields.
2901 if (sDataSource.isEmpty())
2902 sDataSource = "EmbeddedDatabase";
2904 SwDBManager::RevokeDataSource( sDataSource );
2906 // Encode the stream name and the real path into a single URL.
2907 const INetURLObject& rURLObject = rDocShell.GetMedium()->GetURLObject();
2908 OUString const aURL = ConstructVndSunStarPkgUrl(
2909 rURLObject.GetMainURL(INetURLObject::DecodeMechanism::NONE),
2910 m_sEmbeddedName);
2912 uno::Reference<uno::XInterface> xDataSource(xDatabaseContext->getByName(aURL), uno::UNO_QUERY);
2913 xDatabaseContext->registerObject( sDataSource, xDataSource );
2915 // temp file - don't remember connection
2916 if (rData.sDataSource.isEmpty())
2917 s_aUncommittedRegistrations.push_back(std::pair<SwDocShell*, OUString>(nullptr, sDataSource));
2920 void SwDBManager::ExecuteFormLetter( SwWrtShell& rSh,
2921 const uno::Sequence<beans::PropertyValue>& rProperties)
2923 //prevent second call
2924 if(m_pImpl->pMergeDialog)
2925 return ;
2926 OUString sDataSource, sDataTableOrQuery;
2927 uno::Sequence<uno::Any> aSelection;
2929 sal_Int32 nCmdType = sdb::CommandType::TABLE;
2930 uno::Reference< sdbc::XConnection> xConnection;
2932 svx::ODataAccessDescriptor aDescriptor(rProperties);
2933 sDataSource = aDescriptor.getDataSource();
2934 OSL_VERIFY(aDescriptor[svx::DataAccessDescriptorProperty::Command] >>= sDataTableOrQuery);
2935 OSL_VERIFY(aDescriptor[svx::DataAccessDescriptorProperty::CommandType] >>= nCmdType);
2937 if ( aDescriptor.has(svx::DataAccessDescriptorProperty::Selection) )
2938 aDescriptor[svx::DataAccessDescriptorProperty::Selection] >>= aSelection;
2939 if ( aDescriptor.has(svx::DataAccessDescriptorProperty::Connection) )
2940 aDescriptor[svx::DataAccessDescriptorProperty::Connection] >>= xConnection;
2942 if(sDataSource.isEmpty() || sDataTableOrQuery.isEmpty())
2944 OSL_FAIL("PropertyValues missing or unset");
2945 return;
2948 //always create a connection for the dialog and dispose it after the dialog has been closed
2949 SwDSParam* pFound = nullptr;
2950 if(!xConnection.is())
2952 xConnection = SwDBManager::RegisterConnection(sDataSource);
2953 pFound = FindDSConnection(sDataSource, true);
2955 SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
2956 m_pImpl->pMergeDialog = pFact->CreateMailMergeDlg(rSh.GetView().GetViewFrame().GetFrameWeld(), rSh,
2957 sDataSource,
2958 sDataTableOrQuery,
2959 nCmdType,
2960 xConnection);
2961 if(m_pImpl->pMergeDialog->Execute() == RET_OK)
2963 aDescriptor[svx::DataAccessDescriptorProperty::Selection] <<= m_pImpl->pMergeDialog->GetSelection();
2965 uno::Reference<sdbc::XResultSet> xResSet = m_pImpl->pMergeDialog->GetResultSet();
2966 if(xResSet.is())
2967 aDescriptor[svx::DataAccessDescriptorProperty::Cursor] <<= xResSet;
2969 // SfxObjectShellRef is ok, since there should be no control over the document lifetime here
2970 SfxObjectShellRef xDocShell = rSh.GetView().GetViewFrame().GetObjectShell();
2972 lcl_emitEvent(SfxEventHintId::SwMailMerge, STR_SW_EVENT_MAIL_MERGE, xDocShell.get());
2974 // prepare mail merge descriptor
2975 SwMergeDescriptor aMergeDesc( m_pImpl->pMergeDialog->GetMergeType(), rSh, aDescriptor );
2976 aMergeDesc.sSaveToFilter = m_pImpl->pMergeDialog->GetSaveFilter();
2977 aMergeDesc.bCreateSingleFile = m_pImpl->pMergeDialog->IsSaveSingleDoc();
2978 aMergeDesc.bPrefixIsFilename = aMergeDesc.bCreateSingleFile;
2979 aMergeDesc.sPrefix = m_pImpl->pMergeDialog->GetTargetURL();
2981 if(!aMergeDesc.bCreateSingleFile)
2983 if(m_pImpl->pMergeDialog->IsGenerateFromDataBase())
2984 aMergeDesc.sDBcolumn = m_pImpl->pMergeDialog->GetColumnName();
2986 if(m_pImpl->pMergeDialog->IsFileEncryptedFromDataBase())
2987 aMergeDesc.sDBPasswordColumn = m_pImpl->pMergeDialog->GetPasswordColumnName();
2990 Merge( aMergeDesc );
2992 lcl_emitEvent(SfxEventHintId::SwMailMergeEnd, STR_SW_EVENT_MAIL_MERGE_END, xDocShell.get());
2994 // reset the cursor inside
2995 xResSet = nullptr;
2996 aDescriptor[svx::DataAccessDescriptorProperty::Cursor] <<= xResSet;
2998 if(pFound)
3000 for (const auto & pParam : m_DataSourceParams)
3002 if (pParam.get() == pFound)
3006 uno::Reference<lang::XComponent> xComp(pParam->xConnection, uno::UNO_QUERY);
3007 if(xComp.is())
3008 xComp->dispose();
3010 catch(const uno::RuntimeException&)
3012 //may be disposed already since multiple entries may have used the same connection
3014 break;
3016 //pFound doesn't need to be removed/deleted -
3017 //this has been done by the SwConnectionDisposedListener_Impl already
3020 m_pImpl->pMergeDialog.disposeAndClear();
3023 void SwDBManager::InsertText(SwWrtShell& rSh,
3024 const uno::Sequence< beans::PropertyValue>& rProperties)
3026 OUString sDataSource, sDataTableOrQuery;
3027 uno::Reference<sdbc::XResultSet> xResSet;
3028 uno::Sequence<uno::Any> aSelection;
3029 sal_Int16 nCmdType = sdb::CommandType::TABLE;
3030 uno::Reference< sdbc::XConnection> xConnection;
3031 for(const beans::PropertyValue& rValue : rProperties)
3033 if ( rValue.Name == "DataSourceName" )
3034 rValue.Value >>= sDataSource;
3035 else if ( rValue.Name == "Command" )
3036 rValue.Value >>= sDataTableOrQuery;
3037 else if ( rValue.Name == "Cursor" )
3038 rValue.Value >>= xResSet;
3039 else if ( rValue.Name == "Selection" )
3040 rValue.Value >>= aSelection;
3041 else if ( rValue.Name == "CommandType" )
3042 rValue.Value >>= nCmdType;
3043 else if ( rValue.Name == "ActiveConnection" )
3044 rValue.Value >>= xConnection;
3046 if(sDataSource.isEmpty() || sDataTableOrQuery.isEmpty() || !xResSet.is())
3048 OSL_FAIL("PropertyValues missing or unset");
3049 return;
3051 const uno::Reference< uno::XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
3052 uno::Reference<sdbc::XDataSource> xSource;
3053 uno::Reference<container::XChild> xChild(xConnection, uno::UNO_QUERY);
3054 if(xChild.is())
3055 xSource.set(xChild->getParent(), uno::UNO_QUERY);
3056 if(!xSource.is())
3057 xSource = dbtools::getDataSource(sDataSource, xContext);
3058 uno::Reference< sdbcx::XColumnsSupplier > xColSupp( xResSet, uno::UNO_QUERY );
3059 SwDBData aDBData;
3060 aDBData.sDataSource = sDataSource;
3061 aDBData.sCommand = sDataTableOrQuery;
3062 aDBData.nCommandType = nCmdType;
3064 SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
3065 VclPtr<AbstractSwInsertDBColAutoPilot> pDlg(pFact->CreateSwInsertDBColAutoPilot( rSh.GetView(),
3066 xSource,
3067 xColSupp,
3068 aDBData ));
3069 pDlg->StartExecuteAsync(
3070 [xConnection, xSource, pDlg, xResSet, aSelection] (sal_Int32 nResult)->void
3072 if (nResult == RET_OK)
3074 OUString sDummy;
3075 auto xTmpConnection = xConnection;
3076 if(!xTmpConnection.is())
3077 xTmpConnection = xSource->getConnection(sDummy, sDummy);
3080 pDlg->DataToDoc( aSelection , xSource, xTmpConnection, xResSet);
3082 catch (const uno::Exception&)
3084 TOOLS_WARN_EXCEPTION("sw.mailmerge", "");
3086 pDlg->disposeOnce();
3093 uno::Reference<sdbc::XDataSource> SwDBManager::getDataSourceAsParent(const uno::Reference< sdbc::XConnection>& _xConnection,const OUString& _sDataSourceName)
3095 uno::Reference<sdbc::XDataSource> xSource;
3098 uno::Reference<container::XChild> xChild(_xConnection, uno::UNO_QUERY);
3099 if ( xChild.is() )
3100 xSource.set(xChild->getParent(), uno::UNO_QUERY);
3101 if ( !xSource.is() )
3102 xSource = dbtools::getDataSource(_sDataSourceName, ::comphelper::getProcessComponentContext());
3104 catch (const uno::Exception&)
3106 TOOLS_WARN_EXCEPTION("sw.mailmerge", "getDataSourceAsParent()");
3108 return xSource;
3111 uno::Reference<sdbc::XResultSet> SwDBManager::createCursor(const OUString& _sDataSourceName,
3112 const OUString& _sCommand,
3113 sal_Int32 _nCommandType,
3114 const uno::Reference<sdbc::XConnection>& _xConnection,
3115 const SwView* pView)
3117 uno::Reference<sdbc::XResultSet> xResultSet;
3120 uno::Reference< lang::XMultiServiceFactory > xMgr( ::comphelper::getProcessServiceFactory() );
3121 if( xMgr.is() )
3123 uno::Reference<uno::XInterface> xInstance = xMgr->createInstance(u"com.sun.star.sdb.RowSet"_ustr);
3124 uno::Reference<beans::XPropertySet> xRowSetPropSet(xInstance, uno::UNO_QUERY);
3125 if(xRowSetPropSet.is())
3127 xRowSetPropSet->setPropertyValue(u"DataSourceName"_ustr, uno::Any(_sDataSourceName));
3128 xRowSetPropSet->setPropertyValue(u"ActiveConnection"_ustr, uno::Any(_xConnection));
3129 xRowSetPropSet->setPropertyValue(u"Command"_ustr, uno::Any(_sCommand));
3130 xRowSetPropSet->setPropertyValue(u"CommandType"_ustr, uno::Any(_nCommandType));
3132 uno::Reference< sdb::XCompletedExecution > xRowSet(xInstance, uno::UNO_QUERY);
3134 if ( xRowSet.is() )
3136 weld::Window* pWindow = pView ? pView->GetFrameWeld() : nullptr;
3137 uno::Reference< task::XInteractionHandler > xHandler( task::InteractionHandler::createWithParent(comphelper::getComponentContext(xMgr), pWindow ? pWindow->GetXWindow() : nullptr), uno::UNO_QUERY_THROW );
3138 xRowSet->executeWithCompletion(xHandler);
3140 xResultSet.set(xRowSet, uno::UNO_QUERY);
3144 catch (const uno::Exception&)
3146 TOOLS_WARN_EXCEPTION("sw.mailmerge", "Caught exception while creating a new RowSet");
3148 return xResultSet;
3151 void SwDBManager::setEmbeddedName(const OUString& rEmbeddedName, SwDocShell& rDocShell)
3153 bool bLoad = m_sEmbeddedName != rEmbeddedName && !rEmbeddedName.isEmpty();
3154 bool bRegisterListener = m_sEmbeddedName.isEmpty() && !rEmbeddedName.isEmpty();
3156 m_sEmbeddedName = rEmbeddedName;
3158 if (bLoad)
3160 uno::Reference<embed::XStorage> xStorage = rDocShell.GetStorage();
3161 // It's OK that we don't have the named sub-storage yet, in case
3162 // we're in the process of creating it.
3163 if (xStorage->hasByName(rEmbeddedName))
3164 LoadAndRegisterEmbeddedDataSource(rDocShell.GetDoc()->GetDBData(), rDocShell);
3167 if (bRegisterListener)
3168 // Register a remove listener, so we know when the embedded data source is removed.
3169 m_pImpl->m_xDataSourceRemovedListener = new SwDataSourceRemovedListener(*this);
3172 const OUString& SwDBManager::getEmbeddedName() const
3174 return m_sEmbeddedName;
3177 SwDoc* SwDBManager::getDoc() const
3179 return m_pDoc;
3182 void SwDBManager::releaseRevokeListener()
3184 if (m_pImpl->m_xDataSourceRemovedListener.is())
3186 m_pImpl->m_xDataSourceRemovedListener->Dispose();
3187 m_pImpl->m_xDataSourceRemovedListener.clear();
3191 SwDBManager::ConnectionDisposedListener_Impl::ConnectionDisposedListener_Impl(SwDBManager& rManager)
3192 : m_pDBManager(&rManager)
3196 void SwDBManager::ConnectionDisposedListener_Impl::disposing( const lang::EventObject& rSource )
3198 ::SolarMutexGuard aGuard;
3200 if (!m_pDBManager) return; // we're disposed too!
3202 uno::Reference<sdbc::XConnection> xSource(rSource.Source, uno::UNO_QUERY);
3203 for (size_t nPos = m_pDBManager->m_DataSourceParams.size(); nPos; nPos--)
3205 SwDSParam* pParam = m_pDBManager->m_DataSourceParams[nPos - 1].get();
3206 if(pParam->xConnection.is() &&
3207 (xSource == pParam->xConnection))
3209 m_pDBManager->m_DataSourceParams.erase(
3210 m_pDBManager->m_DataSourceParams.begin() + nPos - 1);
3215 std::shared_ptr<SwMailMergeConfigItem> SwDBManager::PerformMailMerge(SwView const * pView)
3217 std::shared_ptr<SwMailMergeConfigItem> xConfigItem = pView->GetMailMergeConfigItem();
3218 if (!xConfigItem)
3219 return xConfigItem;
3221 svx::ODataAccessDescriptor aDescriptor;
3222 aDescriptor.setDataSource(xConfigItem->GetCurrentDBData().sDataSource);
3223 aDescriptor[ svx::DataAccessDescriptorProperty::Connection ] <<= xConfigItem->GetConnection().getTyped();
3224 aDescriptor[ svx::DataAccessDescriptorProperty::Cursor ] <<= xConfigItem->GetResultSet();
3225 aDescriptor[ svx::DataAccessDescriptorProperty::Command ] <<= xConfigItem->GetCurrentDBData().sCommand;
3226 aDescriptor[ svx::DataAccessDescriptorProperty::CommandType ] <<= xConfigItem->GetCurrentDBData().nCommandType;
3227 aDescriptor[ svx::DataAccessDescriptorProperty::Selection ] <<= xConfigItem->GetSelection();
3229 SwWrtShell& rSh = pView->GetWrtShell();
3230 xConfigItem->SetTargetView(nullptr);
3232 SwMergeDescriptor aMergeDesc(DBMGR_MERGE_SHELL, rSh, aDescriptor);
3233 aMergeDesc.pMailMergeConfigItem = xConfigItem.get();
3234 aMergeDesc.bCreateSingleFile = true;
3235 rSh.GetDBManager()->Merge(aMergeDesc);
3237 return xConfigItem;
3240 void SwDBManager::RevokeLastRegistrations()
3242 if (s_aUncommittedRegistrations.empty())
3243 return;
3245 SwView* pView = ( m_pDoc && m_pDoc->GetDocShell() ) ? m_pDoc->GetDocShell()->GetView() : nullptr;
3246 if (pView)
3248 const std::shared_ptr<SwMailMergeConfigItem>& xConfigItem = pView->GetMailMergeConfigItem();
3249 if (xConfigItem)
3251 xConfigItem->DisposeResultSet();
3252 xConfigItem->DocumentReloaded();
3256 for (auto it = s_aUncommittedRegistrations.begin(); it != s_aUncommittedRegistrations.end();)
3258 if ((m_pDoc && it->first == m_pDoc->GetDocShell()) || it->first == nullptr)
3260 RevokeDataSource(it->second);
3261 it = s_aUncommittedRegistrations.erase(it);
3263 else
3264 ++it;
3268 void SwDBManager::CommitLastRegistrations()
3270 for (auto aIt = s_aUncommittedRegistrations.begin(); aIt != s_aUncommittedRegistrations.end();)
3272 if (aIt->first == m_pDoc->GetDocShell() || aIt->first == nullptr)
3274 m_aNotUsedConnections.push_back(aIt->second);
3275 aIt = s_aUncommittedRegistrations.erase(aIt);
3277 else
3278 aIt++;
3282 void SwDBManager::SetAsUsed(const OUString& rName)
3284 auto aFound = std::find(m_aNotUsedConnections.begin(), m_aNotUsedConnections.end(), rName);
3285 if (aFound != m_aNotUsedConnections.end())
3286 m_aNotUsedConnections.erase(aFound);
3289 void SwDBManager::RevokeNotUsedConnections()
3291 for (auto aIt = m_aNotUsedConnections.begin(); aIt != m_aNotUsedConnections.end();)
3293 RevokeDataSource(*aIt);
3294 aIt = m_aNotUsedConnections.erase(aIt);
3298 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */