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"
12 #include "scresid.hxx"
13 #include "document.hxx"
14 #include "orcusfilters.hxx"
16 #include "reffact.hxx"
17 #include "tabvwsh.hxx"
19 #include <unotools/pathoptions.hxx>
20 #include <tools/urlobj.hxx>
21 #include <svtools/svlbitm.hxx>
22 #include <svtools/treelistentry.hxx>
23 #include <svtools/viewdataentry.hxx>
24 #include <sfx2/objsh.hxx>
26 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
27 #include <com/sun/star/ui/dialogs/FilePicker.hpp>
28 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
29 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
31 using namespace com::sun::star
;
35 bool isAttribute(const SvTreeListEntry
& rEntry
)
37 const ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(rEntry
);
41 return pUserData
->meType
== ScOrcusXMLTreeParam::Attribute
;
45 const SvTreeListBox
& rTree
, const SvTreeListEntry
& rEntry
, std::vector
<size_t>& rNamespaces
)
48 for (const SvTreeListEntry
* p
= &rEntry
; p
; p
= rTree
.GetParent(p
))
50 const SvLBoxItem
* pItem
= p
->GetFirstItem(SV_ITEM_ID_LBOXSTRING
);
54 // Collect used namespace.
55 const ScOrcusXMLTreeParam::EntryData
* pData
= ScOrcusXMLTreeParam::getUserData(*p
);
57 rNamespaces
.push_back(pData
->mnNamespaceID
);
59 const SvLBoxString
* pStr
= static_cast<const SvLBoxString
*>(pItem
);
60 aBuf
.insert(0, pStr
->GetText());
61 aBuf
.insert(0, isAttribute(*p
) ? '@' : '/');
64 return aBuf
.makeStringAndClear();
69 ScXMLSourceDlg::ScXMLSourceDlg(
70 SfxBindings
* pB
, SfxChildWindow
* pCW
, vcl::Window
* pParent
, ScDocument
* pDoc
)
71 : ScAnyRefDlg(pB
, pCW
, pParent
, "XMLSourceDialog",
72 "modules/scalc/ui/xmlsourcedialog.ui")
73 , mpCurRefEntry(nullptr)
75 , mbDlgLostFocus(false)
77 get(mpBtnSelectSource
, "selectsource");
78 get(mpFtSourceFile
, "sourcefile");
79 get(mpMapGrid
, "mapgrid");
80 get(mpLbTree
, "tree");
81 Size
aTreeSize(mpLbTree
->LogicToPixel(Size(130, 120), MAP_APPFONT
));
82 mpLbTree
->set_width_request(aTreeSize
.Width());
83 mpLbTree
->set_height_request(aTreeSize
.Height());
84 get(mpRefEdit
, "edit");
85 mpRefEdit
->SetReferences(this, nullptr);
87 mpRefBtn
->SetReferences(this, mpRefEdit
);
88 get(mpBtnCancel
, "cancel");
91 mpActiveEdit
= mpRefEdit
;
93 maXMLParam
.maImgElementDefault
= Image(ScResId(IMG_ELEMENT_DEFAULT
));
94 maXMLParam
.maImgElementRepeat
= Image(ScResId(IMG_ELEMENT_REPEAT
));
95 maXMLParam
.maImgAttribute
= Image(ScResId(IMG_ELEMENT_ATTRIBUTE
));
97 Link
<Button
*,void> aBtnHdl
= LINK(this, ScXMLSourceDlg
, BtnPressedHdl
);
98 mpBtnSelectSource
->SetClickHdl(aBtnHdl
);
99 mpBtnOk
->SetClickHdl(aBtnHdl
);
100 mpBtnCancel
->SetClickHdl(aBtnHdl
);
102 Link
<Control
&,void> aLink2
= LINK(this, ScXMLSourceDlg
, GetFocusHdl
);
103 mpRefEdit
->SetGetFocusHdl(aLink2
);
104 mpRefBtn
->SetGetFocusHdl(aLink2
);
106 mpLbTree
->SetSelectHdl(LINK(this, ScXMLSourceDlg
, TreeItemSelectHdl
));
108 Link
<Edit
&,void> aLink
= LINK(this, ScXMLSourceDlg
, RefModifiedHdl
);
109 mpRefEdit
->SetModifyHdl(aLink
);
114 mpBtnSelectSource
->GrabFocus(); // Initial focus is on the select source button.
117 ScXMLSourceDlg::~ScXMLSourceDlg()
122 void ScXMLSourceDlg::dispose()
124 mpBtnSelectSource
.clear();
125 mpFtSourceFile
.clear();
132 mpActiveEdit
.clear();
133 ScAnyRefDlg::dispose();
136 bool ScXMLSourceDlg::IsRefInputMode() const
138 return mpActiveEdit
!= nullptr && mpActiveEdit
->IsEnabled();
141 void ScXMLSourceDlg::SetReference(const ScRange
& rRange
, ScDocument
* pDoc
)
146 if (rRange
.aStart
!= rRange
.aEnd
)
147 RefInputStart(mpActiveEdit
);
149 OUString
aStr(rRange
.aStart
.Format(ScRefFlags::ADDR_ABS_3D
, pDoc
, pDoc
->GetAddressConvention()));
150 mpActiveEdit
->SetRefString(aStr
);
155 void ScXMLSourceDlg::Deactivate()
157 mbDlgLostFocus
= true;
160 void ScXMLSourceDlg::SetActive()
164 mbDlgLostFocus
= false;
167 mpActiveEdit
->GrabFocus();
178 bool ScXMLSourceDlg::Close()
180 return DoClose(ScXMLSourceDlgWrapper::GetChildWindowId());
183 void ScXMLSourceDlg::SelectSourceFile()
185 uno::Reference
<ui::dialogs::XFilePicker3
> xFilePicker
= ui::dialogs::FilePicker::createWithMode( comphelper::getProcessComponentContext(), ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE
);
187 if (maSrcPath
.isEmpty())
189 xFilePicker
->setDisplayDirectory(SvtPathOptions().GetWorkPath());
192 // Use the directory of current source file.
193 INetURLObject
aURL(maSrcPath
);
194 aURL
.removeSegment();
195 aURL
.removeFinalSlash();
196 OUString aPath
= aURL
.GetMainURL(INetURLObject::NO_DECODE
);
197 xFilePicker
->setDisplayDirectory(aPath
);
200 if (xFilePicker
->execute() != ui::dialogs::ExecutableDialogResults::OK
)
201 // File picker dialog cancelled.
204 uno::Sequence
<OUString
> aFiles
= xFilePicker
->getSelectedFiles();
205 if (!aFiles
.getLength())
208 // There should only be one file returned from the file picker.
209 maSrcPath
= aFiles
[0];
210 mpFtSourceFile
->SetText(maSrcPath
);
211 maHighlightedEntries
.clear();
212 LoadSourceFileStructure(maSrcPath
);
215 void ScXMLSourceDlg::LoadSourceFileStructure(const OUString
& rPath
)
217 ScOrcusFilters
* pOrcus
= ScFormatFilter::Get().GetOrcusFilters();
221 mpXMLContext
.reset(pOrcus
->createXMLContext(*mpDoc
, rPath
));
225 mpXMLContext
->loadXMLStructure(*mpLbTree
, maXMLParam
);
228 void ScXMLSourceDlg::HandleGetFocus(Control
* pCtrl
)
230 mpActiveEdit
= nullptr;
231 if (pCtrl
== mpRefEdit
|| pCtrl
== mpRefBtn
)
232 mpActiveEdit
= mpRefEdit
;
235 mpActiveEdit
->SetSelection(Selection(0, SELECTION_MAX
));
240 class UnhighlightEntry
: public std::unary_function
<SvTreeListEntry
*, void>
242 SvTreeListBox
& mrTree
;
244 explicit UnhighlightEntry(SvTreeListBox
& rTree
) : mrTree(rTree
) {}
246 void operator() (SvTreeListEntry
* p
)
248 SvViewDataEntry
* pView
= mrTree
.GetViewDataEntry(p
);
252 pView
->SetHighlighted(false);
258 * When the current entry is a direct or indirect child of a mappable
259 * repeat element entry, that entry becomes the reference entry.
260 * Otherwise the reference entry equals the current entry. A reference
261 * entry is the entry that stores mapped cell position.
263 SvTreeListEntry
* getReferenceEntry(SvTreeListBox
& rTree
, SvTreeListEntry
* pCurEntry
)
265 SvTreeListEntry
* pParent
= rTree
.GetParent(pCurEntry
);
266 SvTreeListEntry
* pRefEntry
= nullptr;
269 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*pParent
);
270 OSL_ASSERT(pUserData
);
271 if (pUserData
->meType
== ScOrcusXMLTreeParam::ElementRepeat
)
273 // This is a repeat element.
276 // Second repeat element encountered. Not good.
282 pParent
= rTree
.GetParent(pParent
);
285 return pRefEntry
? pRefEntry
: pCurEntry
;
290 void ScXMLSourceDlg::TreeItemSelected()
292 SvTreeListEntry
* pEntry
= mpLbTree
->GetCurEntry();
296 if (!maHighlightedEntries
.empty())
298 // Remove highlights from all previously highlighted entries (if any).
299 std::for_each(maHighlightedEntries
.begin(), maHighlightedEntries
.end(), UnhighlightEntry(*mpLbTree
));
300 maHighlightedEntries
.clear();
303 mpCurRefEntry
= getReferenceEntry(*mpLbTree
, pEntry
);
305 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*mpCurRefEntry
);
306 OSL_ASSERT(pUserData
);
308 const ScAddress
& rPos
= pUserData
->maLinkedPos
;
311 OUString
aStr(rPos
.Format(ScRefFlags::ADDR_ABS_3D
, mpDoc
, mpDoc
->GetAddressConvention()));
312 mpRefEdit
->SetRefString(aStr
);
315 mpRefEdit
->SetRefString(OUString());
317 switch (pUserData
->meType
)
319 case ScOrcusXMLTreeParam::Attribute
:
320 AttributeSelected(*mpCurRefEntry
);
322 case ScOrcusXMLTreeParam::ElementDefault
:
323 DefaultElementSelected(*mpCurRefEntry
);
325 case ScOrcusXMLTreeParam::ElementRepeat
:
326 RepeatElementSelected(*mpCurRefEntry
);
333 void ScXMLSourceDlg::DefaultElementSelected(SvTreeListEntry
& rEntry
)
336 if (mpLbTree
->GetChildCount(&rEntry
) > 0)
338 // Only an element with no child elements (leaf element) can be linked.
339 bool bHasChild
= false;
340 for (SvTreeListEntry
* pChild
= mpLbTree
->FirstChild(&rEntry
); pChild
; pChild
= SvTreeListBox::NextSibling(pChild
))
342 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*pChild
);
343 OSL_ASSERT(pUserData
);
344 if (pUserData
->meType
!= ScOrcusXMLTreeParam::Attribute
)
346 // This child is not an attribute. Bail out.
359 // Check all its parents and make sure non of them are range-linked nor
361 if (IsParentDirty(&rEntry
))
370 void ScXMLSourceDlg::RepeatElementSelected(SvTreeListEntry
& rEntry
)
372 // Check all its parents first.
374 if (IsParentDirty(&rEntry
))
380 // Check all its child elements / attributes and make sure non of them are
381 // linked or repeat elements. In the future we will support range linking
382 // of repeat element who has another repeat elements. But first I need to
383 // support that scenario in orcus.
385 if (IsChildrenDirty(&rEntry
))
391 SvViewDataEntry
* p
= mpLbTree
->GetViewDataEntry(&rEntry
);
392 if (!p
->IsHighlighted())
394 // Highlight the entry if not highlighted already. This can happen
395 // when the current entry is a child entry of a repeat element entry.
396 p
->SetHighlighted(true);
397 mpLbTree
->Invalidate();
398 maHighlightedEntries
.push_back(&rEntry
);
401 SelectAllChildEntries(rEntry
);
405 void ScXMLSourceDlg::AttributeSelected(SvTreeListEntry
& rEntry
)
407 // Check all its parent elements and make sure non of them are linked nor
408 // repeat elements. In attribute's case, it's okay to have the immediate
409 // parent element linked (but not range-linked).
411 SvTreeListEntry
* pParent
= mpLbTree
->GetParent(&rEntry
);
412 OSL_ASSERT(pParent
); // attribute should have a parent element.
414 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*pParent
);
415 OSL_ASSERT(pUserData
);
416 if (pUserData
->maLinkedPos
.IsValid() && pUserData
->mbRangeParent
)
418 // Parent element is range-linked. Bail out.
423 if (IsParentDirty(&rEntry
))
432 void ScXMLSourceDlg::SetNonLinkable()
434 mpMapGrid
->Disable();
437 void ScXMLSourceDlg::SetSingleLinkable()
442 void ScXMLSourceDlg::SetRangeLinkable()
447 void ScXMLSourceDlg::SelectAllChildEntries(SvTreeListEntry
& rEntry
)
449 SvTreeListEntries
& rChildren
= rEntry
.GetChildEntries();
450 for (auto const& it
: rChildren
)
452 SvTreeListEntry
& r
= *it
;
453 SelectAllChildEntries(r
); // select recursively.
454 SvViewDataEntry
* p
= mpLbTree
->GetViewDataEntry(&r
);
455 p
->SetHighlighted(true);
456 mpLbTree
->Invalidate();
457 maHighlightedEntries
.push_back(&r
);
461 bool ScXMLSourceDlg::IsParentDirty(SvTreeListEntry
* pEntry
) const
463 SvTreeListEntry
* pParent
= mpLbTree
->GetParent(pEntry
);
466 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*pParent
);
468 if (pUserData
->maLinkedPos
.IsValid())
470 // This parent is already linked.
473 if (pUserData
->meType
== ScOrcusXMLTreeParam::ElementRepeat
)
475 // This is a repeat element.
478 pParent
= mpLbTree
->GetParent(pParent
);
483 bool ScXMLSourceDlg::IsChildrenDirty(SvTreeListEntry
* pEntry
) const
485 for (SvTreeListEntry
* pChild
= mpLbTree
->FirstChild(pEntry
); pChild
; pChild
= SvTreeListBox::NextSibling(pChild
))
487 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*pChild
);
488 OSL_ASSERT(pUserData
);
489 if (pUserData
->maLinkedPos
.IsValid())
493 if (pUserData
->meType
== ScOrcusXMLTreeParam::ElementRepeat
)
494 // We don't support linking of nested repeat elements (yet).
497 if (pUserData
->meType
== ScOrcusXMLTreeParam::ElementDefault
)
499 // Check recursively.
500 if (IsChildrenDirty(pChild
))
511 * Pick only the leaf elements.
514 ScOrcusImportXMLParam::RangeLink
& rRangeLink
, std::vector
<size_t>& rNamespaces
,
515 const SvTreeListBox
& rTree
, const SvTreeListEntry
& rEntry
)
517 const SvTreeListEntries
& rChildren
= rEntry
.GetChildEntries();
518 if (rChildren
.empty())
519 // No more children. We're done.
522 for (auto const& it
: rChildren
)
524 const SvTreeListEntry
& rChild
= *it
;
525 OUString aPath
= getXPath(rTree
, rChild
, rNamespaces
);
526 const ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(rChild
);
528 if (pUserData
&& pUserData
->mbLeafNode
)
530 if (!aPath
.isEmpty())
531 // XPath should never be empty anyway, but it won't hurt to check...
532 rRangeLink
.maFieldPaths
.push_back(OUStringToOString(aPath
, RTL_TEXTENCODING_UTF8
));
536 getFieldLinks(rRangeLink
, rNamespaces
, rTree
, rChild
);
540 void removeDuplicates(std::vector
<size_t>& rArray
)
542 std::sort(rArray
.begin(), rArray
.end());
543 std::vector
<size_t>::iterator it
= std::unique(rArray
.begin(), rArray
.end());
544 rArray
.erase(it
, rArray
.end());
549 void ScXMLSourceDlg::OkPressed()
556 ScOrcusImportXMLParam aParam
;
558 // Convert single cell links.
560 std::set
<const SvTreeListEntry
*>::const_iterator it
= maCellLinks
.begin(), itEnd
= maCellLinks
.end();
561 for (; it
!= itEnd
; ++it
)
563 const SvTreeListEntry
& rEntry
= **it
;
564 OUString aPath
= getXPath(*mpLbTree
, rEntry
, aParam
.maNamespaces
);
565 const ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(rEntry
);
567 aParam
.maCellLinks
.push_back(
568 ScOrcusImportXMLParam::CellLink(
569 pUserData
->maLinkedPos
, OUStringToOString(aPath
, RTL_TEXTENCODING_UTF8
)));
573 // Convert range links. For now, an element with range link takes all its
574 // child elements as its fields.
576 std::set
<const SvTreeListEntry
*>::const_iterator it
= maRangeLinks
.begin(), itEnd
= maRangeLinks
.end();
577 for (; it
!= itEnd
; ++it
)
579 const SvTreeListEntry
& rEntry
= **it
;
580 const ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(rEntry
);
582 ScOrcusImportXMLParam::RangeLink aRangeLink
;
583 aRangeLink
.maPos
= pUserData
->maLinkedPos
;
585 // Go through all its child elements.
586 getFieldLinks(aRangeLink
, aParam
.maNamespaces
, *mpLbTree
, rEntry
);
588 aParam
.maRangeLinks
.push_back(aRangeLink
);
592 // Remove duplicate namespace IDs.
593 removeDuplicates(aParam
.maNamespaces
);
595 // Now do the import.
596 mpXMLContext
->importXML(aParam
);
598 // Don't forget to broadcast the change.
599 SfxObjectShell
* pShell
= mpDoc
->GetDocumentShell();
600 pShell
->Broadcast(SfxSimpleHint(FID_DATACHANGED
));
602 // Repaint the grid to force repaint the cell values.
603 ScTabViewShell
* pViewShell
= ScTabViewShell::GetActiveViewShell();
605 pViewShell
->PaintGrid();
610 void ScXMLSourceDlg::CancelPressed()
615 void ScXMLSourceDlg::RefEditModified()
617 OUString aRefStr
= mpRefEdit
->GetText();
619 // Check if the address is valid.
620 ScAddress aLinkedPos
;
621 ScRefFlags nRes
= aLinkedPos
.Parse(aRefStr
, mpDoc
, mpDoc
->GetAddressConvention());
622 bool bValid
= ( (nRes
& ScRefFlags::VALID
) == ScRefFlags::VALID
);
624 // TODO: For some unknown reason, setting the ref invalid will hide the text altogether.
625 // Find out how to make this work.
626 // mpRefEdit->SetRefValid(bValid);
629 aLinkedPos
.SetInvalid();
631 // Set this address to the current reference entry.
633 // This should never happen.
636 ScOrcusXMLTreeParam::EntryData
* pUserData
= ScOrcusXMLTreeParam::getUserData(*mpCurRefEntry
);
638 // This should never happen either.
641 bool bRepeatElem
= pUserData
->meType
== ScOrcusXMLTreeParam::ElementRepeat
;
642 pUserData
->maLinkedPos
= aLinkedPos
;
643 pUserData
->mbRangeParent
= aLinkedPos
.IsValid() && bRepeatElem
;
648 maRangeLinks
.insert(mpCurRefEntry
);
650 maRangeLinks
.erase(mpCurRefEntry
);
655 maCellLinks
.insert(mpCurRefEntry
);
657 maCellLinks
.erase(mpCurRefEntry
);
660 // Enable the import button only when at least one link exists.
661 bool bHasLink
= !maCellLinks
.empty() || !maRangeLinks
.empty();
662 mpBtnOk
->Enable(bHasLink
);
665 IMPL_LINK_TYPED(ScXMLSourceDlg
, GetFocusHdl
, Control
&, rCtrl
, void)
667 HandleGetFocus(&rCtrl
);
670 IMPL_LINK_TYPED(ScXMLSourceDlg
, BtnPressedHdl
, Button
*, pBtn
, void)
672 if (pBtn
== mpBtnSelectSource
)
674 else if (pBtn
== mpBtnOk
)
676 else if (pBtn
== mpBtnCancel
)
680 IMPL_LINK_NOARG_TYPED(ScXMLSourceDlg
, TreeItemSelectHdl
, SvTreeListBox
*, void)
685 IMPL_LINK_NOARG_TYPED(ScXMLSourceDlg
, RefModifiedHdl
, Edit
&, void)
690 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */