1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <xmlsourcedlg.hxx>
11 #include <bitmaps.hlst>
12 #include <document.hxx>
13 #include <orcusfilters.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
;
29 bool isAttribute(const weld::TreeView
& rControl
, const weld::TreeIter
& rEntry
)
31 const ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(rControl
, rEntry
);
35 return pUserData
->meType
== ScOrcusXMLTreeParam::Attribute
;
39 const weld::TreeView
& rTree
, const weld::TreeIter
& rEntry
, std::vector
<size_t>& rNamespaces
)
42 std::unique_ptr
<weld::TreeIter
> xEntry(rTree
.make_iterator(&rEntry
));
45 // Collect used namespace.
46 const ScOrcusXMLTreeParam::EntryData
* pData
= ScOrcusXMLTreeParam::getUserData(rTree
, *xEntry
);
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
))
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
)
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);
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
)
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
);
133 void ScXMLSourceDlg::Deactivate()
135 mbDlgLostFocus
= true;
138 void ScXMLSourceDlg::SetActive()
142 mbDlgLostFocus
= false;
145 mpActiveEdit
->GrabFocus();
150 m_xDialog
->grab_focus();
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.
180 uno::Sequence
<OUString
> aFiles
= xFilePicker
->getSelectedFiles();
181 if (!aFiles
.hasElements())
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();
196 mpXMLContext
= pOrcus
->createXMLContext(*mpDoc
, rPath
);
200 mpXMLContext
->loadXMLStructure(*mxLbTree
, maXMLParam
);
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
;
218 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(rTree
, *xParent
);
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
);
231 std::unique_ptr
<weld::TreeIter
> xCurEntry(rTree
.make_iterator(&rCurEntry
));
237 void ScXMLSourceDlg::TreeItemSelected()
239 std::unique_ptr
<weld::TreeIter
> xEntry(mxLbTree
->make_iterator());
240 if (!mxLbTree
->get_cursor(xEntry
.get()))
243 mxLbTree
->unselect_all();
244 mxLbTree
->select(*xEntry
);
246 mxCurRefEntry
= getReferenceEntry(*mxLbTree
, *xEntry
);
248 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*mxLbTree
, *mxCurRefEntry
);
251 const ScAddress
& rPos
= pUserData
->maLinkedPos
;
254 OUString
aStr(rPos
.Format(ScRefFlags::ADDR_ABS_3D
, mpDoc
, mpDoc
->GetAddressConvention()));
255 mxRefEdit
->SetRefString(aStr
);
258 mxRefEdit
->SetRefString(OUString());
260 switch (pUserData
->meType
)
262 case ScOrcusXMLTreeParam::Attribute
:
263 AttributeSelected(*mxCurRefEntry
);
265 case ScOrcusXMLTreeParam::ElementDefault
:
266 DefaultElementSelected(*mxCurRefEntry
);
268 case ScOrcusXMLTreeParam::ElementRepeat
:
269 RepeatElementSelected(*mxCurRefEntry
);
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
);
288 if (pUserData
->meType
!= ScOrcusXMLTreeParam::Attribute
)
290 // This child is not an attribute. Bail out.
295 while (mxLbTree
->iter_next_sibling(*xChild
));
304 // Check all its parents and make sure non of them are range-linked nor
306 if (IsParentDirty(&rEntry
))
315 void ScXMLSourceDlg::RepeatElementSelected(const weld::TreeIter
& rEntry
)
317 // Check all its parents first.
319 if (IsParentDirty(&rEntry
))
325 // Check all its child elements / attributes and make sure non of them are
328 if (IsChildrenDirty(&rEntry
))
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
);
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
);
355 if (pUserData
->maLinkedPos
.IsValid() && pUserData
->mbRangeParent
)
357 // Parent element is range-linked. Bail out.
362 if (IsParentDirty(&rEntry
))
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
))
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
))
405 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*mxLbTree
, *xParent
);
407 if (pUserData
->maLinkedPos
.IsValid())
409 // This parent is already linked.
413 while (mxLbTree
->iter_parent(*xParent
));
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
))
425 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*mxLbTree
, *xChild
);
427 if (pUserData
->maLinkedPos
.IsValid())
431 if (pUserData
->meType
== ScOrcusXMLTreeParam::ElementDefault
)
433 // Check recursively.
434 if (IsChildrenDirty(xChild
.get()))
437 } while (mxLbTree
->iter_next_sibling(*xChild
));
445 * Pick only the leaf elements.
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
);
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.
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()
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();
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);
568 aLinkedPos
.SetInvalid();
570 // Set this address to the current reference entry.
572 // This should never happen.
575 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*mxLbTree
, *mxCurRefEntry
);
577 // This should never happen either.
580 bool bRepeatElem
= pUserData
->meType
== ScOrcusXMLTreeParam::ElementRepeat
;
581 pUserData
->maLinkedPos
= aLinkedPos
;
582 pUserData
->mbRangeParent
= aLinkedPos
.IsValid() && bRepeatElem
;
587 maRangeLinks
.insert(mxLbTree
->make_iterator(mxCurRefEntry
.get()));
589 maRangeLinks
.erase(mxCurRefEntry
);
594 maCellLinks
.insert(mxLbTree
->make_iterator(mxCurRefEntry
.get()));
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())
608 else if (&rBtn
== mxBtnOk
.get())
610 else if (&rBtn
== mxBtnCancel
.get())
614 IMPL_LINK_NOARG(ScXMLSourceDlg
, TreeItemSelectHdl
, weld::TreeView
&, void)
619 IMPL_LINK_NOARG(ScXMLSourceDlg
, RefModifiedHdl
, formula::RefEdit
&, void)
624 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */