cid#1607171 Data race condition
[LibreOffice.git] / sc / source / ui / xmlsource / xmlsourcedlg.cxx
blob17cc4b928baaa6923955d7a6b4e4b7ffeea606cd
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <xmlsourcedlg.hxx>
11 #include <bitmaps.hlst>
12 #include <document.hxx>
13 #include <orcusfilters.hxx>
14 #include <filter.hxx>
15 #include <reffact.hxx>
16 #include <tabvwsh.hxx>
18 #include <tools/urlobj.hxx>
19 #include <sfx2/filedlghelper.hxx>
21 #include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
22 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
23 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
25 using namespace com::sun::star;
27 namespace {
29 bool isAttribute(const weld::TreeView& rControl, const weld::TreeIter& rEntry)
31 const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rControl, rEntry);
32 if (!pUserData)
33 return false;
35 return pUserData->meType == ScOrcusXMLTreeParam::Attribute;
38 OUString getXPath(
39 const weld::TreeView& rTree, const weld::TreeIter& rEntry, std::vector<size_t>& rNamespaces)
41 OUStringBuffer aBuf;
42 std::unique_ptr<weld::TreeIter> xEntry(rTree.make_iterator(&rEntry));
45 // Collect used namespace.
46 const ScOrcusXMLTreeParam::EntryData* pData = ScOrcusXMLTreeParam::getUserData(rTree, *xEntry);
47 if (pData)
48 rNamespaces.push_back(pData->mnNamespaceID);
50 // element separator is '/' whereas attribute separator is '/@' in xpath.
51 std::u16string_view sSeparator;
52 if (isAttribute(rTree, *xEntry))
53 sSeparator = u"/@";
54 else
55 sSeparator = u"/";
56 aBuf.insert(0, sSeparator + rTree.get_text(*xEntry, 0));
58 while (rTree.iter_parent(*xEntry));
60 return aBuf.makeStringAndClear();
65 ScXMLSourceDlg::ScXMLSourceDlg(
66 SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent, ScDocument* pDoc)
67 : ScAnyRefDlgController(pB, pCW, pParent, u"modules/scalc/ui/xmlsourcedialog.ui"_ustr, u"XMLSourceDialog"_ustr)
68 , mpDoc(pDoc)
69 , mbDlgLostFocus(false)
70 , mxBtnSelectSource(m_xBuilder->weld_button(u"selectsource"_ustr))
71 , mxFtSourceFile(m_xBuilder->weld_label(u"sourcefile"_ustr))
72 , mxMapGrid(m_xBuilder->weld_container(u"mapgrid"_ustr))
73 , mxLbTree(m_xBuilder->weld_tree_view(u"tree"_ustr))
74 , mxRefEdit(new formula::RefEdit(m_xBuilder->weld_entry(u"edit"_ustr)))
75 , mxRefBtn(new formula::RefButton(m_xBuilder->weld_button(u"ref"_ustr)))
76 , mxBtnOk(m_xBuilder->weld_button(u"ok"_ustr))
77 , mxBtnCancel(m_xBuilder->weld_button(u"cancel"_ustr))
78 , maCustomCompare(*mxLbTree)
79 , maCellLinks(maCustomCompare)
80 , maRangeLinks(maCustomCompare)
82 mxLbTree->set_size_request(mxLbTree->get_approximate_digit_width() * 40,
83 mxLbTree->get_height_rows(15));
84 mxLbTree->set_selection_mode(SelectionMode::Multiple);
85 mxRefEdit->SetReferences(this, nullptr);
86 mxRefBtn->SetReferences(this, mxRefEdit.get());
88 mpActiveEdit = mxRefEdit.get();
90 maXMLParam.maImgElementDefault = RID_BMP_ELEMENT_DEFAULT;
91 maXMLParam.maImgElementRepeat = RID_BMP_ELEMENT_REPEAT;
92 maXMLParam.maImgAttribute = RID_BMP_ELEMENT_ATTRIBUTE;
94 Link<weld::Button&,void> aBtnHdl = LINK(this, ScXMLSourceDlg, BtnPressedHdl);
95 mxBtnSelectSource->connect_clicked(aBtnHdl);
96 mxBtnOk->connect_clicked(aBtnHdl);
97 mxBtnCancel->connect_clicked(aBtnHdl);
99 mxLbTree->connect_selection_changed(LINK(this, ScXMLSourceDlg, TreeItemSelectHdl));
101 Link<formula::RefEdit&,void> aLink = LINK(this, ScXMLSourceDlg, RefModifiedHdl);
102 mxRefEdit->SetModifyHdl(aLink);
104 mxBtnOk->set_sensitive(false);
106 SetNonLinkable();
107 mxBtnSelectSource->grab_focus(); // Initial focus is on the select source button.
110 ScXMLSourceDlg::~ScXMLSourceDlg()
114 bool ScXMLSourceDlg::IsRefInputMode() const
116 return mpActiveEdit != nullptr && mpActiveEdit->GetWidget()->get_sensitive();
119 void ScXMLSourceDlg::SetReference(const ScRange& rRange, ScDocument& rDoc)
121 if (!mpActiveEdit)
122 return;
124 if (rRange.aStart != rRange.aEnd)
125 RefInputStart(mpActiveEdit);
127 OUString aStr(rRange.aStart.Format(ScRefFlags::ADDR_ABS_3D, &rDoc, rDoc.GetAddressConvention()));
128 mpActiveEdit->SetRefString(aStr);
130 RefEditModified();
133 void ScXMLSourceDlg::Deactivate()
135 mbDlgLostFocus = true;
138 void ScXMLSourceDlg::SetActive()
140 if (mbDlgLostFocus)
142 mbDlgLostFocus = false;
143 if (mpActiveEdit)
145 mpActiveEdit->GrabFocus();
148 else
150 m_xDialog->grab_focus();
153 RefInputDone();
156 void ScXMLSourceDlg::Close()
158 DoClose(ScXMLSourceDlgWrapper::GetChildWindowId());
161 void ScXMLSourceDlg::SelectSourceFile()
163 sfx2::FileDialogHelper aDlgHelper(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE,
164 FileDialogFlags::NONE, m_xDialog.get());
165 aDlgHelper.SetContext(sfx2::FileDialogHelper::CalcXMLSource);
167 uno::Reference<ui::dialogs::XFilePicker3> xFilePicker = aDlgHelper.GetFilePicker();
169 // Use the directory of current source file.
170 INetURLObject aURL(maSrcPath);
171 aURL.removeSegment();
172 aURL.removeFinalSlash();
173 OUString aPath = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
174 xFilePicker->setDisplayDirectory(aPath);
176 if (xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK)
177 // File picker dialog cancelled.
178 return;
180 uno::Sequence<OUString> aFiles = xFilePicker->getSelectedFiles();
181 if (!aFiles.hasElements())
182 return;
184 // There should only be one file returned from the file picker.
185 maSrcPath = aFiles[0];
186 mxFtSourceFile->set_label(maSrcPath);
187 LoadSourceFileStructure(maSrcPath);
190 void ScXMLSourceDlg::LoadSourceFileStructure(const OUString& rPath)
192 ScOrcusFilters* pOrcus = ScFormatFilter::Get().GetOrcusFilters();
193 if (!pOrcus)
194 return;
196 mpXMLContext = pOrcus->createXMLContext(*mpDoc, rPath);
197 if (!mpXMLContext)
198 return;
200 mpXMLContext->loadXMLStructure(*mxLbTree, maXMLParam);
203 namespace {
206 * The current entry is the reference entry for a cell link. For a range
207 * link, the reference entry is the shallowest repeat element entry up from
208 * the current entry position. The mapped cell position for a range link is
209 * stored with the reference entry.
211 std::unique_ptr<weld::TreeIter> getReferenceEntry(const weld::TreeView& rTree, const weld::TreeIter& rCurEntry)
213 std::unique_ptr<weld::TreeIter> xParent(rTree.make_iterator(&rCurEntry));
214 bool bParent = rTree.iter_parent(*xParent);
215 std::unique_ptr<weld::TreeIter> xRefEntry;
216 while (bParent)
218 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rTree, *xParent);
219 assert(pUserData);
220 if (pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat)
222 // This is a repeat element - a potential reference entry.
223 xRefEntry = rTree.make_iterator(xParent.get());
225 bParent = rTree.iter_parent(*xParent);
228 if (xRefEntry)
229 return xRefEntry;
231 std::unique_ptr<weld::TreeIter> xCurEntry(rTree.make_iterator(&rCurEntry));
232 return xCurEntry;
237 void ScXMLSourceDlg::TreeItemSelected()
239 std::unique_ptr<weld::TreeIter> xEntry(mxLbTree->make_iterator());
240 if (!mxLbTree->get_cursor(xEntry.get()))
241 return;
243 mxLbTree->unselect_all();
244 mxLbTree->select(*xEntry);
246 mxCurRefEntry = getReferenceEntry(*mxLbTree, *xEntry);
248 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mxLbTree, *mxCurRefEntry);
249 assert(pUserData);
251 const ScAddress& rPos = pUserData->maLinkedPos;
252 if (rPos.IsValid())
254 OUString aStr(rPos.Format(ScRefFlags::ADDR_ABS_3D, mpDoc, mpDoc->GetAddressConvention()));
255 mxRefEdit->SetRefString(aStr);
257 else
258 mxRefEdit->SetRefString(OUString());
260 switch (pUserData->meType)
262 case ScOrcusXMLTreeParam::Attribute:
263 AttributeSelected(*mxCurRefEntry);
264 break;
265 case ScOrcusXMLTreeParam::ElementDefault:
266 DefaultElementSelected(*mxCurRefEntry);
267 break;
268 case ScOrcusXMLTreeParam::ElementRepeat:
269 RepeatElementSelected(*mxCurRefEntry);
270 break;
271 default:
276 void ScXMLSourceDlg::DefaultElementSelected(const weld::TreeIter& rEntry)
278 if (mxLbTree->iter_has_child(rEntry))
280 // Only an element with no child elements (leaf element) can be linked.
281 bool bHasChild = false;
282 std::unique_ptr<weld::TreeIter> xChild(mxLbTree->make_iterator(&rEntry));
283 (void)mxLbTree->iter_children(*xChild);
286 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mxLbTree, *xChild);
287 assert(pUserData);
288 if (pUserData->meType != ScOrcusXMLTreeParam::Attribute)
290 // This child is not an attribute. Bail out.
291 bHasChild = true;
292 break;
295 while (mxLbTree->iter_next_sibling(*xChild));
297 if (bHasChild)
299 SetNonLinkable();
300 return;
304 // Check all its parents and make sure non of them are range-linked nor
305 // repeat elements.
306 if (IsParentDirty(&rEntry))
308 SetNonLinkable();
309 return;
312 SetSingleLinkable();
315 void ScXMLSourceDlg::RepeatElementSelected(const weld::TreeIter& rEntry)
317 // Check all its parents first.
319 if (IsParentDirty(&rEntry))
321 SetNonLinkable();
322 return;
325 // Check all its child elements / attributes and make sure non of them are
326 // linked.
328 if (IsChildrenDirty(&rEntry))
330 SetNonLinkable();
331 return;
334 if (!mxLbTree->is_selected(rEntry))
336 // Highlight the entry if not highlighted already. This can happen
337 // when the current entry is a child entry of a repeat element entry.
338 mxLbTree->select(rEntry);
341 SelectAllChildEntries(rEntry);
342 SetRangeLinkable();
345 void ScXMLSourceDlg::AttributeSelected(const weld::TreeIter& rEntry)
347 // Check all its parent elements and make sure non of them are linked nor
348 // repeat elements. In attribute's case, it's okay to have the immediate
349 // parent element linked (but not range-linked).
350 std::unique_ptr<weld::TreeIter> xParent(mxLbTree->make_iterator(&rEntry));
351 mxLbTree->iter_parent(*xParent);
353 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mxLbTree, *xParent);
354 assert(pUserData);
355 if (pUserData->maLinkedPos.IsValid() && pUserData->mbRangeParent)
357 // Parent element is range-linked. Bail out.
358 SetNonLinkable();
359 return;
362 if (IsParentDirty(&rEntry))
364 SetNonLinkable();
365 return;
368 SetSingleLinkable();
371 void ScXMLSourceDlg::SetNonLinkable()
373 mxMapGrid->set_sensitive(false);
376 void ScXMLSourceDlg::SetSingleLinkable()
378 mxMapGrid->set_sensitive(true);
381 void ScXMLSourceDlg::SetRangeLinkable()
383 mxMapGrid->set_sensitive(true);
386 void ScXMLSourceDlg::SelectAllChildEntries(const weld::TreeIter& rEntry)
388 std::unique_ptr<weld::TreeIter> xChild(mxLbTree->make_iterator(&rEntry));
389 if (!mxLbTree->iter_children(*xChild))
390 return;
393 SelectAllChildEntries(*xChild); // select recursively.
394 mxLbTree->select(*xChild);
395 } while (mxLbTree->iter_next_sibling(*xChild));
398 bool ScXMLSourceDlg::IsParentDirty(const weld::TreeIter* pEntry) const
400 std::unique_ptr<weld::TreeIter> xParent(mxLbTree->make_iterator(pEntry));
401 if (!mxLbTree->iter_parent(*xParent))
402 return false;
405 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mxLbTree, *xParent);
406 assert(pUserData);
407 if (pUserData->maLinkedPos.IsValid())
409 // This parent is already linked.
410 return true;
413 while (mxLbTree->iter_parent(*xParent));
414 return false;
417 bool ScXMLSourceDlg::IsChildrenDirty(const weld::TreeIter* pEntry) const
419 std::unique_ptr<weld::TreeIter> xChild(mxLbTree->make_iterator(pEntry));
420 if (!mxLbTree->iter_children(*xChild))
421 return false;
425 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mxLbTree, *xChild);
426 assert(pUserData);
427 if (pUserData->maLinkedPos.IsValid())
428 // Already linked.
429 return true;
431 if (pUserData->meType == ScOrcusXMLTreeParam::ElementDefault)
433 // Check recursively.
434 if (IsChildrenDirty(xChild.get()))
435 return true;
437 } while (mxLbTree->iter_next_sibling(*xChild));
439 return false;
442 namespace {
445 * Pick only the leaf elements.
447 void getFieldLinks(
448 ScOrcusImportXMLParam::RangeLink& rRangeLink, std::vector<size_t>& rNamespaces,
449 const weld::TreeView& rTree, const weld::TreeIter& rEntry)
451 OUString aPath = getXPath(rTree, rEntry, rNamespaces);
452 const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rTree, rEntry);
454 if (pUserData)
456 if (pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat)
457 // nested repeat element automatically becomes a row-group node.
458 rRangeLink.maRowGroups.push_back(
459 OUStringToOString(aPath, RTL_TEXTENCODING_UTF8));
461 if (pUserData->mbLeafNode && !aPath.isEmpty())
462 // XPath should never be empty anyway, but it won't hurt to check...
463 rRangeLink.maFieldPaths.push_back(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8));
466 std::unique_ptr<weld::TreeIter> xChild(rTree.make_iterator(&rEntry));
468 if (!rTree.iter_children(*xChild))
469 // No more children. We're done.
470 return;
474 // Walk recursively.
475 getFieldLinks(rRangeLink, rNamespaces, rTree, *xChild);
477 while (rTree.iter_next_sibling(*xChild));
480 void removeDuplicates(std::vector<size_t>& rArray)
482 std::sort(rArray.begin(), rArray.end());
483 std::vector<size_t>::iterator it = std::unique(rArray.begin(), rArray.end());
484 rArray.erase(it, rArray.end());
489 void ScXMLSourceDlg::OkPressed()
491 if (!mpXMLContext)
492 return;
494 // Begin import.
496 ScOrcusImportXMLParam aParam;
498 // Convert single cell links.
499 for (const auto& rEntry : maCellLinks)
501 OUString aPath = getXPath(*mxLbTree, *rEntry, aParam.maNamespaces);
502 const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mxLbTree, *rEntry);
504 aParam.maCellLinks.emplace_back(
505 pUserData->maLinkedPos, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8));
508 // Convert range links. For now, an element with range link takes all its
509 // child elements as its fields.
510 for (const auto& rEntry: maRangeLinks)
512 const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mxLbTree, *rEntry);
514 ScOrcusImportXMLParam::RangeLink aRangeLink;
515 aRangeLink.maPos = pUserData->maLinkedPos;
517 // Go through all its child elements.
518 getFieldLinks(aRangeLink, aParam.maNamespaces, *mxLbTree, *rEntry);
520 // Add the reference entry as a row-group node, which will be used
521 // as a row position increment point.
522 OUString aThisEntry = getXPath(*mxLbTree, *rEntry, aParam.maNamespaces);
523 aRangeLink.maRowGroups.push_back(
524 OUStringToOString(aThisEntry, RTL_TEXTENCODING_UTF8));
526 aParam.maRangeLinks.push_back(aRangeLink);
529 // Remove duplicate namespace IDs.
530 removeDuplicates(aParam.maNamespaces);
532 // Now do the import.
533 mpXMLContext->importXML(aParam);
535 // Don't forget to broadcast the change.
536 ScDocShell* pShell = mpDoc->GetDocumentShell();
537 pShell->Broadcast(SfxHint(SfxHintId::ScDataChanged));
539 // Repaint the grid to force repaint the cell values.
540 ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
541 if (pViewShell)
542 pViewShell->PaintGrid();
544 m_xDialog->response(RET_OK);
547 void ScXMLSourceDlg::CancelPressed()
549 m_xDialog->response(RET_CANCEL);
552 void ScXMLSourceDlg::RefEditModified()
554 OUString aRefStr = mxRefEdit->GetText();
556 // Check if the address is valid.
557 // Preset current sheet in case only address was entered.
558 ScAddress aLinkedPos;
559 aLinkedPos.SetTab( ScDocShell::GetCurTab());
560 ScRefFlags nRes = aLinkedPos.Parse(aRefStr, *mpDoc, mpDoc->GetAddressConvention());
561 bool bValid = ( (nRes & ScRefFlags::VALID) == ScRefFlags::VALID );
563 // TODO: For some unknown reason, setting the ref invalid will hide the text altogether.
564 // Find out how to make this work.
565 // mxRefEdit->SetRefValid(bValid);
567 if (!bValid)
568 aLinkedPos.SetInvalid();
570 // Set this address to the current reference entry.
571 if (!mxCurRefEntry)
572 // This should never happen.
573 return;
575 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mxLbTree, *mxCurRefEntry);
576 if (!pUserData)
577 // This should never happen either.
578 return;
580 bool bRepeatElem = pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat;
581 pUserData->maLinkedPos = aLinkedPos;
582 pUserData->mbRangeParent = aLinkedPos.IsValid() && bRepeatElem;
584 if (bRepeatElem)
586 if (bValid)
587 maRangeLinks.insert(mxLbTree->make_iterator(mxCurRefEntry.get()));
588 else
589 maRangeLinks.erase(mxCurRefEntry);
591 else
593 if (bValid)
594 maCellLinks.insert(mxLbTree->make_iterator(mxCurRefEntry.get()));
595 else
596 maCellLinks.erase(mxCurRefEntry);
599 // Enable the import button only when at least one link exists.
600 bool bHasLink = !maCellLinks.empty() || !maRangeLinks.empty();
601 mxBtnOk->set_sensitive(bHasLink);
604 IMPL_LINK(ScXMLSourceDlg, BtnPressedHdl, weld::Button&, rBtn, void)
606 if (&rBtn == mxBtnSelectSource.get())
607 SelectSourceFile();
608 else if (&rBtn == mxBtnOk.get())
609 OkPressed();
610 else if (&rBtn == mxBtnCancel.get())
611 CancelPressed();
614 IMPL_LINK_NOARG(ScXMLSourceDlg, TreeItemSelectHdl, weld::TreeView&, void)
616 TreeItemSelected();
619 IMPL_LINK_NOARG(ScXMLSourceDlg, RefModifiedHdl, formula::RefEdit&, void)
621 RefEditModified();
624 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */