summaryrefslogtreecommitdiffstats
path: root/writerfilter/source/dmapper/SdtHelper.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /writerfilter/source/dmapper/SdtHelper.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'writerfilter/source/dmapper/SdtHelper.cxx')
-rw-r--r--writerfilter/source/dmapper/SdtHelper.cxx513
1 files changed, 513 insertions, 0 deletions
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx b/writerfilter/source/dmapper/SdtHelper.cxx
new file mode 100644
index 000000000..31c83d5a3
--- /dev/null
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -0,0 +1,513 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "SdtHelper.hxx"
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/drawing/XControlShape.hpp>
+#include <com/sun/star/text/VertOrientation.hpp>
+#include <editeng/unoprnms.hxx>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/outdev.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/sequence.hxx>
+#include <xmloff/odffields.hxx>
+#include <com/sun/star/text/XTextField.hpp>
+#include "DomainMapper_Impl.hxx"
+#include "StyleSheetTable.hxx"
+#include <officecfg/Office/Writer.hxx>
+#include <com/sun/star/util/XRefreshable.hpp>
+#include <com/sun/star/text/XTextFieldsSupplier.hpp>
+#include <com/sun/star/document/XOOXMLDocumentPropertiesImporter.hpp>
+#include <ooxml/OOXMLDocument.hxx>
+#include <com/sun/star/xml/xpath/XPathAPI.hpp>
+#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
+
+namespace writerfilter::dmapper
+{
+using namespace ::com::sun::star;
+using namespace ::css::xml::xpath;
+using namespace ::comphelper;
+
+/// w:sdt's w:dropDownList doesn't have width, so guess the size based on the longest string.
+static awt::Size lcl_getOptimalWidth(const StyleSheetTablePtr& pStyleSheet,
+ OUString const& rDefault, std::vector<OUString>& rItems)
+{
+ OUString aLongest = rDefault;
+ sal_Int32 nHeight = 0;
+ for (const OUString& rItem : rItems)
+ if (rItem.getLength() > aLongest.getLength())
+ aLongest = rItem;
+
+ MapMode aMap(MapUnit::Map100thMM);
+ OutputDevice* pOut = Application::GetDefaultDevice();
+ pOut->Push(vcl::PushFlags::FONT | vcl::PushFlags::MAPMODE);
+
+ PropertyMapPtr pDefaultCharProps = pStyleSheet->GetDefaultCharProps();
+ vcl::Font aFont(pOut->GetFont());
+ std::optional<PropertyMap::Property> aFontName
+ = pDefaultCharProps->getProperty(PROP_CHAR_FONT_NAME);
+ if (aFontName)
+ aFont.SetFamilyName(aFontName->second.get<OUString>());
+ std::optional<PropertyMap::Property> aHeight = pDefaultCharProps->getProperty(PROP_CHAR_HEIGHT);
+ if (aHeight)
+ {
+ nHeight = aHeight->second.get<double>() * 35; // points -> mm100
+ aFont.SetFontSize(Size(0, nHeight));
+ }
+ pOut->SetFont(aFont);
+ pOut->SetMapMode(aMap);
+ sal_Int32 nWidth = pOut->GetTextWidth(aLongest);
+
+ pOut->Pop();
+
+ // Border: see PDFWriterImpl::drawFieldBorder(), border size is font height / 4,
+ // so additional width / height needed is height / 2.
+ sal_Int32 nBorder = nHeight / 2;
+
+ // Width: space for the text + the square having the dropdown arrow.
+ return { nWidth + nBorder + nHeight, nHeight + nBorder };
+}
+
+SdtHelper::SdtHelper(DomainMapper_Impl& rDM_Impl,
+ css::uno::Reference<css::uno::XComponentContext> const& xContext)
+ : m_rDM_Impl(rDM_Impl)
+ , m_xComponentContext(xContext)
+ , m_aControlType(SdtControlType::unknown)
+ , m_bHasElements(false)
+ , m_bOutsideAParagraph(false)
+ , m_bPropertiesXMLsLoaded(false)
+{
+}
+
+SdtHelper::~SdtHelper() = default;
+
+void SdtHelper::loadPropertiesXMLs()
+{
+ // Initialize properties xml storage (m_xPropertiesXMLs)
+ uno::Reference<uno::XInterface> xTemp
+ = m_xComponentContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.document.OOXMLDocumentPropertiesImporter", m_xComponentContext);
+ uno::Reference<document::XOOXMLDocumentPropertiesImporter> xImporter(xTemp, uno::UNO_QUERY);
+ if (!xImporter.is())
+ return;
+
+ uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder(
+ xml::dom::DocumentBuilder::create(m_xComponentContext));
+ if (!xDomBuilder.is())
+ return;
+
+ // Load core properties
+ try
+ {
+ auto xCorePropsStream = xImporter->getCorePropertiesStream(m_rDM_Impl.m_xDocumentStorage);
+ m_xPropertiesXMLs.insert(
+ { OUString("{6C3C8BC8-F283-45AE-878A-BAB7291924A1}"), // hardcoded id for core props
+ xDomBuilder->parse(xCorePropsStream) });
+ }
+ catch (const uno::Exception&)
+ {
+ SAL_WARN("writerfilter",
+ "SdtHelper::loadPropertiesXMLs: failed loading core properties XML");
+ }
+
+ // Load extended properties
+ try
+ {
+ auto xExtPropsStream
+ = xImporter->getExtendedPropertiesStream(m_rDM_Impl.m_xDocumentStorage);
+ m_xPropertiesXMLs.insert(
+ { OUString("{6668398D-A668-4E3E-A5EB-62B293D839F1}"), // hardcoded id for extended props
+ xDomBuilder->parse(xExtPropsStream) });
+ }
+ catch (const uno::Exception&)
+ {
+ SAL_WARN("writerfilter",
+ "SdtHelper::loadPropertiesXMLs: failed loading extended properties XML");
+ }
+
+ // TODO: some other property items?
+
+ // Add custom XMLs
+ uno::Sequence<uno::Reference<xml::dom::XDocument>> aCustomXmls
+ = m_rDM_Impl.getDocumentReference()->getCustomXmlDomList();
+ uno::Sequence<uno::Reference<xml::dom::XDocument>> aCustomXmlProps
+ = m_rDM_Impl.getDocumentReference()->getCustomXmlDomPropsList();
+ if (aCustomXmls.getLength())
+ {
+ uno::Reference<XXPathAPI> xXpathAPI = XPathAPI::create(m_xComponentContext);
+ xXpathAPI->registerNS("ds",
+ "http://schemas.openxmlformats.org/officeDocument/2006/customXml");
+ sal_Int32 nItem = 0;
+ // Hereby we assume that items from getCustomXmlDomList() and getCustomXmlDomPropsList()
+ // are matching each other:
+ // item1.xml -> itemProps1.xml, item2.xml -> itemProps2.xml
+ // This does works practically, but is it true in general?
+ for (const auto& xDoc : aCustomXmls)
+ {
+ // Retrieve storeid from properties xml
+ OUString aStoreId;
+ uno::Reference<XXPathObject> xResult
+ = xXpathAPI->eval(aCustomXmlProps[nItem], "string(/ds:datastoreItem/@ds:itemID)");
+
+ if (xResult.is() && xResult->getString().getLength())
+ {
+ aStoreId = xResult->getString();
+ }
+ else
+ {
+ SAL_WARN("writerfilter",
+ "SdtHelper::loadPropertiesXMLs: can't fetch storeid for custom doc!");
+ }
+
+ m_xPropertiesXMLs.insert({ aStoreId, xDoc });
+ nItem++;
+ }
+ }
+
+ m_bPropertiesXMLsLoaded = true;
+}
+
+static void lcl_registerNamespaces(std::u16string_view sNamespaceString,
+ const uno::Reference<XXPathAPI>& xXPathAPI)
+{
+ // Split namespaces and register it in XPathAPI
+ auto aNamespaces = string::split(sNamespaceString, ' ');
+ for (const auto& sNamespace : aNamespaces)
+ {
+ // Here we have just one namespace in format "xmlns:ns0='http://someurl'"
+ auto aNamespace = string::split(sNamespace, '=');
+ if (aNamespace.size() < 2)
+ {
+ SAL_WARN("writerfilter",
+ "SdtHelper::getValueFromDataBinding: invalid namespace: " << sNamespace);
+ continue;
+ }
+
+ auto aNamespaceId = string::split(aNamespace[0], ':');
+ if (aNamespaceId.size() < 2)
+ {
+ SAL_WARN("writerfilter",
+ "SdtHelper::getValueFromDataBinding: invalid namespace: " << aNamespace[0]);
+ continue;
+ }
+
+ OUString sNamespaceURL = aNamespace[1];
+ sNamespaceURL = string::strip(sNamespaceURL, ' ');
+ sNamespaceURL = string::strip(sNamespaceURL, '\'');
+
+ xXPathAPI->registerNS(aNamespaceId[1], sNamespaceURL);
+ }
+}
+
+std::optional<OUString> SdtHelper::getValueFromDataBinding()
+{
+ // No xpath - nothing to do
+ if (m_sDataBindingXPath.isEmpty())
+ return {};
+
+ // Load properties XMLs
+ if (!m_bPropertiesXMLsLoaded)
+ loadPropertiesXMLs();
+
+ uno::Reference<XXPathAPI> xXpathAPI = XPathAPI::create(m_xComponentContext);
+
+ lcl_registerNamespaces(m_sDataBindingPrefixMapping, xXpathAPI);
+
+ // Find storage by store id and eval xpath there
+ const auto& aSourceIt = m_xPropertiesXMLs.find(m_sDataBindingStoreItemID);
+ if (aSourceIt != m_xPropertiesXMLs.end())
+ {
+ uno::Reference<XXPathObject> xResult
+ = xXpathAPI->eval(aSourceIt->second, m_sDataBindingXPath);
+
+ if (xResult.is() && xResult->getNodeList() && xResult->getNodeList()->getLength()
+ && xResult->getString().getLength())
+ {
+ return xResult->getString();
+ }
+ }
+
+ // Nothing found? Try to iterate storages and eval xpath
+ for (const auto& aSource : m_xPropertiesXMLs)
+ {
+ uno::Reference<XXPathObject> xResult = xXpathAPI->eval(aSource.second, m_sDataBindingXPath);
+
+ if (xResult.is() && xResult->getNodeList() && xResult->getNodeList()->getLength()
+ && xResult->getString().getLength())
+ {
+ return xResult->getString();
+ }
+ }
+
+ // No data
+ return {};
+}
+
+void SdtHelper::createDropDownControl()
+{
+ assert(getControlType() == SdtControlType::dropDown);
+
+ const bool bDropDown
+ = officecfg::Office::Writer::Filter::Import::DOCX::ImportComboBoxAsDropDown::get();
+ const OUString aDefaultText = m_aSdtTexts.makeStringAndClear();
+
+ if (bDropDown)
+ {
+ // create field
+ uno::Reference<css::text::XTextField> xControlModel(
+ m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.DropDown"),
+ uno::UNO_QUERY);
+
+ const auto it = std::find_if(
+ m_aDropDownItems.begin(), m_aDropDownItems.end(),
+ [aDefaultText](const OUString& item) -> bool { return !item.compareTo(aDefaultText); });
+
+ if (m_aDropDownItems.end() == it)
+ {
+ m_aDropDownItems.push_back(aDefaultText);
+ }
+
+ // set properties
+ uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("SelectedItem", uno::Any(aDefaultText));
+ xPropertySet->setPropertyValue("Items",
+ uno::Any(comphelper::containerToSequence(m_aDropDownItems)));
+
+ // add it into document
+ m_rDM_Impl.appendTextContent(xControlModel, uno::Sequence<beans::PropertyValue>());
+
+ m_bHasElements = true;
+ }
+ else
+ {
+ // create control
+ uno::Reference<awt::XControlModel> xControlModel(
+ m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.form.component.ComboBox"),
+ uno::UNO_QUERY);
+
+ // set properties
+ uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("DefaultText", uno::Any(aDefaultText));
+ xPropertySet->setPropertyValue("Dropdown", uno::Any(true));
+ xPropertySet->setPropertyValue("StringItemList",
+ uno::Any(comphelper::containerToSequence(m_aDropDownItems)));
+
+ // add it into document
+ createControlShape(
+ lcl_getOptimalWidth(m_rDM_Impl.GetStyleSheetTable(), aDefaultText, m_aDropDownItems),
+ xControlModel, uno::Sequence<beans::PropertyValue>());
+ }
+
+ // clean up
+ clear();
+}
+
+void SdtHelper::createPlainTextControl()
+{
+ assert(getControlType() == SdtControlType::plainText);
+
+ OUString aDefaultText = m_aSdtTexts.makeStringAndClear();
+
+ // create field
+ uno::Reference<css::text::XTextField> xControlModel(
+ m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.Input"),
+ uno::UNO_QUERY);
+
+ // set properties
+ uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
+
+ std::optional<OUString> oData = getValueFromDataBinding();
+ if (oData.has_value())
+ aDefaultText = *oData;
+
+ xPropertySet->setPropertyValue("Content", uno::Any(aDefaultText));
+
+ // add it into document
+ m_rDM_Impl.appendTextContent(xControlModel, uno::Sequence<beans::PropertyValue>());
+
+ // Store all unused sdt parameters from grabbag
+ xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG,
+ uno::Any(getInteropGrabBagAndClear()));
+
+ // clean up
+ clear();
+}
+
+void SdtHelper::createDateContentControl()
+{
+ if (!m_xDateFieldStartRange.is())
+ return;
+
+ uno::Reference<text::XTextCursor> xCrsr;
+ if (m_rDM_Impl.HasTopText())
+ {
+ uno::Reference<text::XTextAppend> xTextAppend = m_rDM_Impl.GetTopTextAppend();
+ if (xTextAppend.is())
+ {
+ xCrsr = xTextAppend->createTextCursorByRange(xTextAppend);
+ }
+ }
+ if (!xCrsr.is())
+ return;
+
+ try
+ {
+ xCrsr->gotoRange(m_xDateFieldStartRange, false);
+ bool bIsInTable = (m_rDM_Impl.hasTableManager() && m_rDM_Impl.getTableManager().isInTable())
+ || (m_rDM_Impl.m_nTableDepth > 0);
+ if (bIsInTable)
+ xCrsr->goRight(1, false);
+ xCrsr->gotoEnd(true);
+ }
+ catch (uno::Exception&)
+ {
+ OSL_ENSURE(false, "Cannot get the right text range for date field");
+ return;
+ }
+
+ uno::Reference<uno::XInterface> xFieldInterface
+ = m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.Fieldmark");
+ uno::Reference<text::XFormField> xFormField(xFieldInterface, uno::UNO_QUERY);
+ uno::Reference<text::XTextContent> xToInsert(xFormField, uno::UNO_QUERY);
+ if (!(xFormField.is() && xToInsert.is()))
+ return;
+
+ xToInsert->attach(uno::Reference<text::XTextRange>(xCrsr, uno::UNO_QUERY_THROW));
+ xFormField->setFieldType(ODF_FORMDATE);
+ uno::Reference<container::XNameContainer> xNameCont = xFormField->getParameters();
+ if (xNameCont.is())
+ {
+ OUString sDateFormat = m_sDateFormat.makeStringAndClear();
+
+ // Replace quotation mark used for marking static strings in date format
+ sDateFormat = sDateFormat.replaceAll("'", "\"");
+ xNameCont->insertByName(ODF_FORMDATE_DATEFORMAT, uno::Any(sDateFormat));
+ xNameCont->insertByName(ODF_FORMDATE_DATEFORMAT_LANGUAGE,
+ uno::Any(m_sLocale.makeStringAndClear()));
+ }
+ OUString sFullDate = m_sDate.makeStringAndClear();
+
+ std::optional<OUString> oData = getValueFromDataBinding();
+ if (oData.has_value())
+ sFullDate = *oData;
+
+ if (!sFullDate.isEmpty())
+ {
+ sal_Int32 nTimeSep = sFullDate.indexOf("T");
+ if (nTimeSep != -1)
+ sFullDate = sFullDate.copy(0, nTimeSep);
+ xNameCont->insertByName(ODF_FORMDATE_CURRENTDATE, uno::Any(sFullDate));
+ }
+
+ uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(m_rDM_Impl.GetTextDocument(),
+ uno::UNO_QUERY);
+ uno::Reference<util::XRefreshable> xRefreshable(xTextFieldsSupplier->getTextFields(),
+ uno::UNO_QUERY);
+ xRefreshable->refresh();
+
+ // Store all unused sdt parameters from grabbag
+ xNameCont->insertByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG,
+ uno::Any(getInteropGrabBagAndClear()));
+
+ clear();
+}
+
+void SdtHelper::createControlShape(awt::Size aSize,
+ uno::Reference<awt::XControlModel> const& xControlModel,
+ const uno::Sequence<beans::PropertyValue>& rGrabBag)
+{
+ uno::Reference<drawing::XControlShape> xControlShape(
+ m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.drawing.ControlShape"),
+ uno::UNO_QUERY);
+ xControlShape->setSize(aSize);
+ xControlShape->setControl(xControlModel);
+
+ uno::Reference<beans::XPropertySet> xPropertySet(xControlShape, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("VertOrient", uno::Any(text::VertOrientation::CENTER));
+
+ if (rGrabBag.hasElements())
+ xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, uno::Any(rGrabBag));
+
+ uno::Reference<text::XTextContent> xTextContent(xControlShape, uno::UNO_QUERY);
+ m_rDM_Impl.appendTextContent(xTextContent, uno::Sequence<beans::PropertyValue>());
+ m_bHasElements = true;
+}
+
+void SdtHelper::appendToInteropGrabBag(const beans::PropertyValue& rValue)
+{
+ m_aGrabBag.push_back(rValue);
+}
+
+uno::Sequence<beans::PropertyValue> SdtHelper::getInteropGrabBagAndClear()
+{
+ uno::Sequence<beans::PropertyValue> aRet = comphelper::containerToSequence(m_aGrabBag);
+ m_aGrabBag.clear();
+ return aRet;
+}
+
+bool SdtHelper::isInteropGrabBagEmpty() const { return m_aGrabBag.empty(); }
+
+sal_Int32 SdtHelper::getInteropGrabBagSize() const { return m_aGrabBag.size(); }
+
+bool SdtHelper::containedInInteropGrabBag(const OUString& rValueName)
+{
+ return std::any_of(
+ m_aGrabBag.begin(), m_aGrabBag.end(),
+ [&rValueName](const beans::PropertyValue& i) { return i.Name == rValueName; });
+}
+
+void SdtHelper::SetShowingPlcHdr() { m_bShowingPlcHdr = true; }
+
+bool SdtHelper::GetShowingPlcHdr() const { return m_bShowingPlcHdr; }
+
+void SdtHelper::SetChecked() { m_bChecked = true; }
+
+bool SdtHelper::GetChecked() const { return m_bChecked; }
+
+void SdtHelper::SetCheckedState(const OUString& rCheckedState) { m_aCheckedState = rCheckedState; }
+
+OUString SdtHelper::GetCheckedState() const { return m_aCheckedState; }
+
+void SdtHelper::SetUncheckedState(const OUString& rUncheckedState)
+{
+ m_aUncheckedState = rUncheckedState;
+}
+
+OUString SdtHelper::GetUncheckedState() const { return m_aUncheckedState; }
+
+void SdtHelper::clear()
+{
+ m_aDropDownItems.clear();
+ m_aDropDownDisplayTexts.clear();
+ setControlType(SdtControlType::unknown);
+ m_sDataBindingPrefixMapping.clear();
+ m_sDataBindingXPath.clear();
+ m_sDataBindingStoreItemID.clear();
+ m_aGrabBag.clear();
+ m_bShowingPlcHdr = false;
+ m_bChecked = false;
+ m_aCheckedState.clear();
+ m_aUncheckedState.clear();
+}
+
+void SdtHelper::SetPlaceholderDocPart(const OUString& rPlaceholderDocPart)
+{
+ m_aPlaceholderDocPart = rPlaceholderDocPart;
+}
+
+OUString SdtHelper::GetPlaceholderDocPart() const { return m_aPlaceholderDocPart; }
+
+void SdtHelper::SetColor(const OUString& rColor) { m_aColor = rColor; }
+
+OUString SdtHelper::GetColor() const { return m_aColor; }
+
+} // namespace writerfilter::dmapper
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */