android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / uibase / dbui / dbmgr.cxx
blob5a536cc9ef58e11b02bc6ee39139d1e8e57c25e0
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 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("IsRowCountFinal");
191 aFinal >>= bFinal;
192 if(!bFinal)
194 pParam->xResultSet->last();
195 pParam->xResultSet->first();
197 uno::Any aCount = xPrSet->getPropertyValue("RowCount");
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 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 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("NumberFormatsSupplier");
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("NullDate");
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 pWorkShell->ChgDBData(aData);
524 m_bInMerge = true;
526 if (IsInitDBFields())
528 // with database fields without DB-Name, use DB-Name from Doc
529 std::vector<OUString> aDBNames;
530 aDBNames.emplace_back();
531 SwDBData aInsertData = pWorkShell->GetDBData();
532 OUString sDBName = aInsertData.sDataSource
533 + OUStringChar(DB_DELIM) + aInsertData.sCommand
534 + OUStringChar(DB_DELIM)
535 + OUString::number(aInsertData.nCommandType);
536 pWorkShell->ChangeDBFields( aDBNames, sDBName);
537 SetInitDBFields(false);
540 bool bRet = true;
541 switch(rMergeDesc.nMergeType)
543 case DBMGR_MERGE:
544 pWorkShell->StartAllAction();
545 pWorkShell->SwViewShell::UpdateFields( true );
546 pWorkShell->SetModified();
547 pWorkShell->EndAllAction();
548 break;
550 case DBMGR_MERGE_PRINTER:
551 case DBMGR_MERGE_EMAIL:
552 case DBMGR_MERGE_FILE:
553 case DBMGR_MERGE_SHELL:
554 // save files and send them as e-Mail if required
555 bRet = MergeMailFiles(pWorkShell, rMergeDesc);
556 break;
558 default:
559 // insert selected entries
560 // (was: InsertRecord)
561 ImportFromConnection(pWorkShell);
562 break;
565 m_pImpl->pMergeData.reset();
567 if( xWorkObjSh.Is() )
569 pWorkDoc->SetDBManager( pWorkDocOrigDBManager );
570 xWorkObjSh->DoClose();
573 m_bInMerge = false;
575 return bRet;
578 void SwDBManager::ImportFromConnection( SwWrtShell* pSh )
580 if(!m_pImpl->pMergeData || m_pImpl->pMergeData->bEndOfDB)
581 return;
583 pSh->StartAllAction();
584 pSh->StartUndo();
585 bool bGroupUndo(pSh->DoesGroupUndo());
586 pSh->DoGroupUndo(false);
588 if( pSh->HasSelection() )
589 pSh->DelRight();
591 std::optional<SwWait> oWait;
594 sal_uLong i = 0;
595 do {
597 ImportDBEntry(pSh);
598 if( 10 == ++i )
599 oWait.emplace( *pSh->GetView().GetDocShell(), true);
601 } while(ToNextMergeRecord());
604 pSh->DoGroupUndo(bGroupUndo);
605 pSh->EndUndo();
606 pSh->EndAllAction();
609 void SwDBManager::ImportDBEntry(SwWrtShell* pSh)
611 if(!m_pImpl->pMergeData || m_pImpl->pMergeData->bEndOfDB)
612 return;
614 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY );
615 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
616 OUStringBuffer sStr;
617 uno::Sequence<OUString> aColNames = xCols->getElementNames();
618 const OUString* pColNames = aColNames.getConstArray();
619 tools::Long nLength = aColNames.getLength();
620 for(tools::Long i = 0; i < nLength; i++)
622 uno::Any aCol = xCols->getByName(pColNames[i]);
623 uno::Reference< beans::XPropertySet > xColumnProp;
624 aCol >>= xColumnProp;
625 SwDBFormatData aDBFormat;
626 sStr.append(GetDBField( xColumnProp, aDBFormat));
627 if (i < nLength - 1)
628 sStr.append("\t");
630 pSh->SwEditShell::Insert2(sStr.makeStringAndClear());
631 pSh->SwFEShell::SplitNode(); // line feed
634 bool SwDBManager::GetTableNames(weld::ComboBox& rBox, const OUString& rDBName)
636 bool bRet = false;
637 OUString sOldTableName(rBox.get_active_text());
638 rBox.clear();
639 SwDSParam* pParam = FindDSConnection(rDBName, false);
640 uno::Reference< sdbc::XConnection> xConnection;
641 if (pParam && pParam->xConnection.is())
642 xConnection = pParam->xConnection;
643 else
645 if ( !rDBName.isEmpty() )
646 xConnection = RegisterConnection( rDBName );
648 if (xConnection.is())
650 uno::Reference<sdbcx::XTablesSupplier> xTSupplier(xConnection, uno::UNO_QUERY);
651 if(xTSupplier.is())
653 uno::Reference<container::XNameAccess> xTables = xTSupplier->getTables();
654 const uno::Sequence<OUString> aTables = xTables->getElementNames();
655 for (const OUString& rTable : aTables)
656 rBox.append("0", rTable);
658 uno::Reference<sdb::XQueriesSupplier> xQSupplier(xConnection, uno::UNO_QUERY);
659 if(xQSupplier.is())
661 uno::Reference<container::XNameAccess> xQueries = xQSupplier->getQueries();
662 const uno::Sequence<OUString> aQueries = xQueries->getElementNames();
663 for (const OUString& rQuery : aQueries)
664 rBox.append("1", rQuery);
666 if (!sOldTableName.isEmpty())
667 rBox.set_active_text(sOldTableName);
668 bRet = true;
670 return bRet;
673 // fill Listbox with column names of a database
674 void SwDBManager::GetColumnNames(weld::ComboBox& rBox,
675 const OUString& rDBName, const OUString& rTableName)
677 SwDBData aData;
678 aData.sDataSource = rDBName;
679 aData.sCommand = rTableName;
680 aData.nCommandType = -1;
681 SwDSParam* pParam = FindDSData(aData, false);
682 uno::Reference< sdbc::XConnection> xConnection;
683 if(pParam && pParam->xConnection.is())
684 xConnection = pParam->xConnection;
685 else
687 xConnection = RegisterConnection( rDBName );
689 GetColumnNames(rBox, xConnection, rTableName);
692 void SwDBManager::GetColumnNames(weld::ComboBox& rBox,
693 uno::Reference< sdbc::XConnection> const & xConnection,
694 const OUString& rTableName)
696 rBox.clear();
697 uno::Reference< sdbcx::XColumnsSupplier> xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName);
698 if(xColsSupp.is())
700 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
701 const uno::Sequence<OUString> aColNames = xCols->getElementNames();
702 for (const OUString& rColName : aColNames)
704 rBox.append_text(rColName);
706 ::comphelper::disposeComponent( xColsSupp );
710 SwDBManager::SwDBManager(SwDoc* pDoc)
711 : m_aMergeStatus( MergeStatus::Ok )
712 , m_bInitDBFields(false)
713 , m_bInMerge(false)
714 , m_bMergeSilent(false)
715 , m_pImpl(new SwDBManager_Impl(*this))
716 , m_pMergeEvtSrc(nullptr)
717 , m_pDoc(pDoc)
721 void SwDBManager::ImplDestroy()
723 RevokeLastRegistrations();
725 // copy required, m_DataSourceParams can be modified while disposing components
726 std::vector<uno::Reference<sdbc::XConnection>> aCopiedConnections;
727 for (const auto & pParam : m_DataSourceParams)
729 if(pParam->xConnection.is())
731 aCopiedConnections.push_back(pParam->xConnection);
734 for (const auto & xConnection : aCopiedConnections)
738 uno::Reference<lang::XComponent> xComp(xConnection, uno::UNO_QUERY);
739 if(xComp.is())
740 xComp->dispose();
742 catch(const uno::RuntimeException&)
744 //may be disposed already since multiple entries may have used the same connection
749 SwDBManager::~SwDBManager()
751 suppress_fun_call_w_exception(ImplDestroy());
754 static void lcl_RemoveSectionLinks( SwWrtShell& rWorkShell )
756 //reset all links of the sections of synchronized labels
757 size_t nSections = rWorkShell.GetSectionFormatCount();
758 for (size_t nSection = 0; nSection < nSections; ++nSection)
760 SwSectionData aSectionData( *rWorkShell.GetSectionFormat( nSection ).GetSection() );
761 if( aSectionData.GetType() == SectionType::FileLink )
763 aSectionData.SetType( SectionType::Content );
764 aSectionData.SetLinkFileName( OUString() );
765 rWorkShell.UpdateSection( nSection, aSectionData );
768 rWorkShell.SetLabelDoc( false );
771 static void lcl_SaveDebugDoc( SfxObjectShell *xTargetDocShell,
772 const char *name, int no = 0 )
774 static OUString sTempDirURL;
775 if( sTempDirURL.isEmpty() )
777 SvtPathOptions aPathOpt;
778 utl::TempFileNamed aTempDir( &aPathOpt.GetTempPath(), true );
779 if( aTempDir.IsValid() )
781 INetURLObject aTempDirURL( aTempDir.GetURL() );
782 sTempDirURL = aTempDirURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
783 SAL_INFO( "sw.mailmerge", "Dump directory: " << sTempDirURL );
786 if( sTempDirURL.isEmpty() )
787 return;
789 OUString basename = OUString::createFromAscii( name );
790 if (no > 0)
791 basename += OUString::number(no) + "-";
792 // aTempFile is not deleted, but that seems to be intentional
793 utl::TempFileNamed aTempFile( basename, true, u".odt", &sTempDirURL );
794 INetURLObject aTempFileURL( aTempFile.GetURL() );
795 auto pDstMed = std::make_unique<SfxMedium>(
796 aTempFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
797 StreamMode::STD_READWRITE );
798 bool bAnyError = !xTargetDocShell->DoSaveAs( *pDstMed );
799 // xObjectShell->DoSaveCompleted crashes the mail merge unit tests, so skip it
800 bAnyError |= (ERRCODE_NONE != xTargetDocShell->GetError());
801 if( bAnyError )
802 SAL_WARN( "sw.mailmerge", "Error saving: " << aTempFile.GetURL() );
803 else
804 SAL_INFO( "sw.mailmerge", "Saved doc as: " << aTempFile.GetURL() );
807 static bool lcl_SaveDoc(
808 const INetURLObject* pFileURL,
809 const std::shared_ptr<const SfxFilter>& pStoreToFilter,
810 const OUString* pStoreToFilterOptions,
811 const uno::Sequence< beans::PropertyValue >* pSaveToFilterData,
812 const bool bIsPDFexport,
813 SfxObjectShell* xObjectShell,
814 SwWrtShell& rWorkShell,
815 OUString * const decodedURL = nullptr )
817 OUString url = pFileURL->GetMainURL( INetURLObject::DecodeMechanism::NONE );
818 if( decodedURL )
819 (*decodedURL) = url;
821 SfxMedium* pDstMed = new SfxMedium( url, StreamMode::STD_READWRITE );
822 pDstMed->SetFilter( pStoreToFilter );
823 if( pDstMed->GetItemSet() )
825 if( pStoreToFilterOptions )
826 pDstMed->GetItemSet()->Put( SfxStringItem(SID_FILE_FILTEROPTIONS,
827 *pStoreToFilterOptions));
828 if( pSaveToFilterData->hasElements() )
829 pDstMed->GetItemSet()->Put( SfxUnoAnyItem(SID_FILTER_DATA,
830 uno::Any(*pSaveToFilterData)));
833 // convert fields to text if we are exporting to PDF.
834 // this prevents a second merge while updating the fields
835 // in SwXTextDocument::getRendererCount()
836 if( bIsPDFexport )
837 rWorkShell.ConvertFieldsToText();
839 bool bAnyError = !xObjectShell->DoSaveAs(*pDstMed);
840 // Actually this should be a bool... so in case of email and individual
841 // files, where this is set, we skip the recently used handling
842 bAnyError |= !xObjectShell->DoSaveCompleted( pDstMed, !decodedURL );
843 bAnyError |= (ERRCODE_NONE != xObjectShell->GetError());
844 if( bAnyError )
846 // error message ??
847 ErrorHandler::HandleError( xObjectShell->GetError() );
849 return !bAnyError;
852 static void lcl_PreparePrinterOptions(
853 const uno::Sequence< beans::PropertyValue >& rInPrintOptions,
854 uno::Sequence< beans::PropertyValue >& rOutPrintOptions)
856 // printing should be done synchronously otherwise the document
857 // might already become invalid during the process
859 rOutPrintOptions = { comphelper::makePropertyValue("Wait", true) };
861 // copy print options
862 sal_Int32 nIndex = 1;
863 for( const beans::PropertyValue& rOption : rInPrintOptions)
865 if( rOption.Name == "CopyCount" || rOption.Name == "FileName"
866 || rOption.Name == "Collate" || rOption.Name == "Pages"
867 || rOption.Name == "Wait" || rOption.Name == "PrinterName" )
869 // add an option
870 rOutPrintOptions.realloc( nIndex + 1 );
871 auto pOutPrintOptions = rOutPrintOptions.getArray();
872 pOutPrintOptions[ nIndex ].Name = rOption.Name;
873 pOutPrintOptions[ nIndex++ ].Value = rOption.Value ;
878 static void lcl_PrepareSaveFilterDataOptions(
879 const uno::Sequence< beans::PropertyValue >& rInSaveFilterDataptions,
880 uno::Sequence< beans::PropertyValue >& rOutSaveFilterDataOptions,
881 const OUString& sPassword)
883 rOutSaveFilterDataOptions
884 = { comphelper::makePropertyValue("EncryptFile", true),
885 comphelper::makePropertyValue("DocumentOpenPassword", sPassword) };
887 // copy other options
888 sal_Int32 nIndex = 2;
889 for( const beans::PropertyValue& rOption : rInSaveFilterDataptions)
891 rOutSaveFilterDataOptions.realloc( nIndex + 1 );
892 auto pOutSaveFilterDataOptions = rOutSaveFilterDataOptions.getArray();
893 pOutSaveFilterDataOptions[ nIndex ].Name = rOption.Name;
894 pOutSaveFilterDataOptions[ nIndex++ ].Value = rOption.Value ;
900 static SfxObjectShell* lcl_CreateWorkingDocument(
901 // input
902 const WorkingDocType aType, const SwWrtShell &rSourceWrtShell,
903 // optional input
904 const vcl::Window *pSourceWindow,
905 // optional in and output to swap the DB manager
906 SwDBManager** const ppDBManager,
907 // optional output
908 SwView** const pView, SwWrtShell** const pWrtShell, rtl::Reference<SwDoc>* const pDoc )
910 const SwDoc *pSourceDoc = rSourceWrtShell.GetDoc();
911 SfxObjectShellRef xWorkObjectShell = pSourceDoc->CreateCopy( true, (aType == WorkingDocType::TARGET) );
912 SfxViewFrame* pWorkFrame = SfxViewFrame::LoadHiddenDocument( *xWorkObjectShell, SFX_INTERFACE_NONE );
914 if( pSourceWindow )
916 // the created window has to be located at the same position as the source window
917 vcl::Window& rTargetWindow = pWorkFrame->GetFrame().GetWindow();
918 rTargetWindow.SetPosPixel( pSourceWindow->GetPosPixel() );
921 SwView* pWorkView = static_cast< SwView* >( pWorkFrame->GetViewShell() );
923 if (SwWrtShell* pWorkWrtShell = pWorkView->GetWrtShellPtr())
925 pWorkWrtShell->GetViewOptions()->SetIdle( false );
926 pWorkView->AttrChangedNotify(nullptr);// in order for SelectShell to be called
927 SwDoc* pWorkDoc = pWorkWrtShell->GetDoc();
928 pWorkDoc->GetIDocumentUndoRedo().DoUndo( false );
929 pWorkDoc->ReplaceDocumentProperties( *pSourceDoc );
931 // import print settings
932 const SwPrintData &rPrintData = pSourceDoc->getIDocumentDeviceAccess().getPrintData();
933 pWorkDoc->getIDocumentDeviceAccess().setPrintData(rPrintData);
934 const JobSetup *pJobSetup = pSourceDoc->getIDocumentDeviceAccess().getJobsetup();
935 if (pJobSetup)
936 pWorkDoc->getIDocumentDeviceAccess().setJobsetup(*pJobSetup);
938 if( aType == WorkingDocType::TARGET )
940 assert( !ppDBManager );
941 pWorkDoc->SetInMailMerge( true );
942 pWorkWrtShell->SetLabelDoc( false );
944 else
946 // We have to swap the DBmanager of the new doc, so we also need input
947 assert(ppDBManager && *ppDBManager);
948 SwDBManager *pWorkDBManager = pWorkDoc->GetDBManager();
949 pWorkDoc->SetDBManager( *ppDBManager );
950 *ppDBManager = pWorkDBManager;
952 if( aType == WorkingDocType::SOURCE )
954 // the GetDBData call constructs the data, if it's missing - kind of const...
955 pWorkWrtShell->ChgDBData( const_cast<SwDoc*>(pSourceDoc)->GetDBData() );
956 // some DocumentSettings are currently not copied by SwDoc::CreateCopy
957 pWorkWrtShell->SetLabelDoc( rSourceWrtShell.IsLabelDoc() );
958 pWorkDoc->getIDocumentState().ResetModified();
960 else
961 pWorkDoc->getIDocumentLinksAdministration().EmbedAllLinks();
964 if( pView ) *pView = pWorkView;
965 if( pWrtShell ) *pWrtShell = pWorkWrtShell;
966 if( pDoc ) *pDoc = pWorkDoc;
969 return xWorkObjectShell.get();
972 static rtl::Reference<SwMailMessage> lcl_CreateMailFromDoc(
973 const SwMergeDescriptor &rMergeDescriptor,
974 const OUString &sFileURL, const OUString &sMailRecipient,
975 const OUString &sMailBodyMimeType, rtl_TextEncoding sMailEncoding,
976 const OUString &sAttachmentMimeType )
978 rtl::Reference<SwMailMessage> pMessage = new SwMailMessage;
979 if( rMergeDescriptor.pMailMergeConfigItem->IsMailReplyTo() )
980 pMessage->setReplyToAddress(rMergeDescriptor.pMailMergeConfigItem->GetMailReplyTo());
981 pMessage->addRecipient( sMailRecipient );
982 pMessage->SetSenderAddress( rMergeDescriptor.pMailMergeConfigItem->GetMailAddress() );
984 OUStringBuffer sBody;
985 if( rMergeDescriptor.bSendAsAttachment )
987 sBody = rMergeDescriptor.sMailBody;
988 mail::MailAttachment aAttach;
989 aAttach.Data = new SwMailTransferable( sFileURL,
990 rMergeDescriptor.sAttachmentName, sAttachmentMimeType );
991 aAttach.ReadableName = rMergeDescriptor.sAttachmentName;
992 pMessage->addAttachment( aAttach );
994 else
996 //read in the temporary file and use it as mail body
997 SfxMedium aMedium( sFileURL, StreamMode::READ );
998 SvStream* pInStream = aMedium.GetInStream();
999 assert( pInStream && "no output file created?" );
1000 if( !pInStream )
1001 return pMessage;
1003 pInStream->SetStreamCharSet( sMailEncoding );
1004 OStringBuffer sLine;
1005 while ( pInStream->ReadLine( sLine ) )
1007 sBody.append(OStringToOUString( sLine, sMailEncoding ) + "\n");
1010 pMessage->setSubject( rMergeDescriptor.sSubject );
1011 uno::Reference< datatransfer::XTransferable> xBody =
1012 new SwMailTransferable( sBody.makeStringAndClear(), sMailBodyMimeType );
1013 pMessage->setBody( xBody );
1015 for( const OUString& sCcRecipient : rMergeDescriptor.aCopiesTo )
1016 pMessage->addCcRecipient( sCcRecipient );
1017 for( const OUString& sBccRecipient : rMergeDescriptor.aBlindCopiesTo )
1018 pMessage->addBccRecipient( sBccRecipient );
1020 return pMessage;
1023 class SwDBManager::MailDispatcherListener_Impl : public IMailDispatcherListener
1025 SwDBManager &m_rDBManager;
1027 public:
1028 explicit MailDispatcherListener_Impl( SwDBManager &rDBManager )
1029 : m_rDBManager( rDBManager ) {}
1031 virtual void idle() override {}
1033 virtual void mailDelivered( uno::Reference< mail::XMailMessage> xMessage ) override
1035 std::unique_lock aGuard( m_rDBManager.m_pImpl->m_aAllEmailSendMutex );
1036 if ( m_rDBManager.m_pImpl->m_xLastMessage == xMessage )
1037 m_rDBManager.m_pImpl->m_xLastMessage.clear();
1040 virtual void mailDeliveryError( ::rtl::Reference<MailDispatcher> xMailDispatcher,
1041 uno::Reference< mail::XMailMessage>, const OUString& ) override
1043 std::unique_lock aGuard( m_rDBManager.m_pImpl->m_aAllEmailSendMutex );
1044 m_rDBManager.m_aMergeStatus = MergeStatus::Error;
1045 m_rDBManager.m_pImpl->m_xLastMessage.clear();
1046 xMailDispatcher->stop();
1051 * Please have a look at the README in the same directory, before you make
1052 * larger changes in this function!
1054 bool SwDBManager::MergeMailFiles(SwWrtShell* pSourceShell,
1055 const SwMergeDescriptor& rMergeDescriptor)
1057 // deconstruct mail merge type for better readability.
1058 // uppercase naming is intentional!
1059 const bool bMT_EMAIL = rMergeDescriptor.nMergeType == DBMGR_MERGE_EMAIL;
1060 const bool bMT_SHELL = rMergeDescriptor.nMergeType == DBMGR_MERGE_SHELL;
1061 const bool bMT_PRINTER = rMergeDescriptor.nMergeType == DBMGR_MERGE_PRINTER;
1062 const bool bMT_FILE = rMergeDescriptor.nMergeType == DBMGR_MERGE_FILE;
1064 //check if the doc is synchronized and contains at least one linked section
1065 const bool bSynchronizedDoc = pSourceShell->IsLabelDoc() && pSourceShell->GetSectionFormatCount() > 1;
1066 const bool bNeedsTempFiles = ( bMT_EMAIL || bMT_FILE );
1067 const bool bIsMergeSilent = IsMergeSilent();
1069 bool bCheckSingleFile_ = rMergeDescriptor.bCreateSingleFile;
1070 OUString sPrefix_ = rMergeDescriptor.sPrefix;
1071 if( bMT_EMAIL )
1073 assert( !rMergeDescriptor.bPrefixIsFilename );
1074 assert(!bCheckSingleFile_);
1075 bCheckSingleFile_ = false;
1077 else if( bMT_SHELL || bMT_PRINTER )
1079 assert(bCheckSingleFile_);
1080 bCheckSingleFile_ = true;
1081 assert(sPrefix_.isEmpty());
1082 sPrefix_.clear();
1084 const bool bCreateSingleFile = bCheckSingleFile_;
1085 const OUString sDescriptorPrefix = sPrefix_;
1087 // Setup for dumping debugging documents
1088 static const sal_Int32 nMaxDumpDocs = []() {
1089 if (const char* sEnv = getenv("SW_DEBUG_MAILMERGE_DOCS"))
1090 return OUString(sEnv, strlen(sEnv), osl_getThreadTextEncoding()).toInt32();
1091 else
1092 return sal_Int32(0);
1093 }();
1095 ::rtl::Reference< MailDispatcher > xMailDispatcher;
1096 ::rtl::Reference< IMailDispatcherListener > xMailListener;
1097 OUString sMailBodyMimeType;
1098 rtl_TextEncoding sMailEncoding = ::osl_getThreadTextEncoding();
1100 uno::Reference< beans::XPropertySet > xColumnProp;
1101 uno::Reference< beans::XPropertySet > xPasswordColumnProp;
1103 // Check for (mandatory) email or (optional) filename column
1104 SwDBFormatData aColumnDBFormat;
1105 bool bColumnName = !rMergeDescriptor.sDBcolumn.isEmpty();
1106 bool bPasswordColumnName = !rMergeDescriptor.sDBPasswordColumn.isEmpty();
1108 if( ! bColumnName )
1110 if( bMT_EMAIL )
1111 return false;
1113 else
1115 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY );
1116 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
1117 if( !xCols->hasByName( rMergeDescriptor.sDBcolumn ) )
1118 return false;
1119 uno::Any aCol = xCols->getByName( rMergeDescriptor.sDBcolumn );
1120 aCol >>= xColumnProp;
1122 if(bPasswordColumnName)
1124 aCol = xCols->getByName( rMergeDescriptor.sDBPasswordColumn );
1125 aCol >>= xPasswordColumnProp;
1128 aColumnDBFormat.xFormatter = m_pImpl->pMergeData->xFormatter;
1129 aColumnDBFormat.aNullDate = m_pImpl->pMergeData->aNullDate;
1131 if( bMT_EMAIL )
1133 // Reset internal mail accounting data
1134 m_pImpl->m_xLastMessage.clear();
1136 xMailDispatcher.set( new MailDispatcher(rMergeDescriptor.xSmtpServer) );
1137 xMailListener = new MailDispatcherListener_Impl( *this );
1138 xMailDispatcher->addListener( xMailListener );
1139 if(!rMergeDescriptor.bSendAsAttachment && rMergeDescriptor.bSendAsHTML)
1141 sMailBodyMimeType = "text/html; charset=utf-8";
1142 sMailEncoding = RTL_TEXTENCODING_UTF8;
1144 else
1145 sMailBodyMimeType = "text/plain; charset=UTF-8; format=flowed";
1149 SwDocShell *pSourceDocSh = pSourceShell->GetView().GetDocShell();
1151 // setup the output format
1152 std::shared_ptr<const SfxFilter> pStoreToFilter = SwIoSystem::GetFileFilter(
1153 pSourceDocSh->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE));
1154 SfxFilterContainer* pFilterContainer = SwDocShell::Factory().GetFilterContainer();
1155 const OUString* pStoreToFilterOptions = nullptr;
1157 // if a save_to filter is set then use it - otherwise use the default
1158 if( bMT_EMAIL && !rMergeDescriptor.bSendAsAttachment )
1160 OUString sExtension = rMergeDescriptor.bSendAsHTML ? OUString("html") : OUString("txt");
1161 pStoreToFilter = pFilterContainer->GetFilter4Extension(sExtension, SfxFilterFlags::EXPORT);
1163 else if( !rMergeDescriptor.sSaveToFilter.isEmpty())
1165 std::shared_ptr<const SfxFilter> pFilter =
1166 pFilterContainer->GetFilter4FilterName( rMergeDescriptor.sSaveToFilter );
1167 if(pFilter)
1169 pStoreToFilter = pFilter;
1170 if(!rMergeDescriptor.sSaveToFilterOptions.isEmpty())
1171 pStoreToFilterOptions = &rMergeDescriptor.sSaveToFilterOptions;
1174 const bool bIsPDFexport = pStoreToFilter && pStoreToFilter->GetFilterName() == "writer_pdf_Export";
1175 const bool bIsMultiFile = bMT_FILE && !bCreateSingleFile;
1177 m_aMergeStatus = MergeStatus::Ok;
1179 // in case of creating a single resulting file this has to be created here
1180 SwView* pTargetView = rMergeDescriptor.pMailMergeConfigItem ?
1181 rMergeDescriptor.pMailMergeConfigItem->GetTargetView() : nullptr;
1182 SwWrtShell* pTargetShell = nullptr;
1183 SfxObjectShellRef xTargetDocShell;
1184 rtl::Reference<SwDoc> pTargetDoc;
1186 std::unique_ptr< utl::TempFileNamed > aTempFile;
1187 sal_uInt16 nStartingPageNo = 0;
1189 std::shared_ptr<weld::GenericDialogController> xProgressDlg;
1193 vcl::Window *pSourceWindow = nullptr;
1194 if( !bIsMergeSilent )
1196 // construct the process dialog
1197 pSourceWindow = &pSourceShell->GetView().GetEditWin();
1198 if (!bMT_PRINTER)
1199 xProgressDlg = std::make_shared<CreateMonitor>(pSourceWindow->GetFrameWeld());
1200 else
1202 xProgressDlg = std::make_shared<PrintMonitor>(pSourceWindow->GetFrameWeld());
1203 static_cast<PrintMonitor*>(xProgressDlg.get())->set_title(
1204 pSourceDocSh->GetTitle(22));
1206 weld::DialogController::runAsync(xProgressDlg, [this, &xProgressDlg](sal_Int32 nResult){
1207 if (nResult == RET_CANCEL)
1208 MergeCancel();
1209 xProgressDlg.reset();
1212 Application::Reschedule( true );
1215 if( bCreateSingleFile && !pTargetView )
1217 // create a target docshell to put the merged document into
1218 xTargetDocShell = lcl_CreateWorkingDocument( WorkingDocType::TARGET,
1219 *pSourceShell, bMT_SHELL ? pSourceWindow : nullptr,
1220 nullptr, &pTargetView, &pTargetShell, &pTargetDoc );
1222 if (nMaxDumpDocs)
1223 lcl_SaveDebugDoc( xTargetDocShell.get(), "MergeDoc" );
1225 else if( pTargetView )
1227 pTargetShell = pTargetView->GetWrtShellPtr();
1228 if (pTargetShell)
1230 pTargetDoc = pTargetShell->GetDoc();
1231 xTargetDocShell = pTargetView->GetDocShell();
1235 if( bCreateSingleFile )
1237 // determine the page style and number used at the start of the source document
1238 pSourceShell->SttEndDoc(true);
1239 nStartingPageNo = pSourceShell->GetVirtPageNum();
1242 // Progress, to prohibit KeyInputs
1243 SfxProgress aProgress(pSourceDocSh, OUString(), 1);
1245 // lock all dispatchers
1246 SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst(pSourceDocSh);
1247 while (pViewFrame)
1249 pViewFrame->GetDispatcher()->Lock(true);
1250 pViewFrame = SfxViewFrame::GetNext(*pViewFrame, pSourceDocSh);
1253 sal_Int32 nDocNo = 1;
1255 // For single file mode, the number of pages in the target document so far, which is used
1256 // by AppendDoc() to adjust position of page-bound objects. Getting this information directly
1257 // from the target doc would require repeated layouts of the doc, which is expensive, but
1258 // it can be manually computed from the source documents (for which we do layouts, so the page
1259 // count is known, and there is a blank page between each of them in the target document).
1260 int targetDocPageCount = 0;
1262 if( !bIsMergeSilent && !bMT_PRINTER )
1264 sal_Int32 nRecordCount = 1;
1265 lcl_getCountFromResultSet( nRecordCount, m_pImpl->pMergeData.get() );
1267 // Synchronized docs don't auto-advance the record set, but there is a
1268 // "security" check, which will always advance the record set, if there
1269 // is no "next record" field in a synchronized doc => nRecordPerDoc > 0
1270 sal_Int32 nRecordPerDoc = pSourceShell->GetDoc()
1271 ->getIDocumentFieldsAccess().GetRecordsPerDocument();
1272 if ( bSynchronizedDoc && (nRecordPerDoc > 1) )
1273 --nRecordPerDoc;
1274 assert( nRecordPerDoc > 0 );
1276 sal_Int32 nMaxDocs = nRecordCount / nRecordPerDoc;
1277 if ( 0 != nRecordCount % nRecordPerDoc )
1278 nMaxDocs += 1;
1279 static_cast<CreateMonitor*>(xProgressDlg.get())->SetTotalCount(nMaxDocs);
1282 sal_Int32 nStartRow, nEndRow;
1283 bool bFreezedLayouts = false;
1284 // to collect temporary email files
1285 std::vector< OUString> aFilesToRemove;
1287 // The SfxObjectShell will be closed explicitly later but
1288 // it is more safe to use SfxObjectShellLock here
1289 SfxObjectShellLock xWorkDocSh;
1290 SwView* pWorkView = nullptr;
1291 rtl::Reference<SwDoc> pWorkDoc;
1292 SwDBManager* pWorkDocOrigDBManager = nullptr;
1293 SwWrtShell* pWorkShell = nullptr;
1294 bool bWorkDocInitialized = false;
1298 nStartRow = m_pImpl->pMergeData ? m_pImpl->pMergeData->xResultSet->getRow() : 0;
1300 OUString sColumnData;
1302 // Read the indicated data column, which should contain a valid mail
1303 // address or an optional file name
1304 if( bMT_EMAIL || bColumnName )
1306 sColumnData = GetDBField( xColumnProp, aColumnDBFormat );
1309 // create a new temporary file name - only done once in case of bCreateSingleFile
1310 if( bNeedsTempFiles && ( !bWorkDocInitialized || !bCreateSingleFile ))
1312 OUString sPrefix = sDescriptorPrefix;
1313 OUString sLeading;
1315 //#i97667# if the name is from a database field then it will be used _as is_
1316 if( bColumnName && !bMT_EMAIL )
1318 if (!sColumnData.isEmpty())
1319 sLeading = sColumnData;
1320 else
1321 sLeading = "_";
1323 else
1325 INetURLObject aEntry( sPrefix );
1326 sLeading = aEntry.GetBase();
1327 aEntry.removeSegment();
1328 sPrefix = aEntry.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1331 OUString sExt(comphelper::string::stripStart(pStoreToFilter->GetDefaultExtension(), '*'));
1332 aTempFile.reset( new utl::TempFileNamed(sLeading, sColumnData.isEmpty(), sExt, &sPrefix, true) );
1333 if( !aTempFile->IsValid() )
1335 ErrorHandler::HandleError( ERRCODE_IO_NOTSUPPORTED );
1336 m_aMergeStatus = MergeStatus::Error;
1340 uno::Sequence< beans::PropertyValue > aSaveToFilterDataOptions( rMergeDescriptor.aSaveToFilterData );
1342 if( bMT_EMAIL || bPasswordColumnName )
1344 OUString sPasswordColumnData = GetDBField( xPasswordColumnProp, aColumnDBFormat );
1345 lcl_PrepareSaveFilterDataOptions( rMergeDescriptor.aSaveToFilterData, aSaveToFilterDataOptions, sPasswordColumnData );
1348 if( IsMergeOk() )
1350 std::unique_ptr< INetURLObject > aTempFileURL;
1351 if( bNeedsTempFiles )
1352 aTempFileURL.reset( new INetURLObject(aTempFile->GetURL()));
1353 if( !bIsMergeSilent ) {
1354 if( !bMT_PRINTER )
1355 static_cast<CreateMonitor*>(xProgressDlg.get())->SetCurrentPosition(nDocNo);
1356 else {
1357 PrintMonitor *pPrintMonDlg = static_cast<PrintMonitor*>(xProgressDlg.get());
1358 pPrintMonDlg->m_xPrinter->set_label(bNeedsTempFiles
1359 ? aTempFileURL->GetBase() : pSourceDocSh->GetTitle( 2));
1360 OUString sStat = SwResId(STR_STATSTR_LETTER) + " " + OUString::number( nDocNo );
1361 pPrintMonDlg->m_xPrintInfo->set_label(sStat);
1363 //TODO xProgressDlg->queue_draw();
1366 Scheduler::ProcessEventsToIdle();
1368 // Create a copy of the source document and work with that one instead of the source.
1369 // If we're not in the single file mode (which requires modifying the document for the merging),
1370 // it is enough to do this just once. Currently PDF also has to be treated special.
1371 if( !bWorkDocInitialized || bCreateSingleFile || bIsPDFexport || bIsMultiFile )
1373 assert( !xWorkDocSh.Is());
1374 pWorkDocOrigDBManager = this;
1375 xWorkDocSh = lcl_CreateWorkingDocument( WorkingDocType::COPY,
1376 *pSourceShell, nullptr, &pWorkDocOrigDBManager,
1377 &pWorkView, &pWorkShell, &pWorkDoc );
1378 if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) )
1379 lcl_SaveDebugDoc( xWorkDocSh, "WorkDoc", nDocNo );
1381 // #i69458# lock fields to prevent access to the result set while calculating layout
1382 // tdf#92324: and do not unlock: keep document locked during printing to avoid
1383 // ExpFields update during printing, generation of preview, etc.
1384 pWorkShell->LockExpFields();
1385 pWorkShell->CalcLayout();
1386 // tdf#121168: Now force correct page descriptions applied to page frames. Without
1387 // this, e.g., page frames starting with sections could have page descriptions set
1388 // wrong. This would lead to wrong page styles applied in SwDoc::AppendDoc below.
1389 pWorkShell->GetViewOptions()->SetIdle(true);
1390 for (auto aLayout : pWorkShell->GetDoc()->GetAllLayouts())
1392 aLayout->FreezeLayout(false);
1393 aLayout->AllCheckPageDescs();
1397 lcl_emitEvent(SfxEventHintId::SwEventFieldMerge, STR_SW_EVENT_FIELD_MERGE, xWorkDocSh);
1399 // tdf#92324: Allow ExpFields update only by explicit instruction to avoid
1400 // database cursor movement on any other fields update, for example during
1401 // print preview and other operations
1402 if ( pWorkShell->IsExpFieldsLocked() )
1403 pWorkShell->UnlockExpFields();
1404 pWorkShell->SwViewShell::UpdateFields();
1405 pWorkShell->LockExpFields();
1407 lcl_emitEvent(SfxEventHintId::SwEventFieldMergeFinished, STR_SW_EVENT_FIELD_MERGE_FINISHED, xWorkDocSh);
1409 // also emit MailMergeEvent on XInterface if possible
1410 const SwXMailMerge *pEvtSrc = GetMailMergeEvtSrc();
1411 if(pEvtSrc)
1413 rtl::Reference< SwXMailMerge > xRef(
1414 const_cast<SwXMailMerge*>(pEvtSrc) );
1415 text::MailMergeEvent aEvt( static_cast<text::XMailMergeBroadcaster*>(xRef.get()), xWorkDocSh->GetModel() );
1416 SolarMutexReleaser rel;
1417 xRef->LaunchMailMergeEvent( aEvt );
1420 // working copy is merged - prepare final steps depending on merge options
1422 if( bCreateSingleFile )
1424 assert( pTargetShell && "no target shell available!" );
1426 // prepare working copy and target to append
1428 pWorkDoc->RemoveInvisibleContent();
1429 // remove of invisible content has influence on page count and so on fields for page count,
1430 // therefore layout has to be updated before fields are converted to text
1431 pWorkShell->CalcLayout();
1432 pWorkShell->ConvertFieldsToText();
1433 pWorkShell->SetNumberingRestart();
1434 if( bSynchronizedDoc )
1436 lcl_RemoveSectionLinks( *pWorkShell );
1439 if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) )
1440 lcl_SaveDebugDoc( xWorkDocSh, "WorkDoc", nDocNo );
1442 // append the working document to the target document
1443 if( targetDocPageCount % 2 == 1 )
1444 ++targetDocPageCount; // Docs always start on odd pages (so offset must be even).
1445 SwNodeIndex appendedDocStart = pTargetDoc->AppendDoc( *pWorkDoc,
1446 nStartingPageNo, !bWorkDocInitialized, targetDocPageCount, nDocNo);
1447 targetDocPageCount += pWorkShell->GetPageCnt();
1449 if ( (nMaxDumpDocs < 0) || (nDocNo <= nMaxDumpDocs) )
1450 lcl_SaveDebugDoc( xTargetDocShell.get(), "MergeDoc" );
1452 if (bMT_SHELL)
1454 SwDocMergeInfo aMergeInfo;
1455 // Name of the mark is actually irrelevant, UNO bookmarks have internals names.
1456 aMergeInfo.startPageInTarget = pTargetDoc->getIDocumentMarkAccess()->makeMark(
1457 SwPaM(appendedDocStart), "", IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
1458 ::sw::mark::InsertMode::New);
1459 aMergeInfo.nDBRow = nStartRow;
1460 rMergeDescriptor.pMailMergeConfigItem->AddMergedDocument( aMergeInfo );
1463 else
1465 assert( bNeedsTempFiles );
1466 assert( pWorkShell->IsExpFieldsLocked() );
1468 if (bIsMultiFile && pWorkDoc->HasInvisibleContent())
1470 pWorkDoc->RemoveInvisibleContent();
1471 pWorkShell->CalcLayout();
1472 pWorkShell->ConvertFieldsToText();
1475 // fields are locked, so it's fine to
1476 // restore the old / empty DB manager for save
1477 pWorkDoc->SetDBManager( pWorkDocOrigDBManager );
1479 // save merged document
1480 OUString sFileURL;
1481 if( !lcl_SaveDoc( aTempFileURL.get(), pStoreToFilter, pStoreToFilterOptions,
1482 &aSaveToFilterDataOptions, bIsPDFexport,
1483 xWorkDocSh, *pWorkShell, &sFileURL ) )
1485 m_aMergeStatus = MergeStatus::Error;
1488 // back to the MM DB manager
1489 pWorkDoc->SetDBManager( this );
1491 if( bMT_EMAIL && !IsMergeError() )
1493 // schedule file for later removal
1494 aFilesToRemove.push_back( sFileURL );
1496 if( !SwMailMergeHelper::CheckMailAddress( sColumnData ) )
1498 OSL_FAIL("invalid e-Mail address in database column");
1500 else
1502 uno::Reference< mail::XMailMessage > xMessage = lcl_CreateMailFromDoc(
1503 rMergeDescriptor, sFileURL, sColumnData, sMailBodyMimeType,
1504 sMailEncoding, pStoreToFilter->GetMimeType() );
1505 if( xMessage.is() )
1507 std::unique_lock aGuard( m_pImpl->m_aAllEmailSendMutex );
1508 m_pImpl->m_xLastMessage.set( xMessage );
1509 xMailDispatcher->enqueueMailMessage( xMessage );
1510 if( !xMailDispatcher->isStarted() )
1511 xMailDispatcher->start();
1516 if( bCreateSingleFile || bIsPDFexport || bIsMultiFile)
1518 pWorkDoc->SetDBManager( pWorkDocOrigDBManager );
1519 pWorkDoc.clear();
1520 xWorkDocSh->DoClose();
1521 xWorkDocSh = nullptr;
1525 bWorkDocInitialized = true;
1526 nDocNo++;
1527 nEndRow = m_pImpl->pMergeData ? m_pImpl->pMergeData->xResultSet->getRow() : 0;
1529 // Freeze the layouts of the target document after the first inserted
1530 // sub-document, to get the correct PageDesc.
1531 if(!bFreezedLayouts && bCreateSingleFile)
1533 for ( auto aLayout : pTargetShell->GetDoc()->GetAllLayouts() )
1534 aLayout->FreezeLayout(true);
1535 bFreezedLayouts = true;
1537 } while( IsMergeOk() &&
1538 ((bSynchronizedDoc && (nStartRow != nEndRow)) ? IsValidMergeRecord() : ToNextMergeRecord()));
1540 if ( xWorkDocSh.Is() && pWorkView->GetWrtShell().IsExpFieldsLocked() )
1542 // Unlock document fields after merge complete
1543 pWorkView->GetWrtShell().UnlockExpFields();
1546 if( !bCreateSingleFile )
1548 if( bMT_PRINTER )
1549 Printer::FinishPrintJob( pWorkView->GetPrinterController());
1550 if( !bIsPDFexport )
1552 if (pWorkDoc)
1553 pWorkDoc->SetDBManager(pWorkDocOrigDBManager);
1554 if (xWorkDocSh.Is())
1555 xWorkDocSh->DoClose();
1558 else if( IsMergeOk() ) // && bCreateSingleFile
1560 Application::Reschedule( true );
1562 // sw::DocumentLayoutManager::CopyLayoutFormat() did not generate
1563 // unique fly names, do it here once.
1564 pTargetDoc->SetInMailMerge(false);
1565 pTargetDoc->SetAllUniqueFlyNames();
1567 // Unfreeze target document layouts and correct all PageDescs.
1568 SAL_INFO( "sw.pageframe", "(MergeMailFiles pTargetShell->CalcLayout in" );
1569 pTargetShell->CalcLayout();
1570 SAL_INFO( "sw.pageframe", "MergeMailFiles pTargetShell->CalcLayout out)" );
1571 pTargetShell->GetViewOptions()->SetIdle( true );
1572 pTargetDoc->GetIDocumentUndoRedo().DoUndo( true );
1573 for ( auto aLayout : pTargetShell->GetDoc()->GetAllLayouts() )
1575 aLayout->FreezeLayout(false);
1576 aLayout->AllCheckPageDescs();
1579 Application::Reschedule( true );
1581 if( IsMergeOk() && bMT_FILE )
1583 // save merged document
1584 assert( aTempFile );
1585 INetURLObject aTempFileURL;
1586 if (sDescriptorPrefix.isEmpty() || !rMergeDescriptor.bPrefixIsFilename)
1587 aTempFileURL.SetURL( aTempFile->GetURL() );
1588 else
1590 aTempFileURL.SetURL(sDescriptorPrefix);
1591 // remove the unneeded temporary file
1592 aTempFile->EnableKillingFile();
1594 if( !lcl_SaveDoc( &aTempFileURL, pStoreToFilter,
1595 pStoreToFilterOptions, &rMergeDescriptor.aSaveToFilterData,
1596 bIsPDFexport, xTargetDocShell.get(), *pTargetShell ) )
1598 m_aMergeStatus = MergeStatus::Error;
1601 else if( IsMergeOk() && bMT_PRINTER )
1603 // print the target document
1604 uno::Sequence< beans::PropertyValue > aOptions( rMergeDescriptor.aPrintOptions );
1605 lcl_PreparePrinterOptions( rMergeDescriptor.aPrintOptions, aOptions );
1606 pTargetView->ExecPrint( aOptions, bIsMergeSilent, false/*bPrintAsync*/ );
1610 // we also show canceled documents, as long as there was no error
1611 if( !IsMergeError() && bMT_SHELL )
1612 // leave docshell available for caller (e.g. MM wizard)
1613 rMergeDescriptor.pMailMergeConfigItem->SetTargetView( pTargetView );
1614 else if( xTargetDocShell.is() )
1615 xTargetDocShell->DoClose();
1617 Application::Reschedule( true );
1619 if (xProgressDlg)
1621 xProgressDlg->response(RET_OK);
1624 // unlock all dispatchers
1625 pViewFrame = SfxViewFrame::GetFirst(pSourceDocSh);
1626 while (pViewFrame)
1628 pViewFrame->GetDispatcher()->Lock(false);
1629 pViewFrame = SfxViewFrame::GetNext(*pViewFrame, pSourceDocSh);
1632 SW_MOD()->SetView(&pSourceShell->GetView());
1634 if( xMailDispatcher.is() )
1636 if( IsMergeOk() )
1638 // TODO: Instead of polling via an AutoTimer, post an Idle event,
1639 // if the main loop has been made thread-safe.
1640 AutoTimer aEmailDispatcherPollTimer("sw::SwDBManager aEmailDispatcherPollTimer");
1641 aEmailDispatcherPollTimer.SetTimeout( 500 );
1642 aEmailDispatcherPollTimer.Start();
1643 while( IsMergeOk() && m_pImpl->m_xLastMessage.is() && !Application::IsQuit())
1644 Application::Yield();
1645 aEmailDispatcherPollTimer.Stop();
1647 xMailDispatcher->stop();
1648 xMailDispatcher->shutdown();
1651 // remove the temporary files
1652 // has to be done after xMailDispatcher is finished, as mails may be
1653 // delivered as message attachments!
1654 for( const OUString &sFileURL : aFilesToRemove )
1655 SWUnoHelper::UCB_DeleteFile( sFileURL );
1657 catch (const uno::Exception&)
1659 if (xProgressDlg)
1661 xProgressDlg->response(RET_CANCEL);
1665 return !IsMergeError();
1668 void SwDBManager::MergeCancel()
1670 if (m_aMergeStatus < MergeStatus::Cancel)
1671 m_aMergeStatus = MergeStatus::Cancel;
1674 // determine the column's Numberformat and transfer to the forwarded Formatter,
1675 // if applicable.
1676 sal_uLong SwDBManager::GetColumnFormat( const OUString& rDBName,
1677 const OUString& rTableName,
1678 const OUString& rColNm,
1679 SvNumberFormatter* pNFormatr,
1680 LanguageType nLanguage )
1682 sal_uLong nRet = 0;
1683 if(pNFormatr)
1685 uno::Reference< sdbc::XDataSource> xSource;
1686 uno::Reference< sdbc::XConnection> xConnection;
1687 bool bUseMergeData = false;
1688 uno::Reference< sdbcx::XColumnsSupplier> xColsSupp;
1689 bool bDisposeConnection = false;
1690 if(m_pImpl->pMergeData &&
1691 ((m_pImpl->pMergeData->sDataSource == rDBName && m_pImpl->pMergeData->sCommand == rTableName) ||
1692 (rDBName.isEmpty() && rTableName.isEmpty())))
1694 xConnection = m_pImpl->pMergeData->xConnection;
1695 xSource = SwDBManager::getDataSourceAsParent(xConnection,rDBName);
1696 bUseMergeData = true;
1697 xColsSupp.set(m_pImpl->pMergeData->xResultSet, css::uno::UNO_QUERY);
1699 if(!xConnection.is())
1701 SwDBData aData;
1702 aData.sDataSource = rDBName;
1703 aData.sCommand = rTableName;
1704 aData.nCommandType = -1;
1705 SwDSParam* pParam = FindDSData(aData, false);
1706 if(pParam && pParam->xConnection.is())
1708 xConnection = pParam->xConnection;
1709 xColsSupp.set(pParam->xResultSet, css::uno::UNO_QUERY);
1711 else
1713 xConnection = RegisterConnection( rDBName );
1714 bDisposeConnection = true;
1716 if(bUseMergeData)
1717 m_pImpl->pMergeData->xConnection = xConnection;
1719 bool bDispose = !xColsSupp.is();
1720 if(bDispose)
1722 xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName);
1724 if(xColsSupp.is())
1726 uno::Reference<container::XNameAccess> xCols;
1729 xCols = xColsSupp->getColumns();
1731 catch (const uno::Exception&)
1733 TOOLS_WARN_EXCEPTION("sw.mailmerge", "Exception in getColumns()");
1735 if(!xCols.is() || !xCols->hasByName(rColNm))
1736 return nRet;
1737 uno::Any aCol = xCols->getByName(rColNm);
1738 uno::Reference< beans::XPropertySet > xColumn;
1739 aCol >>= xColumn;
1740 nRet = GetColumnFormat(xSource, xConnection, xColumn, pNFormatr, nLanguage);
1741 if(bDispose)
1743 ::comphelper::disposeComponent( xColsSupp );
1745 if(bDisposeConnection)
1747 ::comphelper::disposeComponent( xConnection );
1750 else
1751 nRet = pNFormatr->GetFormatIndex( NF_NUMBER_STANDARD, LANGUAGE_SYSTEM );
1753 return nRet;
1756 sal_uLong SwDBManager::GetColumnFormat( uno::Reference< sdbc::XDataSource> const & xSource_in,
1757 uno::Reference< sdbc::XConnection> const & xConnection,
1758 uno::Reference< beans::XPropertySet> const & xColumn,
1759 SvNumberFormatter* pNFormatr,
1760 LanguageType nLanguage )
1762 auto xSource = xSource_in;
1764 // set the NumberFormat in the doc if applicable
1765 sal_uLong nRet = 0;
1767 if(!xSource.is())
1769 uno::Reference<container::XChild> xChild(xConnection, uno::UNO_QUERY);
1770 if ( xChild.is() )
1771 xSource.set(xChild->getParent(), uno::UNO_QUERY);
1773 if(xSource.is() && xConnection.is() && xColumn.is() && pNFormatr)
1775 rtl::Reference<SvNumberFormatsSupplierObj> pNumFormat = new SvNumberFormatsSupplierObj( pNFormatr );
1776 uno::Reference< util::XNumberFormats > xDocNumberFormats = pNumFormat->getNumberFormats();
1777 uno::Reference< util::XNumberFormatTypes > xDocNumberFormatTypes(xDocNumberFormats, uno::UNO_QUERY);
1779 css::lang::Locale aLocale( LanguageTag( nLanguage ).getLocale());
1781 //get the number formatter of the data source
1782 uno::Reference<beans::XPropertySet> xSourceProps(xSource, uno::UNO_QUERY);
1783 uno::Reference< util::XNumberFormats > xNumberFormats;
1784 if(xSourceProps.is())
1786 uno::Any aFormats = xSourceProps->getPropertyValue("NumberFormatsSupplier");
1787 if(aFormats.hasValue())
1789 uno::Reference<util::XNumberFormatsSupplier> xSuppl;
1790 aFormats >>= xSuppl;
1791 if(xSuppl.is())
1793 xNumberFormats = xSuppl->getNumberFormats();
1797 bool bUseDefault = true;
1800 uno::Any aFormatKey = xColumn->getPropertyValue("FormatKey");
1801 if(aFormatKey.hasValue())
1803 sal_Int32 nFormat = 0;
1804 aFormatKey >>= nFormat;
1805 if(xNumberFormats.is())
1809 uno::Reference<beans::XPropertySet> xNumProps = xNumberFormats->getByKey( nFormat );
1810 uno::Any aFormatString = xNumProps->getPropertyValue("FormatString");
1811 uno::Any aLocaleVal = xNumProps->getPropertyValue("Locale");
1812 OUString sFormat;
1813 aFormatString >>= sFormat;
1814 lang::Locale aLoc;
1815 aLocaleVal >>= aLoc;
1816 nFormat = xDocNumberFormats->queryKey( sFormat, aLoc, false );
1817 if(NUMBERFORMAT_ENTRY_NOT_FOUND == sal::static_int_cast< sal_uInt32, sal_Int32>(nFormat))
1818 nFormat = xDocNumberFormats->addNew( sFormat, aLoc );
1819 nRet = nFormat;
1820 bUseDefault = false;
1822 catch (const uno::Exception&)
1824 TOOLS_WARN_EXCEPTION("sw.mailmerge", "illegal number format key");
1829 catch(const uno::Exception&)
1831 SAL_WARN("sw.mailmerge", "no FormatKey property found");
1833 if(bUseDefault)
1834 nRet = dbtools::getDefaultNumberFormat(xColumn, xDocNumberFormatTypes, aLocale);
1836 return nRet;
1839 sal_Int32 SwDBManager::GetColumnType( const OUString& rDBName,
1840 const OUString& rTableName,
1841 const OUString& rColNm )
1843 sal_Int32 nRet = sdbc::DataType::SQLNULL;
1844 SwDBData aData;
1845 aData.sDataSource = rDBName;
1846 aData.sCommand = rTableName;
1847 aData.nCommandType = -1;
1848 SwDSParam* pParam = FindDSData(aData, false);
1849 uno::Reference< sdbc::XConnection> xConnection;
1850 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp;
1851 bool bDispose = false;
1852 if(pParam && pParam->xConnection.is())
1854 xConnection = pParam->xConnection;
1855 xColsSupp.set( pParam->xResultSet, uno::UNO_QUERY );
1857 else
1859 xConnection = RegisterConnection( rDBName );
1861 if( !xColsSupp.is() )
1863 xColsSupp = SwDBManager::GetColumnSupplier(xConnection, rTableName);
1864 bDispose = true;
1866 if(xColsSupp.is())
1868 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
1869 if(xCols->hasByName(rColNm))
1871 uno::Any aCol = xCols->getByName(rColNm);
1872 uno::Reference<beans::XPropertySet> xCol;
1873 aCol >>= xCol;
1874 uno::Any aType = xCol->getPropertyValue("Type");
1875 aType >>= nRet;
1877 if(bDispose)
1878 ::comphelper::disposeComponent( xColsSupp );
1880 return nRet;
1883 uno::Reference< sdbc::XConnection> SwDBManager::GetConnection(const OUString& rDataSource,
1884 uno::Reference<sdbc::XDataSource>& rxSource, const SwView *pView)
1886 uno::Reference< sdbc::XConnection> xConnection;
1887 uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
1890 uno::Reference<sdb::XCompletedConnection> xComplConnection(dbtools::getDataSource(rDataSource, xContext), uno::UNO_QUERY);
1891 if ( xComplConnection.is() )
1893 rxSource.set(xComplConnection, uno::UNO_QUERY);
1894 weld::Window* pWindow = pView ? pView->GetFrameWeld() : nullptr;
1895 uno::Reference< task::XInteractionHandler > xHandler( task::InteractionHandler::createWithParent(xContext, pWindow ? pWindow->GetXWindow() : nullptr), uno::UNO_QUERY_THROW );
1896 xConnection = xComplConnection->connectWithCompletion( xHandler );
1899 catch(const uno::Exception&)
1903 return xConnection;
1906 uno::Reference< sdbcx::XColumnsSupplier> SwDBManager::GetColumnSupplier(uno::Reference<sdbc::XConnection> const & xConnection,
1907 const OUString& rTableOrQuery,
1908 SwDBSelect eTableOrQuery)
1910 uno::Reference< sdbcx::XColumnsSupplier> xRet;
1913 if(eTableOrQuery == SwDBSelect::UNKNOWN)
1915 //search for a table with the given command name
1916 uno::Reference<sdbcx::XTablesSupplier> xTSupplier(xConnection, uno::UNO_QUERY);
1917 if(xTSupplier.is())
1919 uno::Reference<container::XNameAccess> xTables = xTSupplier->getTables();
1920 eTableOrQuery = xTables->hasByName(rTableOrQuery) ?
1921 SwDBSelect::TABLE : SwDBSelect::QUERY;
1924 sal_Int32 nCommandType = SwDBSelect::TABLE == eTableOrQuery ?
1925 sdb::CommandType::TABLE : sdb::CommandType::QUERY;
1926 uno::Reference< lang::XMultiServiceFactory > xMgr( ::comphelper::getProcessServiceFactory() );
1927 uno::Reference<sdbc::XRowSet> xRowSet(xMgr->createInstance("com.sun.star.sdb.RowSet"), uno::UNO_QUERY);
1929 OUString sDataSource;
1930 uno::Reference<sdbc::XDataSource> xSource = SwDBManager::getDataSourceAsParent(xConnection, sDataSource);
1931 uno::Reference<beans::XPropertySet> xSourceProperties(xSource, uno::UNO_QUERY);
1932 if(xSourceProperties.is())
1934 xSourceProperties->getPropertyValue("Name") >>= sDataSource;
1937 uno::Reference<beans::XPropertySet> xRowProperties(xRowSet, uno::UNO_QUERY);
1938 xRowProperties->setPropertyValue("DataSourceName", uno::Any(sDataSource));
1939 xRowProperties->setPropertyValue("Command", uno::Any(rTableOrQuery));
1940 xRowProperties->setPropertyValue("CommandType", uno::Any(nCommandType));
1941 xRowProperties->setPropertyValue("FetchSize", uno::Any(sal_Int32(10)));
1942 xRowProperties->setPropertyValue("ActiveConnection", uno::Any(xConnection));
1943 xRowSet->execute();
1944 xRet.set( xRowSet, uno::UNO_QUERY );
1946 catch (const uno::Exception&)
1948 TOOLS_WARN_EXCEPTION("sw.mailmerge", "Exception in SwDBManager::GetColumnSupplier");
1951 return xRet;
1954 OUString SwDBManager::GetDBField(uno::Reference<beans::XPropertySet> const & xColumnProps,
1955 const SwDBFormatData& rDBFormatData,
1956 double* pNumber)
1958 uno::Reference< sdb::XColumn > xColumn(xColumnProps, uno::UNO_QUERY);
1959 OUString sRet;
1960 assert( xColumn.is() && "SwDBManager::::ImportDBField: illegal arguments" );
1961 if(!xColumn.is())
1962 return sRet;
1964 uno::Any aType = xColumnProps->getPropertyValue("Type");
1965 sal_Int32 eDataType = sdbc::DataType::SQLNULL;
1966 aType >>= eDataType;
1967 switch(eDataType)
1969 case sdbc::DataType::CHAR:
1970 case sdbc::DataType::VARCHAR:
1971 case sdbc::DataType::LONGVARCHAR:
1974 sRet = xColumn->getString();
1975 sRet = sRet.replace( '\xb', '\n' ); // MSWord uses \xb as a newline
1977 catch(const sdbc::SQLException&)
1980 break;
1981 case sdbc::DataType::BIT:
1982 case sdbc::DataType::BOOLEAN:
1983 case sdbc::DataType::TINYINT:
1984 case sdbc::DataType::SMALLINT:
1985 case sdbc::DataType::INTEGER:
1986 case sdbc::DataType::BIGINT:
1987 case sdbc::DataType::FLOAT:
1988 case sdbc::DataType::REAL:
1989 case sdbc::DataType::DOUBLE:
1990 case sdbc::DataType::NUMERIC:
1991 case sdbc::DataType::DECIMAL:
1992 case sdbc::DataType::DATE:
1993 case sdbc::DataType::TIME:
1994 case sdbc::DataType::TIMESTAMP:
1999 sRet = dbtools::DBTypeConversion::getFormattedValue(
2000 xColumnProps,
2001 rDBFormatData.xFormatter,
2002 rDBFormatData.aLocale,
2003 rDBFormatData.aNullDate);
2004 if (pNumber)
2006 double fVal = xColumn->getDouble();
2007 if(!xColumn->wasNull())
2009 *pNumber = fVal;
2013 catch (const uno::Exception&)
2015 TOOLS_WARN_EXCEPTION("sw.mailmerge", "");
2019 break;
2022 return sRet;
2025 // checks if a desired data source table or query is open
2026 bool SwDBManager::IsDataSourceOpen(const OUString& rDataSource,
2027 const OUString& rTableOrQuery, bool bMergeShell)
2029 if(m_pImpl->pMergeData)
2031 return ((rDataSource == m_pImpl->pMergeData->sDataSource
2032 && rTableOrQuery == m_pImpl->pMergeData->sCommand)
2033 || (rDataSource.isEmpty() && rTableOrQuery.isEmpty()))
2034 && m_pImpl->pMergeData->xResultSet.is();
2036 else if(!bMergeShell)
2038 SwDBData aData;
2039 aData.sDataSource = rDataSource;
2040 aData.sCommand = rTableOrQuery;
2041 aData.nCommandType = -1;
2042 SwDSParam* pFound = FindDSData(aData, false);
2043 return (pFound && pFound->xResultSet.is());
2045 return false;
2048 // read column data at a specified position
2049 bool SwDBManager::GetColumnCnt(const OUString& rSourceName, const OUString& rTableName,
2050 const OUString& rColumnName, sal_uInt32 nAbsRecordId,
2051 LanguageType nLanguage,
2052 OUString& rResult, double* pNumber)
2054 bool bRet = false;
2055 SwDSParam* pFound = nullptr;
2056 //check if it's the merge data source
2057 if(m_pImpl->pMergeData &&
2058 rSourceName == m_pImpl->pMergeData->sDataSource &&
2059 rTableName == m_pImpl->pMergeData->sCommand)
2061 pFound = m_pImpl->pMergeData.get();
2063 else
2065 SwDBData aData;
2066 aData.sDataSource = rSourceName;
2067 aData.sCommand = rTableName;
2068 aData.nCommandType = -1;
2069 pFound = FindDSData(aData, false);
2071 if (!pFound)
2072 return false;
2073 //check validity of supplied record Id
2074 if(pFound->aSelection.hasElements())
2076 //the destination has to be an element of the selection
2077 bool bFound = std::any_of(std::cbegin(pFound->aSelection), std::cend(pFound->aSelection),
2078 [nAbsRecordId](const uno::Any& rSelection) {
2079 sal_Int32 nSelection = 0;
2080 rSelection >>= nSelection;
2081 return nSelection == static_cast<sal_Int32>(nAbsRecordId);
2083 if(!bFound)
2084 return false;
2086 if( pFound->HasValidRecord() )
2088 sal_Int32 nOldRow = 0;
2091 nOldRow = pFound->xResultSet->getRow();
2093 catch(const uno::Exception&)
2095 return false;
2097 //position to the desired index
2098 bool bMove = true;
2099 if ( nOldRow != static_cast<sal_Int32>(nAbsRecordId) )
2100 bMove = lcl_MoveAbsolute(pFound, nAbsRecordId);
2101 if(bMove)
2102 bRet = lcl_GetColumnCnt(pFound, rColumnName, nLanguage, rResult, pNumber);
2103 if ( nOldRow != static_cast<sal_Int32>(nAbsRecordId) )
2104 lcl_MoveAbsolute(pFound, nOldRow);
2106 return bRet;
2109 // reads the column data at the current position
2110 bool SwDBManager::GetMergeColumnCnt(const OUString& rColumnName, LanguageType nLanguage,
2111 OUString &rResult, double *pNumber)
2113 if( !IsValidMergeRecord() )
2115 rResult.clear();
2116 return false;
2119 bool bRet = lcl_GetColumnCnt(m_pImpl->pMergeData.get(), rColumnName, nLanguage, rResult, pNumber);
2120 return bRet;
2123 bool SwDBManager::ToNextMergeRecord()
2125 assert( m_pImpl->pMergeData && m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" );
2126 return lcl_ToNextRecord( m_pImpl->pMergeData.get() );
2129 bool SwDBManager::FillCalcWithMergeData( SvNumberFormatter *pDocFormatter,
2130 LanguageType nLanguage, SwCalc &rCalc )
2132 if( !IsValidMergeRecord() )
2133 return false;
2135 uno::Reference< sdbcx::XColumnsSupplier > xColsSupp( m_pImpl->pMergeData->xResultSet, uno::UNO_QUERY );
2136 if( !xColsSupp.is() )
2137 return false;
2140 uno::Reference<container::XNameAccess> xCols = xColsSupp->getColumns();
2141 const uno::Sequence<OUString> aColNames = xCols->getElementNames();
2142 OUString aString;
2144 // add the "record number" variable, as SwCalc::VarLook would.
2145 rCalc.VarChange( GetAppCharClass().lowercase(
2146 SwFieldType::GetTypeStr(SwFieldTypesEnum::DatabaseSetNumber) ), GetSelectedRecordId() );
2148 for( const OUString& rColName : aColNames )
2150 // get the column type
2151 sal_Int32 nColumnType = sdbc::DataType::SQLNULL;
2152 uno::Any aCol = xCols->getByName( rColName );
2153 uno::Reference<beans::XPropertySet> xColumnProps;
2154 aCol >>= xColumnProps;
2155 uno::Any aType = xColumnProps->getPropertyValue( "Type" );
2156 aType >>= nColumnType;
2157 double aNumber = DBL_MAX;
2159 lcl_GetColumnCnt( m_pImpl->pMergeData.get(), xColumnProps, nLanguage, aString, &aNumber );
2161 sal_uInt32 nFormat = GetColumnFormat( m_pImpl->pMergeData->sDataSource,
2162 m_pImpl->pMergeData->sCommand,
2163 rColName, pDocFormatter, nLanguage );
2164 // aNumber is overwritten by SwDBField::FormatValue, so store initial status
2165 bool colIsNumber = aNumber != DBL_MAX;
2166 bool bValidValue = SwDBField::FormatValue( pDocFormatter, aString, nFormat,
2167 aNumber, nColumnType );
2168 if( colIsNumber )
2170 if( bValidValue )
2172 SwSbxValue aValue;
2173 aValue.PutDouble( aNumber );
2174 aValue.SetDBvalue( true );
2175 SAL_INFO( "sw.ui", "'" << rColName << "': " << aNumber << " / " << aString );
2176 rCalc.VarChange( rColName, aValue );
2179 else
2181 SwSbxValue aValue;
2182 aValue.PutString( aString );
2183 aValue.SetDBvalue( true );
2184 SAL_INFO( "sw.ui", "'" << rColName << "': " << aString );
2185 rCalc.VarChange( rColName, aValue );
2190 return true;
2193 void SwDBManager::ToNextRecord(
2194 const OUString& rDataSource, const OUString& rCommand)
2196 SwDSParam* pFound = nullptr;
2197 if(m_pImpl->pMergeData &&
2198 rDataSource == m_pImpl->pMergeData->sDataSource &&
2199 rCommand == m_pImpl->pMergeData->sCommand)
2201 pFound = m_pImpl->pMergeData.get();
2203 else
2205 SwDBData aData;
2206 aData.sDataSource = rDataSource;
2207 aData.sCommand = rCommand;
2208 aData.nCommandType = -1;
2209 pFound = FindDSData(aData, false);
2211 lcl_ToNextRecord( pFound );
2214 static bool lcl_ToNextRecord( SwDSParam* pParam, const SwDBNextRecord action )
2216 bool bRet = true;
2218 assert( SwDBNextRecord::NEXT == action ||
2219 (SwDBNextRecord::FIRST == action && pParam) );
2220 if( nullptr == pParam )
2221 return false;
2223 if( action == SwDBNextRecord::FIRST )
2225 pParam->nSelectionIndex = 0;
2226 pParam->bEndOfDB = false;
2229 if( !pParam->HasValidRecord() )
2230 return false;
2234 if( pParam->aSelection.hasElements() )
2236 if( pParam->nSelectionIndex >= pParam->aSelection.getLength() )
2237 pParam->bEndOfDB = true;
2238 else
2240 sal_Int32 nPos = 0;
2241 pParam->aSelection.getConstArray()[ pParam->nSelectionIndex ] >>= nPos;
2242 pParam->bEndOfDB = !pParam->xResultSet->absolute( nPos );
2245 else if( action == SwDBNextRecord::FIRST )
2247 pParam->bEndOfDB = !pParam->xResultSet->first();
2249 else
2251 sal_Int32 nBefore = pParam->xResultSet->getRow();
2252 pParam->bEndOfDB = !pParam->xResultSet->next();
2253 if( !pParam->bEndOfDB && nBefore == pParam->xResultSet->getRow() )
2255 // next returned true but it didn't move
2256 ::dbtools::throwFunctionSequenceException( pParam->xResultSet );
2260 ++pParam->nSelectionIndex;
2261 bRet = !pParam->bEndOfDB;
2263 catch( const uno::Exception & )
2265 // we allow merging with empty databases, so don't warn on init
2266 TOOLS_WARN_EXCEPTION_IF(action == SwDBNextRecord::NEXT,
2267 "sw.mailmerge", "exception in ToNextRecord()");
2268 pParam->bEndOfDB = true;
2269 bRet = false;
2271 return bRet;
2274 // synchronized labels contain a next record field at their end
2275 // to assure that the next page can be created in mail merge
2276 // the cursor position must be validated
2277 bool SwDBManager::IsValidMergeRecord() const
2279 return( m_pImpl->pMergeData && m_pImpl->pMergeData->HasValidRecord() );
2282 sal_uInt32 SwDBManager::GetSelectedRecordId()
2284 sal_uInt32 nRet = 0;
2285 assert( m_pImpl->pMergeData &&
2286 m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" );
2287 if(!m_pImpl->pMergeData || !m_pImpl->pMergeData->xResultSet.is())
2288 return 0;
2291 nRet = m_pImpl->pMergeData->xResultSet->getRow();
2293 catch(const uno::Exception&)
2296 return nRet;
2299 bool SwDBManager::ToRecordId(sal_Int32 nSet)
2301 assert( m_pImpl->pMergeData &&
2302 m_pImpl->pMergeData->xResultSet.is() && "no data source in merge" );
2303 if(!m_pImpl->pMergeData || !m_pImpl->pMergeData->xResultSet.is()|| nSet < 0)
2304 return false;
2305 bool bRet = false;
2306 sal_Int32 nAbsPos = nSet;
2307 assert(nAbsPos >= 0);
2308 bRet = lcl_MoveAbsolute(m_pImpl->pMergeData.get(), nAbsPos);
2309 m_pImpl->pMergeData->bEndOfDB = !bRet;
2310 return bRet;
2313 bool SwDBManager::OpenDataSource(const OUString& rDataSource, const OUString& rTableOrQuery)
2315 SwDBData aData;
2316 aData.sDataSource = rDataSource;
2317 aData.sCommand = rTableOrQuery;
2318 aData.nCommandType = -1;
2320 SwDSParam* pFound = FindDSData(aData, true);
2321 if(pFound->xResultSet.is())
2322 return true;
2323 SwDSParam* pParam = FindDSConnection(rDataSource, false);
2324 if(pParam && pParam->xConnection.is())
2325 pFound->xConnection = pParam->xConnection;
2326 if(pFound->xConnection.is())
2330 uno::Reference< sdbc::XDatabaseMetaData > xMetaData = pFound->xConnection->getMetaData();
2333 pFound->bScrollable = xMetaData
2334 ->supportsResultSetType(sal_Int32(sdbc::ResultSetType::SCROLL_INSENSITIVE));
2336 catch(const uno::Exception&)
2338 // DB driver may not be ODBC 3.0 compliant
2339 pFound->bScrollable = true;
2341 pFound->xStatement = pFound->xConnection->createStatement();
2342 OUString aQuoteChar = xMetaData->getIdentifierQuoteString();
2343 OUString sStatement = "SELECT * FROM " + aQuoteChar + rTableOrQuery + aQuoteChar;
2344 pFound->xResultSet = pFound->xStatement->executeQuery( sStatement );
2346 //after executeQuery the cursor must be positioned
2347 pFound->bEndOfDB = !pFound->xResultSet->next();
2348 ++pFound->nSelectionIndex;
2350 catch (const uno::Exception&)
2352 pFound->xResultSet = nullptr;
2353 pFound->xStatement = nullptr;
2354 pFound->xConnection = nullptr;
2357 return pFound->xResultSet.is();
2360 uno::Reference< sdbc::XConnection> const & SwDBManager::RegisterConnection(OUString const& rDataSource)
2362 SwDSParam* pFound = SwDBManager::FindDSConnection(rDataSource, true);
2363 uno::Reference< sdbc::XDataSource> xSource;
2364 if(!pFound->xConnection.is())
2366 SwView* pView = (m_pDoc && m_pDoc->GetDocShell()) ? m_pDoc->GetDocShell()->GetView() : nullptr;
2367 pFound->xConnection = SwDBManager::GetConnection(rDataSource, xSource, pView);
2370 uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY);
2371 if(xComponent.is())
2372 xComponent->addEventListener(m_pImpl->m_xDisposeListener);
2374 catch(const uno::Exception&)
2378 return pFound->xConnection;
2381 sal_uInt32 SwDBManager::GetSelectedRecordId(
2382 const OUString& rDataSource, const OUString& rTableOrQuery, sal_Int32 nCommandType)
2384 sal_uInt32 nRet = 0xffffffff;
2385 //check for merge data source first
2386 if(m_pImpl->pMergeData &&
2387 ((rDataSource == m_pImpl->pMergeData->sDataSource &&
2388 rTableOrQuery == m_pImpl->pMergeData->sCommand) ||
2389 (rDataSource.isEmpty() && rTableOrQuery.isEmpty())) &&
2390 (nCommandType == -1 || nCommandType == m_pImpl->pMergeData->nCommandType) &&
2391 m_pImpl->pMergeData->xResultSet.is())
2393 nRet = GetSelectedRecordId();
2395 else
2397 SwDBData aData;
2398 aData.sDataSource = rDataSource;
2399 aData.sCommand = rTableOrQuery;
2400 aData.nCommandType = nCommandType;
2401 SwDSParam* pFound = FindDSData(aData, false);
2402 if(pFound && pFound->xResultSet.is())
2405 { //if a selection array is set the current row at the result set may not be set yet
2406 if(pFound->aSelection.hasElements())
2408 sal_Int32 nSelIndex = pFound->nSelectionIndex;
2409 if(nSelIndex >= pFound->aSelection.getLength())
2410 nSelIndex = pFound->aSelection.getLength() -1;
2411 pFound->aSelection.getConstArray()[nSelIndex] >>= nRet;
2414 else
2415 nRet = pFound->xResultSet->getRow();
2417 catch(const uno::Exception&)
2422 return nRet;
2425 // close all data sources - after fields were updated
2426 void SwDBManager::CloseAll(bool bIncludingMerge)
2428 //the only thing done here is to reset the selection index
2429 //all connections stay open
2430 for (auto & pParam : m_DataSourceParams)
2432 if (bIncludingMerge || pParam.get() != m_pImpl->pMergeData.get())
2434 pParam->nSelectionIndex = 0;
2435 pParam->bEndOfDB = false;
2438 if(!m_bInMerge && pParam->xResultSet.is())
2439 pParam->xResultSet->first();
2441 catch(const uno::Exception&)
2447 SwDSParam* SwDBManager::FindDSData(const SwDBData& rData, bool bCreate)
2449 //prefer merge data if available
2450 if(m_pImpl->pMergeData &&
2451 ((rData.sDataSource == m_pImpl->pMergeData->sDataSource &&
2452 rData.sCommand == m_pImpl->pMergeData->sCommand) ||
2453 (rData.sDataSource.isEmpty() && rData.sCommand.isEmpty())) &&
2454 (rData.nCommandType == -1 || rData.nCommandType == m_pImpl->pMergeData->nCommandType ||
2455 (bCreate && m_pImpl->pMergeData->nCommandType == -1)))
2457 return m_pImpl->pMergeData.get();
2460 SwDSParam* pFound = nullptr;
2461 for (size_t nPos = m_DataSourceParams.size(); nPos; nPos--)
2463 SwDSParam* pParam = m_DataSourceParams[nPos - 1].get();
2464 if(rData.sDataSource == pParam->sDataSource &&
2465 rData.sCommand == pParam->sCommand &&
2466 (rData.nCommandType == -1 || rData.nCommandType == pParam->nCommandType ||
2467 (bCreate && pParam->nCommandType == -1)))
2469 // calls from the calculator may add a connection with an invalid commandtype
2470 //later added "real" data base connections have to re-use the already available
2471 //DSData and set the correct CommandType
2472 if(bCreate && pParam->nCommandType == -1)
2473 pParam->nCommandType = rData.nCommandType;
2474 pFound = pParam;
2475 break;
2478 if(bCreate && !pFound)
2480 pFound = new SwDSParam(rData);
2481 m_DataSourceParams.push_back(std::unique_ptr<SwDSParam>(pFound));
2484 uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY);
2485 if(xComponent.is())
2486 xComponent->addEventListener(m_pImpl->m_xDisposeListener);
2488 catch(const uno::Exception&)
2492 return pFound;
2495 SwDSParam* SwDBManager::FindDSConnection(const OUString& rDataSource, bool bCreate)
2497 //prefer merge data if available
2498 if(m_pImpl->pMergeData && rDataSource == m_pImpl->pMergeData->sDataSource )
2500 SetAsUsed(rDataSource);
2501 return m_pImpl->pMergeData.get();
2503 SwDSParam* pFound = nullptr;
2504 for (const auto & pParam : m_DataSourceParams)
2506 if(rDataSource == pParam->sDataSource)
2508 SetAsUsed(rDataSource);
2509 pFound = pParam.get();
2510 break;
2513 if(bCreate && !pFound)
2515 SwDBData aData;
2516 aData.sDataSource = rDataSource;
2517 pFound = new SwDSParam(aData);
2518 SetAsUsed(rDataSource);
2519 m_DataSourceParams.push_back(std::unique_ptr<SwDSParam>(pFound));
2522 uno::Reference<lang::XComponent> xComponent(pFound->xConnection, uno::UNO_QUERY);
2523 if(xComponent.is())
2524 xComponent->addEventListener(m_pImpl->m_xDisposeListener);
2526 catch(const uno::Exception&)
2530 return pFound;
2533 const SwDBData& SwDBManager::GetAddressDBName()
2535 return SW_MOD()->GetDBConfig()->GetAddressSource();
2538 uno::Sequence<OUString> SwDBManager::GetExistingDatabaseNames()
2540 uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() );
2541 uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext);
2542 return xDBContext->getElementNames();
2545 namespace sw
2547 DBConnURIType GetDBunoType(const INetURLObject &rURL)
2549 OUString sExt(rURL.GetFileExtension());
2550 DBConnURIType type = DBConnURIType::UNKNOWN;
2552 if (sExt == "odb")
2554 type = DBConnURIType::ODB;
2556 else if (sExt.equalsIgnoreAsciiCase("sxc")
2557 || sExt.equalsIgnoreAsciiCase("ods")
2558 || sExt.equalsIgnoreAsciiCase("xls")
2559 || sExt.equalsIgnoreAsciiCase("xlsx"))
2561 type = DBConnURIType::CALC;
2563 else if (sExt.equalsIgnoreAsciiCase("sxw") || sExt.equalsIgnoreAsciiCase("odt") || sExt.equalsIgnoreAsciiCase("doc") || sExt.equalsIgnoreAsciiCase("docx"))
2565 type = DBConnURIType::WRITER;
2567 else if (sExt.equalsIgnoreAsciiCase("dbf"))
2569 type = DBConnURIType::DBASE;
2571 else if (sExt.equalsIgnoreAsciiCase("csv") || sExt.equalsIgnoreAsciiCase("txt"))
2573 type = DBConnURIType::FLAT;
2575 #ifdef _WIN32
2576 else if (sExt.equalsIgnoreAsciiCase("mdb") || sExt.equalsIgnoreAsciiCase("mde"))
2578 type = DBConnURIType::MSJET;
2580 else if (sExt.equalsIgnoreAsciiCase("accdb") || sExt.equalsIgnoreAsciiCase("accde"))
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::MSJET:
2638 #ifdef _WIN32
2640 OUString sDBURL("sdbc:ado:access:PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" + rURL.PathToFileName());
2641 aURLAny <<= sDBURL;
2643 #endif
2644 break;
2645 case DBConnURIType::MSACE:
2646 #ifdef _WIN32
2648 OUString sDBURL("sdbc:ado:PROVIDER=Microsoft.ACE.OLEDB.12.0;DATA SOURCE=" + rURL.PathToFileName());
2649 aURLAny <<= sDBURL;
2651 #endif
2652 break;
2654 return aURLAny;
2657 /// Returns the URL of this SwDoc.
2658 OUString getOwnURL(SfxObjectShell const * pDocShell)
2660 OUString aRet;
2662 if (!pDocShell)
2663 return aRet;
2665 const INetURLObject& rURLObject = pDocShell->GetMedium()->GetURLObject();
2666 aRet = rURLObject.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2667 return aRet;
2671 Loads a data source from file and registers it.
2673 In case of success it returns the registered name, otherwise an empty string.
2674 Optionally add a prefix to the registered DB name.
2676 OUString LoadAndRegisterDataSource_Impl(DBConnURIType type, const uno::Reference< beans::XPropertySet > *pSettings,
2677 const INetURLObject &rURL, const OUString *pDestDir, SfxObjectShell* pDocShell)
2679 OUString sExt(rURL.GetFileExtension());
2680 uno::Any aTableFilterAny;
2681 uno::Any aSuppressVersionsAny;
2682 uno::Any aInfoAny;
2683 bool bStore = true;
2684 OUString sFind;
2686 uno::Any aURLAny = GetDBunoURI(rURL, type);
2687 switch (type) {
2688 case DBConnURIType::UNKNOWN:
2689 case DBConnURIType::CALC:
2690 case DBConnURIType::WRITER:
2691 break;
2692 case DBConnURIType::ODB:
2693 bStore = false;
2694 break;
2695 case DBConnURIType::FLAT:
2696 case DBConnURIType::DBASE:
2697 //set the filter to the file name without extension
2699 uno::Sequence<OUString> aFilters { rURL.getBase(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset) };
2700 aTableFilterAny <<= aFilters;
2702 break;
2703 case DBConnURIType::MSJET:
2704 case DBConnURIType::MSACE:
2705 aSuppressVersionsAny <<= true;
2706 break;
2711 uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext());
2712 uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext);
2714 OUString sNewName = rURL.getName(
2715 INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::Unambiguous);
2716 sal_Int32 nExtLen = sExt.getLength();
2717 sNewName = sNewName.replaceAt(sNewName.getLength() - nExtLen - 1, nExtLen + 1, u"");
2719 //find a unique name if sNewName already exists
2720 sFind = sNewName;
2721 sal_Int32 nIndex = 0;
2722 while (xDBContext->hasByName(sFind))
2723 sFind = sNewName + OUString::number(++nIndex);
2725 uno::Reference<uno::XInterface> xNewInstance;
2726 if (!bStore)
2728 //odb-file
2729 uno::Any aDataSource = xDBContext->getByName(rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE));
2730 aDataSource >>= xNewInstance;
2732 else
2734 xNewInstance = xDBContext->createInstance();
2735 uno::Reference<beans::XPropertySet> xDataProperties(xNewInstance, uno::UNO_QUERY);
2737 if (aURLAny.hasValue())
2738 xDataProperties->setPropertyValue("URL", aURLAny);
2739 if (aTableFilterAny.hasValue())
2740 xDataProperties->setPropertyValue("TableFilter", aTableFilterAny);
2741 if (aSuppressVersionsAny.hasValue())
2742 xDataProperties->setPropertyValue("SuppressVersionColumns", aSuppressVersionsAny);
2743 if (aInfoAny.hasValue())
2744 xDataProperties->setPropertyValue("Info", aInfoAny);
2746 if (DBConnURIType::FLAT == type && pSettings)
2748 uno::Any aSettings = xDataProperties->getPropertyValue("Settings");
2749 uno::Reference < beans::XPropertySet > xDSSettings;
2750 aSettings >>= xDSSettings;
2751 ::comphelper::copyProperties(*pSettings, xDSSettings);
2752 xDSSettings->setPropertyValue("Extension", uno::Any(sExt));
2755 uno::Reference<sdb::XDocumentDataSource> xDS(xNewInstance, uno::UNO_QUERY_THROW);
2756 uno::Reference<frame::XStorable> xStore(xDS->getDatabaseDocument(), uno::UNO_QUERY_THROW);
2757 OUString aOwnURL = getOwnURL(pDocShell);
2758 if (aOwnURL.isEmpty())
2760 // Cannot embed, as embedded data source would need the URL of the parent document.
2761 OUString sHomePath(SvtPathOptions().GetWorkPath());
2762 const OUString sTmpName = utl::CreateTempURL(sNewName, true, u".odb", pDestDir ? pDestDir : &sHomePath);
2763 xStore->storeAsURL(sTmpName, uno::Sequence<beans::PropertyValue>());
2765 else
2767 // Embed.
2768 OUString aStreamRelPath = "EmbeddedDatabase";
2769 uno::Reference<embed::XStorage> xStorage = pDocShell->GetStorage();
2771 // Refer to the sub-storage name in the document settings, so
2772 // we can load it again next time the file is imported.
2773 uno::Reference<lang::XMultiServiceFactory> xFactory(pDocShell->GetModel(), uno::UNO_QUERY);
2774 uno::Reference<beans::XPropertySet> xPropertySet(xFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY);
2775 xPropertySet->setPropertyValue("EmbeddedDatabaseName", uno::Any(aStreamRelPath));
2777 // Store it only after setting the above property, so that only one data source gets registered.
2778 SwDBManager::StoreEmbeddedDataSource(xStore, xStorage, aStreamRelPath, aOwnURL);
2781 xDBContext->registerObject(sFind, xNewInstance);
2783 catch (const uno::Exception&)
2785 sFind.clear();
2787 return sFind;
2791 OUString SwDBManager::LoadAndRegisterDataSource(weld::Window* pParent, SwDocShell* pDocShell)
2793 sfx2::FileDialogHelper aDlgHelper(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, pParent);
2794 aDlgHelper.SetContext(sfx2::FileDialogHelper::WriterRegisterDataSource);
2795 uno::Reference < ui::dialogs::XFilePicker3 > xFP = aDlgHelper.GetFilePicker();
2797 OUString sFilterAll(SwResId(STR_FILTER_ALL));
2798 OUString sFilterAllData(SwResId(STR_FILTER_ALL_DATA));
2800 const std::vector<std::pair<OUString, OUString>> filters{
2801 { SwResId(STR_FILTER_SXB), "*.odb" },
2802 { SwResId(STR_FILTER_SXC), "*.ods;*.sxc" },
2803 { SwResId(STR_FILTER_SXW), "*.odt;*.sxw" },
2804 { SwResId(STR_FILTER_DBF), "*.dbf" },
2805 { SwResId(STR_FILTER_XLS), "*.xls;*.xlsx" },
2806 { SwResId(STR_FILTER_DOC), "*.doc;*.docx" },
2807 { SwResId(STR_FILTER_TXT), "*.txt" },
2808 { SwResId(STR_FILTER_CSV), "*.csv" },
2809 #ifdef _WIN32
2810 { SwResId(STR_FILTER_MDB), "*.mdb;*.mde" },
2811 { SwResId(STR_FILTER_ACCDB), "*.accdb;*.accde" },
2812 #endif
2815 OUStringBuffer sAllDataFilter;
2816 for (const auto& [name, filter] : filters)
2818 (void)name;
2819 if (!sAllDataFilter.isEmpty())
2820 sAllDataFilter.append(';');
2821 sAllDataFilter.append(filter);
2824 xFP->appendFilter( sFilterAll, "*" );
2825 xFP->appendFilter( sFilterAllData, sAllDataFilter.makeStringAndClear());
2827 // Similar to sfx2::addExtension from sfx2/source/dialog/filtergrouping.cxx
2828 for (const auto& [name, filter] : filters)
2829 xFP->appendFilter(name + " (" + filter + ")", filter);
2831 xFP->setCurrentFilter( sFilterAll ) ;
2832 OUString sFind;
2833 if( ERRCODE_NONE == aDlgHelper.Execute() )
2835 uno::Reference< beans::XPropertySet > aSettings;
2836 const INetURLObject aURL( xFP->getSelectedFiles()[0] );
2837 const DBConnURIType type = GetDBunoType( aURL );
2839 if( DBConnURIType::FLAT == type )
2841 uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() );
2842 uno::Reference < sdb::XTextConnectionSettings > xSettingsDlg = sdb::TextConnectionSettings::create(xContext);
2843 if( xSettingsDlg->execute() )
2844 aSettings.set( uno::Reference < beans::XPropertySet >( xSettingsDlg, uno::UNO_QUERY_THROW ) );
2846 sFind = LoadAndRegisterDataSource_Impl( type, DBConnURIType::FLAT == type ? &aSettings : nullptr, aURL, nullptr, pDocShell );
2848 s_aUncommittedRegistrations.push_back(std::pair<SwDocShell*, OUString>(pDocShell, sFind));
2850 return sFind;
2853 void SwDBManager::StoreEmbeddedDataSource(const uno::Reference<frame::XStorable>& xStorable,
2854 const uno::Reference<embed::XStorage>& xStorage,
2855 const OUString& rStreamRelPath,
2856 const OUString& rOwnURL, bool bCopyTo)
2858 // Construct vnd.sun.star.pkg:// URL for later loading, and TargetStorage/StreamRelPath for storing.
2859 OUString const sTmpName = ConstructVndSunStarPkgUrl(rOwnURL, rStreamRelPath);
2861 uno::Sequence<beans::PropertyValue> aSequence = comphelper::InitPropertySequence(
2863 {"TargetStorage", uno::Any(xStorage)},
2864 {"StreamRelPath", uno::Any(rStreamRelPath)},
2865 {"BaseURI", uno::Any(rOwnURL)}
2867 if (bCopyTo)
2868 xStorable->storeToURL(sTmpName, aSequence);
2869 else
2870 xStorable->storeAsURL(sTmpName, aSequence);
2873 OUString SwDBManager::LoadAndRegisterDataSource(std::u16string_view rURI, const OUString *pDestDir)
2875 return LoadAndRegisterDataSource_Impl( DBConnURIType::UNKNOWN, nullptr, INetURLObject(rURI), pDestDir, nullptr );
2878 namespace
2880 // tdf#117824 switch the embedded database away from using its current storage and point it to temporary storage
2881 // which allows the original storage to be deleted
2882 void switchEmbeddedDatabaseStorage(const uno::Reference<sdb::XDatabaseContext>& rDatabaseContext, const OUString& rName)
2884 uno::Reference<sdb::XDocumentDataSource> xDS(rDatabaseContext->getByName(rName), uno::UNO_QUERY);
2885 if (!xDS)
2886 return;
2887 uno::Reference<document::XStorageBasedDocument> xStorageDoc(xDS->getDatabaseDocument(), uno::UNO_QUERY);
2888 if (!xStorageDoc)
2889 return;
2890 xStorageDoc->switchToStorage(comphelper::OStorageHelper::GetTemporaryStorage());
2894 void SwDBManager::RevokeDataSource(const OUString& rName)
2896 uno::Reference<sdb::XDatabaseContext> xDatabaseContext = sdb::DatabaseContext::create(comphelper::getProcessComponentContext());
2897 if (xDatabaseContext->hasByName(rName))
2899 switchEmbeddedDatabaseStorage(xDatabaseContext, rName);
2900 xDatabaseContext->revokeObject(rName);
2904 void SwDBManager::LoadAndRegisterEmbeddedDataSource(const SwDBData& rData, const SwDocShell& rDocShell)
2906 uno::Reference<sdb::XDatabaseContext> xDatabaseContext = sdb::DatabaseContext::create(comphelper::getProcessComponentContext());
2908 OUString sDataSource = rData.sDataSource;
2910 // Fallback, just in case the document would contain an embedded data source, but no DB fields.
2911 if (sDataSource.isEmpty())
2912 sDataSource = "EmbeddedDatabase";
2914 SwDBManager::RevokeDataSource( sDataSource );
2916 // Encode the stream name and the real path into a single URL.
2917 const INetURLObject& rURLObject = rDocShell.GetMedium()->GetURLObject();
2918 OUString const aURL = ConstructVndSunStarPkgUrl(
2919 rURLObject.GetMainURL(INetURLObject::DecodeMechanism::NONE),
2920 m_sEmbeddedName);
2922 uno::Reference<uno::XInterface> xDataSource(xDatabaseContext->getByName(aURL), uno::UNO_QUERY);
2923 xDatabaseContext->registerObject( sDataSource, xDataSource );
2925 // temp file - don't remember connection
2926 if (rData.sDataSource.isEmpty())
2927 s_aUncommittedRegistrations.push_back(std::pair<SwDocShell*, OUString>(nullptr, sDataSource));
2930 void SwDBManager::ExecuteFormLetter( SwWrtShell& rSh,
2931 const uno::Sequence<beans::PropertyValue>& rProperties)
2933 //prevent second call
2934 if(m_pImpl->pMergeDialog)
2935 return ;
2936 OUString sDataSource, sDataTableOrQuery;
2937 uno::Sequence<uno::Any> aSelection;
2939 sal_Int32 nCmdType = sdb::CommandType::TABLE;
2940 uno::Reference< sdbc::XConnection> xConnection;
2942 svx::ODataAccessDescriptor aDescriptor(rProperties);
2943 sDataSource = aDescriptor.getDataSource();
2944 OSL_VERIFY(aDescriptor[svx::DataAccessDescriptorProperty::Command] >>= sDataTableOrQuery);
2945 OSL_VERIFY(aDescriptor[svx::DataAccessDescriptorProperty::CommandType] >>= nCmdType);
2947 if ( aDescriptor.has(svx::DataAccessDescriptorProperty::Selection) )
2948 aDescriptor[svx::DataAccessDescriptorProperty::Selection] >>= aSelection;
2949 if ( aDescriptor.has(svx::DataAccessDescriptorProperty::Connection) )
2950 aDescriptor[svx::DataAccessDescriptorProperty::Connection] >>= xConnection;
2952 if(sDataSource.isEmpty() || sDataTableOrQuery.isEmpty())
2954 OSL_FAIL("PropertyValues missing or unset");
2955 return;
2958 //always create a connection for the dialog and dispose it after the dialog has been closed
2959 SwDSParam* pFound = nullptr;
2960 if(!xConnection.is())
2962 xConnection = SwDBManager::RegisterConnection(sDataSource);
2963 pFound = FindDSConnection(sDataSource, true);
2965 SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
2966 m_pImpl->pMergeDialog = pFact->CreateMailMergeDlg(rSh.GetView().GetViewFrame().GetFrameWeld(), rSh,
2967 sDataSource,
2968 sDataTableOrQuery,
2969 nCmdType,
2970 xConnection);
2971 if(m_pImpl->pMergeDialog->Execute() == RET_OK)
2973 aDescriptor[svx::DataAccessDescriptorProperty::Selection] <<= m_pImpl->pMergeDialog->GetSelection();
2975 uno::Reference<sdbc::XResultSet> xResSet = m_pImpl->pMergeDialog->GetResultSet();
2976 if(xResSet.is())
2977 aDescriptor[svx::DataAccessDescriptorProperty::Cursor] <<= xResSet;
2979 // SfxObjectShellRef is ok, since there should be no control over the document lifetime here
2980 SfxObjectShellRef xDocShell = rSh.GetView().GetViewFrame().GetObjectShell();
2982 lcl_emitEvent(SfxEventHintId::SwMailMerge, STR_SW_EVENT_MAIL_MERGE, xDocShell.get());
2984 // prepare mail merge descriptor
2985 SwMergeDescriptor aMergeDesc( m_pImpl->pMergeDialog->GetMergeType(), rSh, aDescriptor );
2986 aMergeDesc.sSaveToFilter = m_pImpl->pMergeDialog->GetSaveFilter();
2987 aMergeDesc.bCreateSingleFile = m_pImpl->pMergeDialog->IsSaveSingleDoc();
2988 aMergeDesc.bPrefixIsFilename = aMergeDesc.bCreateSingleFile;
2989 aMergeDesc.sPrefix = m_pImpl->pMergeDialog->GetTargetURL();
2991 if(!aMergeDesc.bCreateSingleFile)
2993 if(m_pImpl->pMergeDialog->IsGenerateFromDataBase())
2994 aMergeDesc.sDBcolumn = m_pImpl->pMergeDialog->GetColumnName();
2996 if(m_pImpl->pMergeDialog->IsFileEncryptedFromDataBase())
2997 aMergeDesc.sDBPasswordColumn = m_pImpl->pMergeDialog->GetPasswordColumnName();
3000 Merge( aMergeDesc );
3002 lcl_emitEvent(SfxEventHintId::SwMailMergeEnd, STR_SW_EVENT_MAIL_MERGE_END, xDocShell.get());
3004 // reset the cursor inside
3005 xResSet = nullptr;
3006 aDescriptor[svx::DataAccessDescriptorProperty::Cursor] <<= xResSet;
3008 if(pFound)
3010 for (const auto & pParam : m_DataSourceParams)
3012 if (pParam.get() == pFound)
3016 uno::Reference<lang::XComponent> xComp(pParam->xConnection, uno::UNO_QUERY);
3017 if(xComp.is())
3018 xComp->dispose();
3020 catch(const uno::RuntimeException&)
3022 //may be disposed already since multiple entries may have used the same connection
3024 break;
3026 //pFound doesn't need to be removed/deleted -
3027 //this has been done by the SwConnectionDisposedListener_Impl already
3030 m_pImpl->pMergeDialog.disposeAndClear();
3033 void SwDBManager::InsertText(SwWrtShell& rSh,
3034 const uno::Sequence< beans::PropertyValue>& rProperties)
3036 OUString sDataSource, sDataTableOrQuery;
3037 uno::Reference<sdbc::XResultSet> xResSet;
3038 uno::Sequence<uno::Any> aSelection;
3039 sal_Int16 nCmdType = sdb::CommandType::TABLE;
3040 uno::Reference< sdbc::XConnection> xConnection;
3041 for(const beans::PropertyValue& rValue : rProperties)
3043 if ( rValue.Name == "DataSourceName" )
3044 rValue.Value >>= sDataSource;
3045 else if ( rValue.Name == "Command" )
3046 rValue.Value >>= sDataTableOrQuery;
3047 else if ( rValue.Name == "Cursor" )
3048 rValue.Value >>= xResSet;
3049 else if ( rValue.Name == "Selection" )
3050 rValue.Value >>= aSelection;
3051 else if ( rValue.Name == "CommandType" )
3052 rValue.Value >>= nCmdType;
3053 else if ( rValue.Name == "ActiveConnection" )
3054 rValue.Value >>= xConnection;
3056 if(sDataSource.isEmpty() || sDataTableOrQuery.isEmpty() || !xResSet.is())
3058 OSL_FAIL("PropertyValues missing or unset");
3059 return;
3061 uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
3062 uno::Reference<sdbc::XDataSource> xSource;
3063 uno::Reference<container::XChild> xChild(xConnection, uno::UNO_QUERY);
3064 if(xChild.is())
3065 xSource.set(xChild->getParent(), uno::UNO_QUERY);
3066 if(!xSource.is())
3067 xSource = dbtools::getDataSource(sDataSource, xContext);
3068 uno::Reference< sdbcx::XColumnsSupplier > xColSupp( xResSet, uno::UNO_QUERY );
3069 SwDBData aDBData;
3070 aDBData.sDataSource = sDataSource;
3071 aDBData.sCommand = sDataTableOrQuery;
3072 aDBData.nCommandType = nCmdType;
3074 SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
3075 ScopedVclPtr<AbstractSwInsertDBColAutoPilot> pDlg(pFact->CreateSwInsertDBColAutoPilot( rSh.GetView(),
3076 xSource,
3077 xColSupp,
3078 aDBData ));
3079 if( RET_OK != pDlg->Execute() )
3080 return;
3082 OUString sDummy;
3083 if(!xConnection.is())
3084 xConnection = xSource->getConnection(sDummy, sDummy);
3087 pDlg->DataToDoc( aSelection , xSource, xConnection, xResSet);
3089 catch (const uno::Exception&)
3091 TOOLS_WARN_EXCEPTION("sw.mailmerge", "");
3095 uno::Reference<sdbc::XDataSource> SwDBManager::getDataSourceAsParent(const uno::Reference< sdbc::XConnection>& _xConnection,const OUString& _sDataSourceName)
3097 uno::Reference<sdbc::XDataSource> xSource;
3100 uno::Reference<container::XChild> xChild(_xConnection, uno::UNO_QUERY);
3101 if ( xChild.is() )
3102 xSource.set(xChild->getParent(), uno::UNO_QUERY);
3103 if ( !xSource.is() )
3104 xSource = dbtools::getDataSource(_sDataSourceName, ::comphelper::getProcessComponentContext());
3106 catch (const uno::Exception&)
3108 TOOLS_WARN_EXCEPTION("sw.mailmerge", "getDataSourceAsParent()");
3110 return xSource;
3113 uno::Reference<sdbc::XResultSet> SwDBManager::createCursor(const OUString& _sDataSourceName,
3114 const OUString& _sCommand,
3115 sal_Int32 _nCommandType,
3116 const uno::Reference<sdbc::XConnection>& _xConnection,
3117 const SwView* pView)
3119 uno::Reference<sdbc::XResultSet> xResultSet;
3122 uno::Reference< lang::XMultiServiceFactory > xMgr( ::comphelper::getProcessServiceFactory() );
3123 if( xMgr.is() )
3125 uno::Reference<uno::XInterface> xInstance = xMgr->createInstance("com.sun.star.sdb.RowSet");
3126 uno::Reference<beans::XPropertySet> xRowSetPropSet(xInstance, uno::UNO_QUERY);
3127 if(xRowSetPropSet.is())
3129 xRowSetPropSet->setPropertyValue("DataSourceName", uno::Any(_sDataSourceName));
3130 xRowSetPropSet->setPropertyValue("ActiveConnection", uno::Any(_xConnection));
3131 xRowSetPropSet->setPropertyValue("Command", uno::Any(_sCommand));
3132 xRowSetPropSet->setPropertyValue("CommandType", uno::Any(_nCommandType));
3134 uno::Reference< sdb::XCompletedExecution > xRowSet(xInstance, uno::UNO_QUERY);
3136 if ( xRowSet.is() )
3138 weld::Window* pWindow = pView ? pView->GetFrameWeld() : nullptr;
3139 uno::Reference< task::XInteractionHandler > xHandler( task::InteractionHandler::createWithParent(comphelper::getComponentContext(xMgr), pWindow ? pWindow->GetXWindow() : nullptr), uno::UNO_QUERY_THROW );
3140 xRowSet->executeWithCompletion(xHandler);
3142 xResultSet.set(xRowSet, uno::UNO_QUERY);
3146 catch (const uno::Exception&)
3148 TOOLS_WARN_EXCEPTION("sw.mailmerge", "Caught exception while creating a new RowSet");
3150 return xResultSet;
3153 void SwDBManager::setEmbeddedName(const OUString& rEmbeddedName, SwDocShell& rDocShell)
3155 bool bLoad = m_sEmbeddedName != rEmbeddedName && !rEmbeddedName.isEmpty();
3156 bool bRegisterListener = m_sEmbeddedName.isEmpty() && !rEmbeddedName.isEmpty();
3158 m_sEmbeddedName = rEmbeddedName;
3160 if (bLoad)
3162 uno::Reference<embed::XStorage> xStorage = rDocShell.GetStorage();
3163 // It's OK that we don't have the named sub-storage yet, in case
3164 // we're in the process of creating it.
3165 if (xStorage->hasByName(rEmbeddedName))
3166 LoadAndRegisterEmbeddedDataSource(rDocShell.GetDoc()->GetDBData(), rDocShell);
3169 if (bRegisterListener)
3170 // Register a remove listener, so we know when the embedded data source is removed.
3171 m_pImpl->m_xDataSourceRemovedListener = new SwDataSourceRemovedListener(*this);
3174 const OUString& SwDBManager::getEmbeddedName() const
3176 return m_sEmbeddedName;
3179 SwDoc* SwDBManager::getDoc() const
3181 return m_pDoc;
3184 void SwDBManager::releaseRevokeListener()
3186 if (m_pImpl->m_xDataSourceRemovedListener.is())
3188 m_pImpl->m_xDataSourceRemovedListener->Dispose();
3189 m_pImpl->m_xDataSourceRemovedListener.clear();
3193 SwDBManager::ConnectionDisposedListener_Impl::ConnectionDisposedListener_Impl(SwDBManager& rManager)
3194 : m_pDBManager(&rManager)
3198 void SwDBManager::ConnectionDisposedListener_Impl::disposing( const lang::EventObject& rSource )
3200 ::SolarMutexGuard aGuard;
3202 if (!m_pDBManager) return; // we're disposed too!
3204 uno::Reference<sdbc::XConnection> xSource(rSource.Source, uno::UNO_QUERY);
3205 for (size_t nPos = m_pDBManager->m_DataSourceParams.size(); nPos; nPos--)
3207 SwDSParam* pParam = m_pDBManager->m_DataSourceParams[nPos - 1].get();
3208 if(pParam->xConnection.is() &&
3209 (xSource == pParam->xConnection))
3211 m_pDBManager->m_DataSourceParams.erase(
3212 m_pDBManager->m_DataSourceParams.begin() + nPos - 1);
3217 std::shared_ptr<SwMailMergeConfigItem> SwDBManager::PerformMailMerge(SwView const * pView)
3219 std::shared_ptr<SwMailMergeConfigItem> xConfigItem = pView->GetMailMergeConfigItem();
3220 if (!xConfigItem)
3221 return xConfigItem;
3223 svx::ODataAccessDescriptor aDescriptor;
3224 aDescriptor.setDataSource(xConfigItem->GetCurrentDBData().sDataSource);
3225 aDescriptor[ svx::DataAccessDescriptorProperty::Connection ] <<= xConfigItem->GetConnection().getTyped();
3226 aDescriptor[ svx::DataAccessDescriptorProperty::Cursor ] <<= xConfigItem->GetResultSet();
3227 aDescriptor[ svx::DataAccessDescriptorProperty::Command ] <<= xConfigItem->GetCurrentDBData().sCommand;
3228 aDescriptor[ svx::DataAccessDescriptorProperty::CommandType ] <<= xConfigItem->GetCurrentDBData().nCommandType;
3229 aDescriptor[ svx::DataAccessDescriptorProperty::Selection ] <<= xConfigItem->GetSelection();
3231 SwWrtShell& rSh = pView->GetWrtShell();
3232 xConfigItem->SetTargetView(nullptr);
3234 SwMergeDescriptor aMergeDesc(DBMGR_MERGE_SHELL, rSh, aDescriptor);
3235 aMergeDesc.pMailMergeConfigItem = xConfigItem.get();
3236 aMergeDesc.bCreateSingleFile = true;
3237 rSh.GetDBManager()->Merge(aMergeDesc);
3239 return xConfigItem;
3242 void SwDBManager::RevokeLastRegistrations()
3244 if (s_aUncommittedRegistrations.empty())
3245 return;
3247 SwView* pView = ( m_pDoc && m_pDoc->GetDocShell() ) ? m_pDoc->GetDocShell()->GetView() : nullptr;
3248 if (pView)
3250 const std::shared_ptr<SwMailMergeConfigItem>& xConfigItem = pView->GetMailMergeConfigItem();
3251 if (xConfigItem)
3253 xConfigItem->DisposeResultSet();
3254 xConfigItem->DocumentReloaded();
3258 for (auto it = s_aUncommittedRegistrations.begin(); it != s_aUncommittedRegistrations.end();)
3260 if ((m_pDoc && it->first == m_pDoc->GetDocShell()) || it->first == nullptr)
3262 RevokeDataSource(it->second);
3263 it = s_aUncommittedRegistrations.erase(it);
3265 else
3266 ++it;
3270 void SwDBManager::CommitLastRegistrations()
3272 for (auto aIt = s_aUncommittedRegistrations.begin(); aIt != s_aUncommittedRegistrations.end();)
3274 if (aIt->first == m_pDoc->GetDocShell() || aIt->first == nullptr)
3276 m_aNotUsedConnections.push_back(aIt->second);
3277 aIt = s_aUncommittedRegistrations.erase(aIt);
3279 else
3280 aIt++;
3284 void SwDBManager::SetAsUsed(const OUString& rName)
3286 auto aFound = std::find(m_aNotUsedConnections.begin(), m_aNotUsedConnections.end(), rName);
3287 if (aFound != m_aNotUsedConnections.end())
3288 m_aNotUsedConnections.erase(aFound);
3291 void SwDBManager::RevokeNotUsedConnections()
3293 for (auto aIt = m_aNotUsedConnections.begin(); aIt != m_aNotUsedConnections.end();)
3295 RevokeDataSource(*aIt);
3296 aIt = m_aNotUsedConnections.erase(aIt);
3300 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */