summaryrefslogtreecommitdiff
path: root/writerfilter
diff options
context:
space:
mode:
authorJaume Pujantell <jaume.pujantell@collabora.com>2023-09-13 08:58:21 +0200
committerJaume Pujantell <jaume.pujantell@collabora.com>2023-10-11 15:19:58 +0200
commit5082d50d24c3fec4487c724a15eb0d54a82ecd0d (patch)
tree8cae9bc61647c7ba87b53c28d8d4e11269c0fe2c /writerfilter
parentaf9c3ed6f748781f2a77e676ffe740992788969b (diff)
writerfilter: use content controls for text in block SDTs
Text inside block SDTs was shown as Text Fields wich ignored properties such as alias and formatting. Now those texts are imported as content controls like in the case of run SDTs. Added the ability for content controls to remember and export the "multiline" property of block SDT text. Some existing tests have been changed due to some different export results. Change-Id: Ice1eb4ca6dd53c99d5abb239371f8ac896c3b6e4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/156867 Reviewed-by: Miklos Vajna <vmiklos@collabora.com> Tested-by: Jenkins
Diffstat (limited to 'writerfilter')
-rw-r--r--writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx30
-rw-r--r--writerfilter/qa/cppunittests/dmapper/data/sdt-block-text.docxbin0 -> 13467 bytes
-rw-r--r--writerfilter/source/dmapper/DomainMapper.cxx30
-rw-r--r--writerfilter/source/dmapper/SdtHelper.cxx109
-rw-r--r--writerfilter/source/dmapper/SdtHelper.hxx13
5 files changed, 156 insertions, 26 deletions
diff --git a/writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx b/writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx
index 97f856044d6c..7cafcd19f280 100644
--- a/writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx
+++ b/writerfilter/qa/cppunittests/dmapper/DomainMapper.cxx
@@ -119,6 +119,36 @@ CPPUNIT_TEST_FIXTURE(Test, testFloattableThenTable)
// Make sure the anchor text is the body text, not some cell.
CPPUNIT_ASSERT_EQUAL(xBodyText, xAnchor->getText());
}
+
+CPPUNIT_TEST_FIXTURE(Test, testSdtBlockText)
+{
+ // Given a document with a block SDT that only contains text:
+ loadFromURL(u"sdt-block-text.docx");
+
+ // Then make sure that the text inside the SDT is imported as a content control:
+ uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
+ uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xTextDocument->getText(),
+ uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration();
+ uno::Reference<container::XEnumerationAccess> xPara(xParaEnum->nextElement(), uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xPortionEnum = xPara->createEnumeration();
+ uno::Reference<beans::XPropertySet> xPortion(xPortionEnum->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xPortion->getPropertyValue("TextPortionType") >>= aTextPortionType;
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: ContentControl
+ // - Actual : TextField
+ // i.e. the SDT was imported as a text field, not as a content control.
+ CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aTextPortionType);
+
+ // Make sure the properties are imported
+ uno::Reference<text::XTextContent> xContentControl;
+ xPortion->getPropertyValue("ContentControl") >>= xContentControl;
+ uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
+ OUString aAlias;
+ xContentControlProps->getPropertyValue("Alias") >>= aAlias;
+ CPPUNIT_ASSERT_EQUAL(OUString("myalias"), aAlias);
+}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/writerfilter/qa/cppunittests/dmapper/data/sdt-block-text.docx b/writerfilter/qa/cppunittests/dmapper/data/sdt-block-text.docx
new file mode 100644
index 000000000000..2dfbc4a3284a
--- /dev/null
+++ b/writerfilter/qa/cppunittests/dmapper/data/sdt-block-text.docx
Binary files differ
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx
index 726730ad3076..f015b79520bf 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -1132,7 +1132,7 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
}
if (nName == NS_ooxml::LN_CT_SdtRun_sdtContent)
{
- if (m_pImpl->GetSdtStarts().empty() && !m_pImpl->m_pSdtHelper->getSdtTexts().isEmpty())
+ if (m_pImpl->GetSdtStarts().empty() && m_pImpl->m_pSdtHelper->hasUnusedText())
{
// A non-inline SDT is already started, first convert that to a field and only
// then map the inline SDT to a content control.
@@ -1191,6 +1191,9 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
m_pImpl->PushSdt();
break;
}
+ if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText
+ && GetCurrentTextRange().is())
+ m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
m_pImpl->SetSdt(true);
}
break;
@@ -2977,7 +2980,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext )
{
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::datePicker);
resolveSprmProps(*this, rSprm);
- m_pImpl->m_pSdtHelper->setDateFieldStartRange(GetCurrentTextRange()->getEnd());
+ m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
}
break;
case NS_ooxml::LN_CT_SdtDate_dateFormat:
@@ -4128,13 +4131,13 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, size_t len)
}
else if (m_pImpl->m_pSdtHelper->GetSdtType() != NS_ooxml::LN_CT_SdtRun_sdtContent && m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText)
{
- m_pImpl->m_pSdtHelper->getSdtTexts().append(sText);
if (bNewLine)
{
m_pImpl->m_pSdtHelper->createPlainTextControl();
finishParagraph();
+ m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
+ return;
}
- return;
}
else if (!m_pImpl->m_pSdtHelper->isInteropGrabBagEmpty())
{
@@ -4387,6 +4390,10 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, size_t len)
m_pImpl->clearDeferredBreaks();
}
+ bool bSdtBlockUnusedText
+ = m_pImpl->m_pSdtHelper->GetSdtType() != NS_ooxml::LN_CT_SdtRun_sdtContent
+ && m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText
+ && m_pImpl->m_pSdtHelper->hasUnusedText();
if (pContext && pContext->GetFootnote().is())
{
pContext->GetFootnote()->setLabel( sText );
@@ -4396,18 +4403,33 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, size_t len)
}
else if (m_pImpl->IsOpenFieldCommand() && !m_pImpl->IsForceGenericFields())
{
+ if (bSdtBlockUnusedText)
+ m_pImpl->m_pSdtHelper->createPlainTextControl();
m_pImpl->AppendFieldCommand(sText);
+ if (bSdtBlockUnusedText)
+ m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
}
else if( m_pImpl->IsOpenField() && m_pImpl->IsFieldResultAsString())
+ {
+ if (bSdtBlockUnusedText)
+ m_pImpl->m_pSdtHelper->createPlainTextControl();
/*depending on the success of the field insert operation this result will be
set at the field or directly inserted into the text*/
m_pImpl->AppendFieldResult(sText);
+ if (bSdtBlockUnusedText)
+ m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd());
+ }
else
{
if (pContext == nullptr)
pContext = new PropertyMap();
m_pImpl->appendTextPortion( sText, pContext );
+
+ if (m_pImpl->m_pSdtHelper->GetSdtType() == NS_ooxml::LN_CT_SdtBlock_sdtContent
+ && m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText
+ && !sText.isEmpty())
+ m_pImpl->m_pSdtHelper->setHasUnusedText(true);
}
}
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx b/writerfilter/source/dmapper/SdtHelper.cxx
index 041802147605..c7f99878e12d 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -83,6 +83,7 @@ SdtHelper::SdtHelper(DomainMapper_Impl& rDM_Impl,
: m_rDM_Impl(rDM_Impl)
, m_xComponentContext(std::move(xContext))
, m_aControlType(SdtControlType::unknown)
+ , m_bHasUnusedText(false)
, m_bHasElements(false)
, m_bOutsideAParagraph(false)
, m_bPropertiesXMLsLoaded(false)
@@ -334,31 +335,102 @@ void SdtHelper::createPlainTextControl()
{
assert(getControlType() == SdtControlType::plainText);
- OUString aDefaultText = m_aSdtTexts.makeStringAndClear();
+ if (!m_xFieldStartRange.is())
+ return;
- // create field
- uno::Reference<css::text::XTextField> xControlModel(
- m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.Input"),
- uno::UNO_QUERY);
+ uno::Reference<text::XTextCursor> xCrsr;
+ uno::Reference<text::XText> xText;
+ if (m_rDM_Impl.HasTopText())
+ {
+ uno::Reference<text::XTextAppend> xTextAppend = m_rDM_Impl.GetTopTextAppend();
+ if (xTextAppend.is())
+ {
+ xText = m_rDM_Impl.GetTopTextAppend()->getEnd()->getText();
+ xCrsr = xText->createTextCursorByRange(m_xFieldStartRange);
+ }
+ }
+ if (!xCrsr.is())
+ return;
- // set properties
- uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
+ try
+ {
+ bool bIsInTable = (m_rDM_Impl.hasTableManager() && m_rDM_Impl.getTableManager().isInTable())
+ != (m_rDM_Impl.m_nTableDepth > 0)
+ && m_rDM_Impl.GetIsDummyParaAddedForTableInSection();
+ if (bIsInTable)
+ xCrsr->goRight(1, false);
+ xCrsr->gotoEnd(true);
+ }
+ catch (uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("writerfilter.dmapper",
+ "Cannot get the right text range for date field");
+ return;
+ }
std::optional<OUString> oData = getValueFromDataBinding();
if (oData.has_value())
- aDefaultText = *oData;
+ xCrsr->setString(*oData);
- xPropertySet->setPropertyValue("Content", uno::Any(aDefaultText));
+ uno::Reference<text::XTextContent> xContentControl(
+ m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.ContentControl"),
+ uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
- PropertyMap aMap;
- aMap.InsertProps(m_rDM_Impl.GetTopContext());
+ for (const beans::PropertyValue& prop : getInteropGrabBagAndClear())
+ {
+ OUString sPropertyName;
+ if (prop.Name == "ooxml:CT_SdtPr_showingPlcHdr")
+ sPropertyName = "ShowingPlaceHolder";
+ else if (prop.Name == "ooxml:CT_SdtPr_alias")
+ sPropertyName = "Alias";
+ else if (prop.Name == "ooxml:CT_SdtPr_tag")
+ sPropertyName = "Tag";
+ else if (prop.Name == "ooxml:CT_SdtPr_id")
+ sPropertyName = "Id";
+ else if (prop.Name == "ooxml:CT_SdtPr_tabIndex")
+ sPropertyName = "TabIndex";
+ else if (prop.Name == "ooxml:CT_SdtPr_lock")
+ sPropertyName = "Lock";
+ else if (prop.Name == "ooxml:CT_SdtPlaceholder_docPart"
+ || prop.Name == "ooxml:CT_SdtPr_dataBinding" || prop.Name == "ooxml:CT_SdtPr_color"
+ || prop.Name == "ooxml:CT_SdtPr_appearance" || prop.Name == "ooxml:CT_SdtPr_text")
+ {
+ uno::Sequence<beans::PropertyValue> aInternalGrabBag;
+ prop.Value >>= aInternalGrabBag;
+ for (const beans::PropertyValue& internalProp : aInternalGrabBag)
+ {
+ if (internalProp.Name == "ooxml:CT_SdtPlaceholder_docPart_val")
+ sPropertyName = "PlaceholderDocPart";
+ else if (internalProp.Name == "ooxml:CT_DataBinding_prefixMappings")
+ sPropertyName = "DataBindingPrefixMappings";
+ else if (internalProp.Name == "ooxml:CT_DataBinding_xpath")
+ sPropertyName = "DataBindingXpath";
+ else if (internalProp.Name == "ooxml:CT_DataBinding_storeItemID")
+ sPropertyName = "DataBindingStoreItemID";
+ else if (internalProp.Name == "ooxml:CT_SdtAppearance_val")
+ sPropertyName = "Appearance";
+ else if (internalProp.Name == "ooxml:CT_SdtColor_val")
+ sPropertyName = "Color";
+ else if (internalProp.Name == "ooxml:CT_SdtText_multiLine")
+ sPropertyName = "MultiLine";
+ if (!sPropertyName.isEmpty())
+ {
+ xContentControlProps->setPropertyValue(sPropertyName, internalProp.Value);
+ }
+ sPropertyName.clear();
+ }
+ }
- // add it into document
- m_rDM_Impl.appendTextContent(xControlModel, aMap.GetPropertyValues());
+ if (!sPropertyName.isEmpty())
+ {
+ xContentControlProps->setPropertyValue(sPropertyName, prop.Value);
+ }
+ }
- // Store all unused sdt parameters from grabbag
- xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG,
- uno::Any(getInteropGrabBagAndClear()));
+ xContentControlProps->setPropertyValue("PlainText", uno::Any(true));
+
+ xText->insertTextContent(xCrsr, xContentControl, /*bAbsorb=*/true);
// clean up
clear();
@@ -366,7 +438,7 @@ void SdtHelper::createPlainTextControl()
void SdtHelper::createDateContentControl()
{
- if (!m_xDateFieldStartRange.is())
+ if (!m_xFieldStartRange.is())
return;
uno::Reference<text::XTextCursor> xCrsr;
@@ -383,7 +455,7 @@ void SdtHelper::createDateContentControl()
try
{
- xCrsr->gotoRange(m_xDateFieldStartRange, false);
+ xCrsr->gotoRange(m_xFieldStartRange, false);
// tdf#138093: Date selector reset, if placed inside table
// Modified to XOR relationship and adding dummy paragraph conditions
bool bIsInTable = (m_rDM_Impl.hasTableManager() && m_rDM_Impl.getTableManager().isInTable())
@@ -520,6 +592,7 @@ void SdtHelper::clear()
m_sDataBindingXPath.clear();
m_sDataBindingStoreItemID.clear();
m_aGrabBag.clear();
+ m_bHasUnusedText = false;
m_bShowingPlcHdr = false;
m_bChecked = false;
m_aCheckedState.clear();
diff --git a/writerfilter/source/dmapper/SdtHelper.hxx b/writerfilter/source/dmapper/SdtHelper.hxx
index 6803db16ef06..5db799bd1fd2 100644
--- a/writerfilter/source/dmapper/SdtHelper.hxx
+++ b/writerfilter/source/dmapper/SdtHelper.hxx
@@ -83,12 +83,14 @@ class SdtHelper final : public virtual SvRefBase
/// <w:dataBinding w:storeItemID="">
OUString m_sDataBindingStoreItemID;
- /// Start range of the date field
- css::uno::Reference<css::text::XTextRange> m_xDateFieldStartRange;
+ /// Start range of the date or plain text field
+ css::uno::Reference<css::text::XTextRange> m_xFieldStartRange;
/// Locale string as it comes from the ooxml document.
OUStringBuffer m_sLocale;
/// Grab bag to store unsupported SDTs, aiming to save them back on export.
std::vector<css::beans::PropertyValue> m_aGrabBag;
+ /// Has inserted texts for plain text control
+ bool m_bHasUnusedText;
bool m_bHasElements;
/// The last stored SDT element is outside paragraphs.
@@ -169,9 +171,9 @@ public:
void setDataBindingStoreItemID(const OUString& sValue) { m_sDataBindingStoreItemID = sValue; }
const OUString& GetDataBindingStoreItemID() const { return m_sDataBindingStoreItemID; }
- void setDateFieldStartRange(const css::uno::Reference<css::text::XTextRange>& xStartRange)
+ void setFieldStartRange(const css::uno::Reference<css::text::XTextRange>& xStartRange)
{
- m_xDateFieldStartRange = xStartRange;
+ m_xFieldStartRange = xStartRange;
}
OUStringBuffer& getLocale() { return m_sLocale; }
@@ -204,6 +206,9 @@ public:
bool containedInInteropGrabBag(const OUString& rValueName);
sal_Int32 getInteropGrabBagSize() const;
+ void setHasUnusedText(bool bHasUnusedText) { m_bHasUnusedText = bHasUnusedText; }
+ bool hasUnusedText() const { return m_bHasUnusedText; }
+
void SetShowingPlcHdr();
bool GetShowingPlcHdr() const;