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 "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>
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())
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
);
60 aFont
.SetFamilyName(aFontName
->second
.get
<OUString
>());
61 std::optional
<PropertyMap::Property
> aHeight
= pDefaultCharProps
->getProperty(PROP_CHAR_HEIGHT
);
64 nHeight
= aHeight
->second
.get
<double>() * 35; // points -> mm100
65 aFont
.SetFontSize(Size(0, nHeight
));
68 pOut
->SetMapMode(aMap
);
69 sal_Int32 nWidth
= pOut
->GetTextWidth(aLongest
);
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
);
104 uno::Reference
<xml::dom::XDocumentBuilder
> xDomBuilder(
105 xml::dom::DocumentBuilder::create(m_xComponentContext
));
106 if (!xDomBuilder
.is())
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
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?
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");
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
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();
168 SAL_WARN("writerfilter",
169 "SdtHelper::loadPropertiesXMLs: can't fetch storeid for custom doc!");
172 m_xPropertiesXMLs
.insert({ aStoreId
, xDoc
});
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
);
196 auto aNamespaceId
= string::split(aNamespace
[0], ':');
197 if (aNamespaceId
.size() < 2)
199 SAL_WARN("writerfilter",
200 "SdtHelper::getValueFromDataBinding: invalid namespace: " << aNamespace
[0]);
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())
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
);
273 void SdtHelper::createDropDownControl()
275 assert(getControlType() == SdtControlType::dropDown
276 || getControlType() == SdtControlType::comboBox
);
279 = officecfg::Office::Writer::Filter::Import::DOCX::ImportComboBoxAsDropDown::get();
280 const OUString aDefaultText
= m_aSdtTexts
.makeStringAndClear();
285 uno::Reference
<css::text::XTextField
> xControlModel(
286 m_rDM_Impl
.GetTextFactory()->createInstance("com.sun.star.text.TextField.DropDown"),
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
);
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;
312 uno::Reference
<awt::XControlModel
> xControlModel(
313 m_rDM_Impl
.GetTextFactory()->createInstance("com.sun.star.form.component.ComboBox"),
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
325 lcl_getOptimalWidth(m_rDM_Impl
.GetStyleSheetTable(), aDefaultText
, m_aDropDownItems
),
326 xControlModel
, uno::Sequence
<beans::PropertyValue
>());
333 void SdtHelper::createPlainTextControl()
335 assert(getControlType() == SdtControlType::plainText
);
337 OUString aDefaultText
= m_aSdtTexts
.makeStringAndClear();
340 uno::Reference
<css::text::XTextField
> xControlModel(
341 m_rDM_Impl
.GetTextFactory()->createInstance("com.sun.star.text.TextField.Input"),
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
));
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()));
367 void SdtHelper::createDateContentControl()
369 if (!m_xDateFieldStartRange
.is())
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
);
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();
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");
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()))
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();
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())
429 if (!sFullDate
.isEmpty())
431 sal_Int32 nTimeSep
= sFullDate
.indexOf("T");
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(),
439 uno::Reference
<util::XRefreshable
> xRefreshable(xTextFieldsSupplier
->getTextFields(),
441 xRefreshable
->refresh();
443 // Store all unused sdt parameters from grabbag
444 xNameCont
->insertByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG
,
445 uno::Any(getInteropGrabBagAndClear()));
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"),
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
);
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
)
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
);
519 m_sDataBindingPrefixMapping
.clear();
520 m_sDataBindingXPath
.clear();
521 m_sDataBindingStoreItemID
.clear();
523 m_bShowingPlcHdr
= false;
525 m_aCheckedState
.clear();
526 m_aUncheckedState
.clear();
527 m_aPlaceholderDocPart
.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: */