android: Update app-specific/MIME type icons
[LibreOffice.git] / writerfilter / source / dmapper / SdtHelper.cxx
blob04180214760521b40fcab92b4688e498228eb82c
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 "SdtHelper.hxx"
11 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
12 #include <com/sun/star/drawing/XControlShape.hpp>
13 #include <com/sun/star/text/VertOrientation.hpp>
14 #include <editeng/unoprnms.hxx>
15 #include <sal/log.hxx>
16 #include <utility>
17 #include <vcl/svapp.hxx>
18 #include <vcl/outdev.hxx>
19 #include <comphelper/diagnose_ex.hxx>
20 #include <comphelper/string.hxx>
21 #include <comphelper/sequence.hxx>
22 #include <xmloff/odffields.hxx>
23 #include <com/sun/star/text/XTextField.hpp>
24 #include "DomainMapper_Impl.hxx"
25 #include "StyleSheetTable.hxx"
26 #include <officecfg/Office/Writer.hxx>
27 #include <com/sun/star/util/XRefreshable.hpp>
28 #include <com/sun/star/text/XTextFieldsSupplier.hpp>
29 #include <com/sun/star/document/XOOXMLDocumentPropertiesImporter.hpp>
30 #include <ooxml/OOXMLDocument.hxx>
31 #include <com/sun/star/xml/xpath/XPathAPI.hpp>
32 #include <com/sun/star/xml/xpath/XPathException.hpp>
33 #include <com/sun/star/xml/dom/DocumentBuilder.hpp>
35 namespace writerfilter::dmapper
37 using namespace ::com::sun::star;
38 using namespace ::css::xml::xpath;
39 using namespace ::comphelper;
41 /// w:sdt's w:dropDownList doesn't have width, so guess the size based on the longest string.
42 static awt::Size lcl_getOptimalWidth(const StyleSheetTablePtr& pStyleSheet,
43 OUString const& rDefault, std::vector<OUString>& rItems)
45 OUString aLongest = rDefault;
46 sal_Int32 nHeight = 0;
47 for (const OUString& rItem : rItems)
48 if (rItem.getLength() > aLongest.getLength())
49 aLongest = rItem;
51 MapMode aMap(MapUnit::Map100thMM);
52 OutputDevice* pOut = Application::GetDefaultDevice();
53 pOut->Push(vcl::PushFlags::FONT | vcl::PushFlags::MAPMODE);
55 PropertyMapPtr pDefaultCharProps = pStyleSheet->GetDefaultCharProps();
56 vcl::Font aFont(pOut->GetFont());
57 std::optional<PropertyMap::Property> aFontName
58 = pDefaultCharProps->getProperty(PROP_CHAR_FONT_NAME);
59 if (aFontName)
60 aFont.SetFamilyName(aFontName->second.get<OUString>());
61 std::optional<PropertyMap::Property> aHeight = pDefaultCharProps->getProperty(PROP_CHAR_HEIGHT);
62 if (aHeight)
64 nHeight = aHeight->second.get<double>() * 35; // points -> mm100
65 aFont.SetFontSize(Size(0, nHeight));
67 pOut->SetFont(aFont);
68 pOut->SetMapMode(aMap);
69 sal_Int32 nWidth = pOut->GetTextWidth(aLongest);
71 pOut->Pop();
73 // Border: see PDFWriterImpl::drawFieldBorder(), border size is font height / 4,
74 // so additional width / height needed is height / 2.
75 sal_Int32 nBorder = nHeight / 2;
77 // Width: space for the text + the square having the dropdown arrow.
78 return { nWidth + nBorder + nHeight, nHeight + nBorder };
81 SdtHelper::SdtHelper(DomainMapper_Impl& rDM_Impl,
82 css::uno::Reference<css::uno::XComponentContext> xContext)
83 : m_rDM_Impl(rDM_Impl)
84 , m_xComponentContext(std::move(xContext))
85 , m_aControlType(SdtControlType::unknown)
86 , m_bHasElements(false)
87 , m_bOutsideAParagraph(false)
88 , m_bPropertiesXMLsLoaded(false)
92 SdtHelper::~SdtHelper() = default;
94 void SdtHelper::loadPropertiesXMLs()
96 // Initialize properties xml storage (m_xPropertiesXMLs)
97 uno::Reference<uno::XInterface> xTemp
98 = m_xComponentContext->getServiceManager()->createInstanceWithContext(
99 "com.sun.star.document.OOXMLDocumentPropertiesImporter", m_xComponentContext);
100 uno::Reference<document::XOOXMLDocumentPropertiesImporter> xImporter(xTemp, uno::UNO_QUERY);
101 if (!xImporter.is())
102 return;
104 uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder(
105 xml::dom::DocumentBuilder::create(m_xComponentContext));
106 if (!xDomBuilder.is())
107 return;
109 // Load core properties
112 auto xCorePropsStream = xImporter->getCorePropertiesStream(m_rDM_Impl.m_xDocumentStorage);
113 m_xPropertiesXMLs.insert(
114 { OUString("{6C3C8BC8-F283-45AE-878A-BAB7291924A1}"), // hardcoded id for core props
115 xDomBuilder->parse(xCorePropsStream) });
117 catch (const uno::Exception&)
119 SAL_WARN("writerfilter",
120 "SdtHelper::loadPropertiesXMLs: failed loading core properties XML");
123 // Load extended properties
126 auto xExtPropsStream
127 = xImporter->getExtendedPropertiesStream(m_rDM_Impl.m_xDocumentStorage);
128 m_xPropertiesXMLs.insert(
129 { OUString("{6668398D-A668-4E3E-A5EB-62B293D839F1}"), // hardcoded id for extended props
130 xDomBuilder->parse(xExtPropsStream) });
132 catch (const uno::Exception&)
134 SAL_WARN("writerfilter",
135 "SdtHelper::loadPropertiesXMLs: failed loading extended properties XML");
138 // TODO: some other property items?
140 // Add custom XMLs
141 uno::Sequence<uno::Reference<xml::dom::XDocument>> aCustomXmls
142 = m_rDM_Impl.getDocumentReference()->getCustomXmlDomList();
143 uno::Sequence<uno::Reference<xml::dom::XDocument>> aCustomXmlProps
144 = m_rDM_Impl.getDocumentReference()->getCustomXmlDomPropsList();
145 if (aCustomXmls.getLength())
147 uno::Reference<XXPathAPI> xXpathAPI = XPathAPI::create(m_xComponentContext);
148 xXpathAPI->registerNS("ds",
149 "http://schemas.openxmlformats.org/officeDocument/2006/customXml");
150 sal_Int32 nItem = 0;
151 // Hereby we assume that items from getCustomXmlDomList() and getCustomXmlDomPropsList()
152 // are matching each other:
153 // item1.xml -> itemProps1.xml, item2.xml -> itemProps2.xml
154 // This does works practically, but is it true in general?
155 for (const auto& xDoc : aCustomXmls)
157 // Retrieve storeid from properties xml
158 OUString aStoreId;
159 uno::Reference<XXPathObject> xResult
160 = xXpathAPI->eval(aCustomXmlProps[nItem], "string(/ds:datastoreItem/@ds:itemID)");
162 if (xResult.is() && xResult->getString().getLength())
164 aStoreId = xResult->getString();
166 else
168 SAL_WARN("writerfilter",
169 "SdtHelper::loadPropertiesXMLs: can't fetch storeid for custom doc!");
172 m_xPropertiesXMLs.insert({ aStoreId, xDoc });
173 nItem++;
177 m_bPropertiesXMLsLoaded = true;
180 static void lcl_registerNamespaces(std::u16string_view sNamespaceString,
181 const uno::Reference<XXPathAPI>& xXPathAPI)
183 // Split namespaces and register it in XPathAPI
184 auto aNamespaces = string::split(sNamespaceString, ' ');
185 for (const auto& sNamespace : aNamespaces)
187 // Here we have just one namespace in format "xmlns:ns0='http://someurl'"
188 auto aNamespace = string::split(sNamespace, '=');
189 if (aNamespace.size() < 2)
191 SAL_WARN("writerfilter",
192 "SdtHelper::getValueFromDataBinding: invalid namespace: " << sNamespace);
193 continue;
196 auto aNamespaceId = string::split(aNamespace[0], ':');
197 if (aNamespaceId.size() < 2)
199 SAL_WARN("writerfilter",
200 "SdtHelper::getValueFromDataBinding: invalid namespace: " << aNamespace[0]);
201 continue;
204 OUString sNamespaceURL = aNamespace[1];
205 sNamespaceURL = string::strip(sNamespaceURL, ' ');
206 sNamespaceURL = string::strip(sNamespaceURL, '\'');
208 xXPathAPI->registerNS(aNamespaceId[1], sNamespaceURL);
212 std::optional<OUString> SdtHelper::getValueFromDataBinding()
214 // No xpath - nothing to do
215 if (m_sDataBindingXPath.isEmpty())
216 return {};
218 // Load properties XMLs
219 if (!m_bPropertiesXMLsLoaded)
220 loadPropertiesXMLs();
222 uno::Reference<XXPathAPI> xXpathAPI = XPathAPI::create(m_xComponentContext);
224 lcl_registerNamespaces(m_sDataBindingPrefixMapping, xXpathAPI);
226 // Find storage by store id and eval xpath there
227 const auto& aSourceIt = m_xPropertiesXMLs.find(m_sDataBindingStoreItemID);
228 if (aSourceIt != m_xPropertiesXMLs.end())
232 uno::Reference<XXPathObject> xResult
233 = xXpathAPI->eval(aSourceIt->second, m_sDataBindingXPath);
235 if (xResult.is() && xResult->getNodeList() && xResult->getNodeList()->getLength()
236 && xResult->getString().getLength())
238 return xResult->getString();
241 catch (const XPathException& e)
243 // XPath failed? Log and continue with next data document
244 SAL_WARN("writerfilter", "SdtHelper::failed running XPath: " << e.Message);
248 // Nothing found? Try to iterate storages and eval xpath
249 for (const auto& aSource : m_xPropertiesXMLs)
253 uno::Reference<XXPathObject> xResult
254 = xXpathAPI->eval(aSource.second, m_sDataBindingXPath);
256 if (xResult.is() && xResult->getNodeList() && xResult->getNodeList()->getLength()
257 && xResult->getString().getLength())
259 return xResult->getString();
262 catch (const XPathException& e)
264 // XPath failed? Log and continue with next data document
265 SAL_WARN("writerfilter", "SdtHelper::failed running XPath: " << e.Message);
269 // No data
270 return {};
273 void SdtHelper::createDropDownControl()
275 assert(getControlType() == SdtControlType::dropDown
276 || getControlType() == SdtControlType::comboBox);
278 const bool bDropDown
279 = officecfg::Office::Writer::Filter::Import::DOCX::ImportComboBoxAsDropDown::get();
280 const OUString aDefaultText = m_aSdtTexts.makeStringAndClear();
282 if (bDropDown)
284 // create field
285 uno::Reference<css::text::XTextField> xControlModel(
286 m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.DropDown"),
287 uno::UNO_QUERY);
289 const auto it = std::find_if(
290 m_aDropDownItems.begin(), m_aDropDownItems.end(),
291 [aDefaultText](const OUString& item) -> bool { return !item.compareTo(aDefaultText); });
293 if (m_aDropDownItems.end() == it)
295 m_aDropDownItems.push_back(aDefaultText);
298 // set properties
299 uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
300 xPropertySet->setPropertyValue("SelectedItem", uno::Any(aDefaultText));
301 xPropertySet->setPropertyValue("Items",
302 uno::Any(comphelper::containerToSequence(m_aDropDownItems)));
304 // add it into document
305 m_rDM_Impl.appendTextContent(xControlModel, uno::Sequence<beans::PropertyValue>());
307 m_bHasElements = true;
309 else
311 // create control
312 uno::Reference<awt::XControlModel> xControlModel(
313 m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.form.component.ComboBox"),
314 uno::UNO_QUERY);
316 // set properties
317 uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
318 xPropertySet->setPropertyValue("DefaultText", uno::Any(aDefaultText));
319 xPropertySet->setPropertyValue("Dropdown", uno::Any(true));
320 xPropertySet->setPropertyValue("StringItemList",
321 uno::Any(comphelper::containerToSequence(m_aDropDownItems)));
323 // add it into document
324 createControlShape(
325 lcl_getOptimalWidth(m_rDM_Impl.GetStyleSheetTable(), aDefaultText, m_aDropDownItems),
326 xControlModel, uno::Sequence<beans::PropertyValue>());
329 // clean up
330 clear();
333 void SdtHelper::createPlainTextControl()
335 assert(getControlType() == SdtControlType::plainText);
337 OUString aDefaultText = m_aSdtTexts.makeStringAndClear();
339 // create field
340 uno::Reference<css::text::XTextField> xControlModel(
341 m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.Input"),
342 uno::UNO_QUERY);
344 // set properties
345 uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
347 std::optional<OUString> oData = getValueFromDataBinding();
348 if (oData.has_value())
349 aDefaultText = *oData;
351 xPropertySet->setPropertyValue("Content", uno::Any(aDefaultText));
353 PropertyMap aMap;
354 aMap.InsertProps(m_rDM_Impl.GetTopContext());
356 // add it into document
357 m_rDM_Impl.appendTextContent(xControlModel, aMap.GetPropertyValues());
359 // Store all unused sdt parameters from grabbag
360 xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG,
361 uno::Any(getInteropGrabBagAndClear()));
363 // clean up
364 clear();
367 void SdtHelper::createDateContentControl()
369 if (!m_xDateFieldStartRange.is())
370 return;
372 uno::Reference<text::XTextCursor> xCrsr;
373 if (m_rDM_Impl.HasTopText())
375 uno::Reference<text::XTextAppend> xTextAppend = m_rDM_Impl.GetTopTextAppend();
376 if (xTextAppend.is())
378 xCrsr = xTextAppend->createTextCursorByRange(xTextAppend);
381 if (!xCrsr.is())
382 return;
386 xCrsr->gotoRange(m_xDateFieldStartRange, false);
387 // tdf#138093: Date selector reset, if placed inside table
388 // Modified to XOR relationship and adding dummy paragraph conditions
389 bool bIsInTable = (m_rDM_Impl.hasTableManager() && m_rDM_Impl.getTableManager().isInTable())
390 != (m_rDM_Impl.m_nTableDepth > 0)
391 && m_rDM_Impl.GetIsDummyParaAddedForTableInSection();
392 if (bIsInTable)
393 xCrsr->goRight(1, false);
394 xCrsr->gotoEnd(true);
396 catch (uno::Exception&)
398 TOOLS_WARN_EXCEPTION("writerfilter.dmapper",
399 "Cannot get the right text range for date field");
400 return;
403 uno::Reference<uno::XInterface> xFieldInterface
404 = m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.Fieldmark");
405 uno::Reference<text::XFormField> xFormField(xFieldInterface, uno::UNO_QUERY);
406 uno::Reference<text::XTextContent> xToInsert(xFormField, uno::UNO_QUERY);
407 if (!(xFormField.is() && xToInsert.is()))
408 return;
410 xToInsert->attach(uno::Reference<text::XTextRange>(xCrsr, uno::UNO_QUERY_THROW));
411 xFormField->setFieldType(ODF_FORMDATE);
412 uno::Reference<container::XNameContainer> xNameCont = xFormField->getParameters();
413 if (xNameCont.is())
415 OUString sDateFormat = m_sDateFormat.makeStringAndClear();
417 // Replace quotation mark used for marking static strings in date format
418 sDateFormat = sDateFormat.replaceAll("'", "\"");
419 xNameCont->insertByName(ODF_FORMDATE_DATEFORMAT, uno::Any(sDateFormat));
420 xNameCont->insertByName(ODF_FORMDATE_DATEFORMAT_LANGUAGE,
421 uno::Any(m_sLocale.makeStringAndClear()));
423 OUString sFullDate = m_sDate.makeStringAndClear();
425 std::optional<OUString> oData = getValueFromDataBinding();
426 if (oData.has_value())
427 sFullDate = *oData;
429 if (!sFullDate.isEmpty())
431 sal_Int32 nTimeSep = sFullDate.indexOf("T");
432 if (nTimeSep != -1)
433 sFullDate = sFullDate.copy(0, nTimeSep);
434 xNameCont->insertByName(ODF_FORMDATE_CURRENTDATE, uno::Any(sFullDate));
437 uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(m_rDM_Impl.GetTextDocument(),
438 uno::UNO_QUERY);
439 uno::Reference<util::XRefreshable> xRefreshable(xTextFieldsSupplier->getTextFields(),
440 uno::UNO_QUERY);
441 xRefreshable->refresh();
443 // Store all unused sdt parameters from grabbag
444 xNameCont->insertByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG,
445 uno::Any(getInteropGrabBagAndClear()));
447 clear();
450 void SdtHelper::createControlShape(awt::Size aSize,
451 uno::Reference<awt::XControlModel> const& xControlModel,
452 const uno::Sequence<beans::PropertyValue>& rGrabBag)
454 uno::Reference<drawing::XControlShape> xControlShape(
455 m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.drawing.ControlShape"),
456 uno::UNO_QUERY);
457 xControlShape->setSize(aSize);
458 xControlShape->setControl(xControlModel);
460 uno::Reference<beans::XPropertySet> xPropertySet(xControlShape, uno::UNO_QUERY);
461 xPropertySet->setPropertyValue("VertOrient", uno::Any(text::VertOrientation::CENTER));
463 if (rGrabBag.hasElements())
464 xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, uno::Any(rGrabBag));
466 uno::Reference<text::XTextContent> xTextContent(xControlShape, uno::UNO_QUERY);
467 m_rDM_Impl.appendTextContent(xTextContent, uno::Sequence<beans::PropertyValue>());
468 m_bHasElements = true;
471 void SdtHelper::appendToInteropGrabBag(const beans::PropertyValue& rValue)
473 m_aGrabBag.push_back(rValue);
476 uno::Sequence<beans::PropertyValue> SdtHelper::getInteropGrabBagAndClear()
478 uno::Sequence<beans::PropertyValue> aRet = comphelper::containerToSequence(m_aGrabBag);
479 m_aGrabBag.clear();
480 return aRet;
483 bool SdtHelper::isInteropGrabBagEmpty() const { return m_aGrabBag.empty(); }
485 sal_Int32 SdtHelper::getInteropGrabBagSize() const { return m_aGrabBag.size(); }
487 bool SdtHelper::containedInInteropGrabBag(const OUString& rValueName)
489 return std::any_of(
490 m_aGrabBag.begin(), m_aGrabBag.end(),
491 [&rValueName](const beans::PropertyValue& i) { return i.Name == rValueName; });
494 void SdtHelper::SetShowingPlcHdr() { m_bShowingPlcHdr = true; }
496 bool SdtHelper::GetShowingPlcHdr() const { return m_bShowingPlcHdr; }
498 void SdtHelper::SetChecked() { m_bChecked = true; }
500 bool SdtHelper::GetChecked() const { return m_bChecked; }
502 void SdtHelper::SetCheckedState(const OUString& rCheckedState) { m_aCheckedState = rCheckedState; }
504 const OUString& SdtHelper::GetCheckedState() const { return m_aCheckedState; }
506 void SdtHelper::SetUncheckedState(const OUString& rUncheckedState)
508 m_aUncheckedState = rUncheckedState;
511 const OUString& SdtHelper::GetUncheckedState() const { return m_aUncheckedState; }
513 void SdtHelper::clear()
515 m_aDropDownItems.clear();
516 m_aDropDownDisplayTexts.clear();
517 setControlType(SdtControlType::unknown);
518 m_nSdtType = 0;
519 m_sDataBindingPrefixMapping.clear();
520 m_sDataBindingXPath.clear();
521 m_sDataBindingStoreItemID.clear();
522 m_aGrabBag.clear();
523 m_bShowingPlcHdr = false;
524 m_bChecked = false;
525 m_aCheckedState.clear();
526 m_aUncheckedState.clear();
527 m_aPlaceholderDocPart.clear();
528 m_aColor.clear();
529 m_aAlias.clear();
530 m_aTag.clear();
531 m_nId = 0;
532 m_nTabIndex = 0;
533 m_aLock.clear();
536 void SdtHelper::SetPlaceholderDocPart(const OUString& rPlaceholderDocPart)
538 m_aPlaceholderDocPart = rPlaceholderDocPart;
541 const OUString& SdtHelper::GetPlaceholderDocPart() const { return m_aPlaceholderDocPart; }
543 void SdtHelper::SetColor(const OUString& rColor) { m_aColor = rColor; }
545 const OUString& SdtHelper::GetColor() const { return m_aColor; }
547 void SdtHelper::SetAppearance(const OUString& rAppearance) { m_aAppearance = rAppearance; }
549 const OUString& SdtHelper::GetAppearance() const { return m_aAppearance; }
551 void SdtHelper::SetAlias(const OUString& rAlias) { m_aAlias = rAlias; }
553 const OUString& SdtHelper::GetAlias() const { return m_aAlias; }
555 void SdtHelper::SetTag(const OUString& rTag) { m_aTag = rTag; }
557 const OUString& SdtHelper::GetTag() const { return m_aTag; }
559 void SdtHelper::SetId(sal_Int32 nId) { m_nId = nId; }
561 sal_Int32 SdtHelper::GetId() const { return m_nId; }
563 void SdtHelper::SetTabIndex(sal_uInt32 nTabIndex) { m_nTabIndex = nTabIndex; }
565 sal_uInt32 SdtHelper::GetTabIndex() const { return m_nTabIndex; }
567 void SdtHelper::SetLock(const OUString& rLock) { m_aLock = rLock; }
569 const OUString& SdtHelper::GetLock() const { return m_aLock; }
571 } // namespace writerfilter::dmapper
573 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */