diff --git a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
index a4f057d8d1e6..e03c3dd2d256 100644
--- a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng
@@ -4135,4 +4135,15 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx
index 1fc309a23f95..dfc1eab8782a 100644
--- a/sw/source/core/doc/docredln.cxx
+++ b/sw/source/core/doc/docredln.cxx
@@ -1585,6 +1585,24 @@ void SwRangeRedline::CallDisplayFunc(size_t nMyPos)
ShowOriginal(0, nMyPos);
}
+namespace
+{
+RedlineType GetRedlineTypeIgnoringAdditonalFormat(const SwRangeRedline& rRedline)
+{
+ RedlineType eType = rRedline.GetType();
+
+ if (eType == RedlineType::Format && rRedline.GetStackCount() > 1
+ && rRedline.GetType(1) == RedlineType::Delete)
+ {
+ // Consider format-on-delete the same as simple delete, so the range gets moved to the
+ // "Deleted Change Tracking content" toplevel section from body content during file save.
+ eType = RedlineType::Delete;
+ }
+
+ return eType;
+}
+}
+
void SwRangeRedline::Show(sal_uInt16 nLoop, size_t nMyPos, bool bForced)
{
SwDoc& rDoc = GetDoc();
@@ -1606,7 +1624,7 @@ void SwRangeRedline::Show(sal_uInt16 nLoop, size_t nMyPos, bool bForced)
rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
- switch( GetType() )
+ switch (GetRedlineTypeIgnoringAdditonalFormat(*this))
{
case RedlineType::Insert: // Content has been inserted
m_bIsVisible = true;
@@ -1646,7 +1664,7 @@ void SwRangeRedline::Hide(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/)
rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
- switch( GetType() )
+ switch (GetRedlineTypeIgnoringAdditonalFormat(*this))
{
case RedlineType::Insert: // Content has been inserted
m_bIsVisible = true;
diff --git a/sw/source/core/unocore/unoredline.cxx b/sw/source/core/unocore/unoredline.cxx
index 31cbaf2f86c7..f9783e4cd318 100644
--- a/sw/source/core/unocore/unoredline.cxx
+++ b/sw/source/core/unocore/unoredline.cxx
@@ -176,6 +176,22 @@ static uno::Sequence lcl_GetSuccessorProperties(const SwRa
const SwRedlineData* pNext = rRedline.GetRedlineData().Next();
if(pNext)
{
+ uno::Reference xRedlineText;
+ if (pNext->GetType() == RedlineType::Delete)
+ {
+ // Something on delete: produce the XText for the underlying delete.
+ const SwNodeIndex* pNodeIdx = rRedline.GetContentIdx();
+ if (pNodeIdx
+ && (pNodeIdx->GetNode().EndOfSectionIndex() - pNodeIdx->GetNode().GetIndex())
+ > SwNodeOffset(1))
+ {
+ // We have at least one node between the start and end node, create the
+ // SwXRedlineText.
+ SwDoc& rDoc = rRedline.GetDoc();
+ xRedlineText = new SwXRedlineText(&rDoc, *pNodeIdx);
+ }
+ }
+
return
{
// GetAuthorString(n) walks the SwRedlineData* chain;
@@ -183,10 +199,11 @@ static uno::Sequence lcl_GetSuccessorProperties(const SwRa
comphelper::makePropertyValue(UNO_NAME_REDLINE_AUTHOR, rRedline.GetAuthorString(1)),
comphelper::makePropertyValue(UNO_NAME_REDLINE_DATE_TIME, pNext->GetTimeStamp().GetUNODateTime()),
comphelper::makePropertyValue(UNO_NAME_REDLINE_COMMENT, pNext->GetComment()),
- comphelper::makePropertyValue(UNO_NAME_REDLINE_TYPE, SwRedlineTypeToOUString(pNext->GetType()))
+ comphelper::makePropertyValue(UNO_NAME_REDLINE_TYPE, SwRedlineTypeToOUString(pNext->GetType())),
+ comphelper::makePropertyValue(UNO_NAME_REDLINE_TEXT, xRedlineText)
};
}
- return uno::Sequence(4);
+ return uno::Sequence(5);
}
uno::Any SwXRedlinePortion::getPropertyValue( const OUString& rPropertyName )
diff --git a/xmloff/qa/unit/data/del-then-format.docx b/xmloff/qa/unit/data/del-then-format.docx
new file mode 100644
index 000000000000..f458578f5568
Binary files /dev/null and b/xmloff/qa/unit/data/del-then-format.docx differ
diff --git a/xmloff/qa/unit/text.cxx b/xmloff/qa/unit/text.cxx
index 2ef9e2673360..ce11001b12cd 100644
--- a/xmloff/qa/unit/text.cxx
+++ b/xmloff/qa/unit/text.cxx
@@ -1286,6 +1286,27 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testNestedSpans)
CPPUNIT_ASSERT_EQUAL(awt::FontWeight::BOLD, fWeight);
}
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDeleteThenFormatOdtExport)
+{
+ // Given a document that has a delete redline, and then a format redline on top of it:
+ loadFromFile(u"del-then-format.docx");
+
+ // When exporting that to DOCX:
+ save(u"writer8"_ustr);
+
+ // Then make sure not only has the child, but also an
+ // one, with text:
+ xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 1
+ // - Actual : 0
+ // - XPath '//text:tracked-changes/text:changed-region[2]/text:deletion' number of nodes is incorrect
+ // i.e. was written for a delete-then-format.
+ assertXPath(pXmlDoc, "//text:tracked-changes/text:changed-region[2]/text:deletion");
+ // Also the delete's text was in the body text.
+ assertXPath(pXmlDoc, "//text:tracked-changes/text:changed-region[2]/text:deletion/text:p");
+}
+
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/source/text/XMLRedlineExport.cxx b/xmloff/source/text/XMLRedlineExport.cxx
index 093040c3b15b..34e3ae5c5264 100644
--- a/xmloff/source/text/XMLRedlineExport.cxx
+++ b/xmloff/source/text/XMLRedlineExport.cxx
@@ -43,6 +43,7 @@
#include
#include
#include
+#include
using namespace ::com::sun::star;
@@ -361,7 +362,8 @@ void XMLRedlineExport::ExportChangedRegion(
aAny = rPropSet->getPropertyValue(u"RedlineText"_ustr);
Reference xText;
aAny >>= xText;
- if (xText.is())
+ // Avoid double export for format-on-delete: no write on format, only on delete.
+ if (xText.is() && sType == u"Delete")
{
rExport.GetTextParagraphExport()->exportText(xText);
// default parameters: bProgress, bExportParagraph ???
@@ -380,15 +382,31 @@ void XMLRedlineExport::ExportChangedRegion(
// process change info
if (aSuccessorData.hasElements())
{
- // The only change that can be "undone" is an insertion -
- // after all, you can't re-insert a deletion, but you can
- // delete an insertion. This assumption is asserted in
- // ExportChangeInfo(Sequence&).
+ // Look up the type of the change. In practice this is always insert or delete, other types
+ // can't have additional redlines on top of them.
+ OUString sType;
+ comphelper::SequenceAsHashMap aMap(aSuccessorData);
+ auto it = aMap.find("RedlineType");
+ if (it != aMap.end())
+ {
+ it->second >>= sType;
+ }
SvXMLElementExport aSecondChangeElem(
- rExport, XML_NAMESPACE_TEXT, XML_INSERTION,
+ rExport, XML_NAMESPACE_TEXT, ConvertTypeName(sType),
true, true);
ExportChangeInfo(aSuccessorData);
+ it = aMap.find("RedlineText");
+ if (it != aMap.end())
+ {
+ // Delete has its own content outside the body text: export it here.
+ uno::Reference xText;
+ it->second >>= xText;
+ if (xText.is())
+ {
+ rExport.GetTextParagraphExport()->exportText(xText);
+ }
+ }
}
// else: no hierarchical change
}
@@ -529,10 +547,10 @@ void XMLRedlineExport::ExportChangeInfo(
}
else if( rVal.Name == "RedlineType" )
{
- // check if this is an insertion; cf. comment at calling location
+ // check if this has one of the expected types
OUString sTmp;
rVal.Value >>= sTmp;
- DBG_ASSERT(sTmp == "Insert",
+ DBG_ASSERT(sTmp == "Insert" || sTmp == "Delete",
"hierarchical change must be insertion");
}
// else: unknown value -> ignore