summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiklos Vajna <vmiklos@collabora.com>2022-11-22 14:30:08 +0100
committerMiklos Vajna <vmiklos@collabora.com>2022-11-22 19:36:37 +0100
commit100c914d44ae8f362924fe567d7d41d0033ae8ad (patch)
tree7b598ced8fdcf9c662c6c432c6a954aa383e82b8
parentb71303fe1b06c1711ad80e5a0269d41ccaaad8e1 (diff)
DOCX filter: fix <w:id> creating both grab-bag and content control for <w:sdt>
Exporting the DOCX bugdoc back to DOCX resulted in a document that can't be opened in Word. Examining the output, the problem is that the document had 2 inline <w:sdt> elements with <w:id>, and we mapped such <w:sdt> elements to both grab-bags and content controls, leading to duplicate <w:sdt> elements on export. This is schema-valid, but it goes against the intention of the spec and Word can't open it. The initial fix was just a writerfilter/ tweak to avoid grab-bagging <w:id> for inline <w:sdt>, but CppunitTest_sw_ooxmlexport4's testSimpleSdts points out that in other cases we already require preserving <w:id>. Fix the problem by storing <w:id> in the content control, which is essentially a subset of <https://gerrit.libreoffice.org/c/core/+/142818>. Thanks to Justin Luth! - he prototyped the DOCX filter for <w:id>. Change-Id: I9f002b770049ce8be30253d0b39410d9a58981dd Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143117 Reviewed-by: Miklos Vajna <vmiklos@collabora.com> Tested-by: Jenkins
-rw-r--r--offapi/com/sun/star/text/ContentControl.idl6
-rw-r--r--sw/inc/formatcontentcontrol.hxx7
-rw-r--r--sw/inc/unoprnms.hxx1
-rw-r--r--sw/qa/extras/ooxmlexport/data/sdt-duplicated-id.docxbin0 -> 11940 bytes
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport18.cxx17
-rw-r--r--sw/source/core/txtnode/attrcontentcontrol.cxx2
-rw-r--r--sw/source/core/unocore/unocontentcontrol.cxx29
-rw-r--r--sw/source/core/unocore/unomap1.cxx1
-rw-r--r--sw/source/filter/ww8/docxattributeoutput.cxx6
-rw-r--r--writerfilter/source/dmapper/DomainMapper.cxx6
-rw-r--r--writerfilter/source/dmapper/DomainMapper_Impl.cxx5
-rw-r--r--writerfilter/source/dmapper/SdtHelper.cxx5
-rw-r--r--writerfilter/source/dmapper/SdtHelper.hxx6
13 files changed, 91 insertions, 0 deletions
diff --git a/offapi/com/sun/star/text/ContentControl.idl b/offapi/com/sun/star/text/ContentControl.idl
index 7bdb39fa5101..6abcc79fd204 100644
--- a/offapi/com/sun/star/text/ContentControl.idl
+++ b/offapi/com/sun/star/text/ContentControl.idl
@@ -121,6 +121,12 @@ service ContentControl
@since LibreOffice 7.5
*/
[optional, property, readonly] string DateString;
+
+ /** A unique numeric id, used by macros to identify a specific control.
+
+ @since LibreOffice 7.5
+ */
+ [optional, property] long Id;
};
diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx
index 2d5676a1e3dd..3ac6848e388f 100644
--- a/sw/inc/formatcontentcontrol.hxx
+++ b/sw/inc/formatcontentcontrol.hxx
@@ -176,6 +176,9 @@ class SW_DLLPUBLIC SwContentControl : public sw::BroadcastingModify
/// The tag: just remembered.
OUString m_aTag;
+ /// The id: just remembered.
+ sal_Int32 m_nId = 0;
+
/// Stores a list item index, in case the doc model is not yet updated.
std::optional<size_t> m_oSelectedListItem;
@@ -342,6 +345,10 @@ public:
const OUString& GetTag() const { return m_aTag; }
+ void SetId(sal_Int32 nId) { m_nId = nId; }
+
+ sal_Int32 GetId() const { return m_nId; }
+
void SetReadWrite(bool bReadWrite) { m_bReadWrite = bReadWrite; }
bool GetReadWrite() const { return m_bReadWrite; }
diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index 4249949add6d..69ef10b881f9 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -893,6 +893,7 @@
#define UNO_NAME_COLOR "Color"
#define UNO_NAME_ALIAS "Alias"
#define UNO_NAME_TAG "Tag"
+#define UNO_NAME_ID "Id"
#define UNO_NAME_DATE_STRING "DateString"
#endif
diff --git a/sw/qa/extras/ooxmlexport/data/sdt-duplicated-id.docx b/sw/qa/extras/ooxmlexport/data/sdt-duplicated-id.docx
new file mode 100644
index 000000000000..d21894df3007
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/sdt-duplicated-id.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
index af42616d778d..71d28b313d2a 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
@@ -155,6 +155,23 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf150966_regularInset)
assertXPathAttrs(pXmlDoc, "//wps:bodyPr", { { "tIns", "179640" }, { "bIns", "360000" } });
}
+CPPUNIT_TEST_FIXTURE(Test, testSdtDuplicatedId)
+{
+ // Given a document with 2 inline <w:sdt>, with each a <w:id>:
+ createSwDoc("sdt-duplicated-id.docx");
+
+ // When exporting that back to DOCX:
+ save("Office Open XML Text");
+
+ // Then make sure we write 2 <w:sdt> and no duplicates:
+ xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2
+ // - Actual : 4
+ // i.e. grab-bags introduced 2 unwanted duplicates.
+ assertXPath(pXmlDoc, "//w:sdt", 2);
+}
+
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx
index 965c6adf8476..33ee4faa1e07 100644
--- a/sw/source/core/txtnode/attrcontentcontrol.cxx
+++ b/sw/source/core/txtnode/attrcontentcontrol.cxx
@@ -435,6 +435,8 @@ void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("alias"),
BAD_CAST(m_aAlias.toUtf8().getStr()));
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("tag"), BAD_CAST(m_aTag.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"),
+ BAD_CAST(OString::number(m_nId).getStr()));
if (!m_aListItems.empty())
{
diff --git a/sw/source/core/unocore/unocontentcontrol.cxx b/sw/source/core/unocore/unocontentcontrol.cxx
index 175e12f35165..1a3092e21c52 100644
--- a/sw/source/core/unocore/unocontentcontrol.cxx
+++ b/sw/source/core/unocore/unocontentcontrol.cxx
@@ -178,6 +178,7 @@ public:
OUString m_aColor;
OUString m_aAlias;
OUString m_aTag;
+ sal_Int32 m_nId;
Impl(SwXContentControl& rThis, SwDoc& rDoc, SwContentControl* pContentControl,
uno::Reference<text::XText> xParentText, std::unique_ptr<const TextRangeList_t> pPortions)
@@ -195,6 +196,7 @@ public:
, m_bPlainText(false)
, m_bComboBox(false)
, m_bDropDown(false)
+ , m_nId(0)
{
if (m_pContentControl)
{
@@ -490,6 +492,7 @@ void SwXContentControl::AttachImpl(const uno::Reference<text::XTextRange>& xText
pContentControl->SetColor(m_pImpl->m_aColor);
pContentControl->SetAlias(m_pImpl->m_aAlias);
pContentControl->SetTag(m_pImpl->m_aTag);
+ pContentControl->SetId(m_pImpl->m_nId);
SwFormatContentControl aContentControl(pContentControl, nWhich);
bool bSuccess
@@ -965,6 +968,21 @@ void SAL_CALL SwXContentControl::setPropertyValue(const OUString& rPropertyName,
}
}
}
+ else if (rPropertyName == UNO_NAME_ID)
+ {
+ sal_Int32 nValue = 0;
+ if (rValue >>= nValue)
+ {
+ if (m_pImpl->m_bIsDescriptor)
+ {
+ m_pImpl->m_nId = nValue;
+ }
+ else
+ {
+ m_pImpl->m_pContentControl->SetId(nValue);
+ }
+ }
+ }
else
{
throw beans::UnknownPropertyException();
@@ -1216,6 +1234,17 @@ uno::Any SAL_CALL SwXContentControl::getPropertyValue(const OUString& rPropertyN
aRet <<= m_pImpl->m_pContentControl->GetDateString();
}
}
+ else if (rPropertyName == UNO_NAME_ID)
+ {
+ if (m_pImpl->m_bIsDescriptor)
+ {
+ aRet <<= m_pImpl->m_nId;
+ }
+ else
+ {
+ aRet <<= m_pImpl->m_pContentControl->GetId();
+ }
+ }
else
{
throw beans::UnknownPropertyException();
diff --git a/sw/source/core/unocore/unomap1.cxx b/sw/source/core/unocore/unomap1.cxx
index 92f41e52b7a2..7adbfdba060c 100644
--- a/sw/source/core/unocore/unomap1.cxx
+++ b/sw/source/core/unocore/unomap1.cxx
@@ -1012,6 +1012,7 @@ o3tl::span<const SfxItemPropertyMapEntry> SwUnoPropertyMapProvider::GetContentCo
{ u"" UNO_NAME_COLOR, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 },
{ u"" UNO_NAME_ALIAS, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 },
{ u"" UNO_NAME_TAG, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 },
+ { u"" UNO_NAME_ID, 0, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0 },
{ u"" UNO_NAME_DATE_STRING, 0, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0 },
};
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index 26ed9d57a95b..e245b22caf4a 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -2373,6 +2373,12 @@ void DocxAttributeOutput::WriteContentControlStart()
m_pContentControl->GetTag());
}
+ if (m_pContentControl->GetId())
+ {
+ m_pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val),
+ OString::number(m_pContentControl->GetId()));
+ }
+
if (m_pContentControl->GetShowingPlaceHolder())
{
m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx
index b5e45b7c2734..94422d8a6a98 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -2865,6 +2865,12 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext )
break;
}
+ if (nSprmId == NS_ooxml::LN_CT_SdtPr_id)
+ {
+ m_pImpl->m_pSdtHelper->SetId(nIntValue);
+ break;
+ }
+
if (nSprmId == NS_ooxml::LN_CT_SdtPr_checkbox)
{
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::checkBox);
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 3cae3962811e..6477e58bbdaf 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -950,6 +950,11 @@ void DomainMapper_Impl::PopSdt()
uno::Any(m_pSdtHelper->GetTag()));
}
+ if (m_pSdtHelper->GetId())
+ {
+ xContentControlProps->setPropertyValue("Id", uno::Any(m_pSdtHelper->GetId()));
+ }
+
if (m_pSdtHelper->getControlType() == SdtControlType::checkBox)
{
xContentControlProps->setPropertyValue("Checkbox", uno::Any(true));
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx b/writerfilter/source/dmapper/SdtHelper.cxx
index 6f08c81559cf..a6a6b6cb08f8 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -520,6 +520,7 @@ void SdtHelper::clear()
m_aColor.clear();
m_aAlias.clear();
m_aTag.clear();
+ m_nId = 0;
}
void SdtHelper::SetPlaceholderDocPart(const OUString& rPlaceholderDocPart)
@@ -541,6 +542,10 @@ void SdtHelper::SetTag(const OUString& rTag) { m_aTag = rTag; }
const OUString& SdtHelper::GetTag() const { return m_aTag; }
+void SdtHelper::SetId(sal_Int32 nId) { m_nId = nId; }
+
+sal_Int32 SdtHelper::GetId() const { return m_nId; }
+
} // namespace writerfilter::dmapper
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/writerfilter/source/dmapper/SdtHelper.hxx b/writerfilter/source/dmapper/SdtHelper.hxx
index dc1a3172e364..736476e63c2f 100644
--- a/writerfilter/source/dmapper/SdtHelper.hxx
+++ b/writerfilter/source/dmapper/SdtHelper.hxx
@@ -132,6 +132,9 @@ class SdtHelper final : public virtual SvRefBase
/// <w:sdtPr>'s <w:tag w:val="...">.
OUString m_aTag;
+ /// <w:sdtPr>'s <w:id w:val="...">.
+ sal_Int32 m_nId = 0;
+
public:
explicit SdtHelper(DomainMapper_Impl& rDM_Impl,
css::uno::Reference<css::uno::XComponentContext> xContext);
@@ -217,6 +220,9 @@ public:
void SetTag(const OUString& rTag);
const OUString& GetTag() const;
+ void SetId(sal_Int32 nId);
+ sal_Int32 GetId() const;
+
std::optional<OUString> getValueFromDataBinding();
};