From d325cd0c69b7c0cc4f47105749a98995de81cc9d Mon Sep 17 00:00:00 2001 From: László Németh Date: Wed, 24 Feb 2021 15:05:38 +0100 Subject: tdf#115815 sw: fix lost annotation ranges of redlines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Annotations of tracked deletions lost their ranges during ODF export, according to the limitation of ODF or its recent implementation. As a workaround, save and restore the start of the annotation ranges using temporary bookmarks (which can be part of text:deletion). Note: maybe it's possible to split redline ranges regarding to the start of the annotation ranges, mixing tracked deletions with normal text or tracked insertions, but this would be a not backward compatible solution, because the ODF import of this file inserts extra (not tracked) spaces around the annotation, losing the original text content. Change-Id: I786993a05ee1683076e213374a92969d1856cf8c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111489 Tested-by: László Németh Reviewed-by: László Németh --- sw/inc/IDocumentMarkAccess.hxx | 3 +++ sw/qa/extras/odfexport/data/tdf115815.odt | Bin 0 -> 9809 bytes sw/qa/extras/odfexport/odfexport.cxx | 39 ++++++++++++++++++++++++++++++ sw/source/core/doc/docbm.cxx | 28 +++++++++++++++++++++ sw/source/core/doc/docredln.cxx | 29 ++++++++++++++++++++++ sw/source/core/inc/MarkManager.hxx | 2 ++ sw/source/filter/xml/swxml.cxx | 4 +++ sw/source/filter/xml/wrtxml.cxx | 4 +++ 8 files changed, 109 insertions(+) create mode 100644 sw/qa/extras/odfexport/data/tdf115815.odt diff --git a/sw/inc/IDocumentMarkAccess.hxx b/sw/inc/IDocumentMarkAccess.hxx index 1ad39701d02b..801ce98ba503 100644 --- a/sw/inc/IDocumentMarkAccess.hxx +++ b/sw/inc/IDocumentMarkAccess.hxx @@ -341,6 +341,9 @@ class IDocumentMarkAccess virtual sal_Int32 getAnnotationMarksCount() const = 0; virtual const_iterator_t findAnnotationMark( const OUString& rName ) const = 0; virtual sw::mark::IMark* getAnnotationMarkFor(const SwPosition& rPosition) const = 0; + // restore text ranges of annotations of tracked deletions + // based on the helper bookmarks (which can survive I/O and hiding redlines) + virtual void restoreAnnotationMarks() = 0; /** Finds the first mark that is starting after. @returns diff --git a/sw/qa/extras/odfexport/data/tdf115815.odt b/sw/qa/extras/odfexport/data/tdf115815.odt new file mode 100644 index 000000000000..7c2aad0da638 Binary files /dev/null and b/sw/qa/extras/odfexport/data/tdf115815.odt differ diff --git a/sw/qa/extras/odfexport/odfexport.cxx b/sw/qa/extras/odfexport/odfexport.cxx index 98712ae76fdc..4e86f035fa92 100644 --- a/sw/qa/extras/odfexport/odfexport.cxx +++ b/sw/qa/extras/odfexport/odfexport.cxx @@ -767,6 +767,45 @@ DECLARE_ODFEXPORT_TEST(testFdo60769, "fdo60769.odt") } } +DECLARE_ODFEXPORT_TEST(testTdf115815, "tdf115815.odt") +{ + CPPUNIT_ASSERT_EQUAL(1, getPages()); + // Test comment range feature on tracked deletion. + uno::Reference xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference xParaEnumAccess(xTextDocument->getText(), uno::UNO_QUERY); + uno::Reference xParaEnum = xParaEnumAccess->createEnumeration(); + uno::Reference xRunEnumAccess(xParaEnum->nextElement(), uno::UNO_QUERY); + uno::Reference xRunEnum = xRunEnumAccess->createEnumeration(); + bool bAnnotationStart = false; + bool bBeforeAnnotation = true; + OUString sTextBeforeAnnotation; + while (xRunEnum->hasMoreElements()) + { + uno::Reference xPropertySet(xRunEnum->nextElement(), uno::UNO_QUERY); + OUString aType = getProperty(xPropertySet, "TextPortionType"); + // there is no AnnotationEnd with preceding AnnotationStart, + // i.e. annotation with lost range + CPPUNIT_ASSERT(aType != "AnnotationEnd" || !bAnnotationStart); + + bAnnotationStart = (aType == "Annotation"); + + // collect paragraph text before the first annotation + if (bBeforeAnnotation) + { + if (bAnnotationStart) + bBeforeAnnotation = false; + else if (aType == "Text") + { + uno::Reference xRun(xPropertySet, uno::UNO_QUERY); + sTextBeforeAnnotation += xRun->getString(); + } + } + } + + // This was "Lorem ipsum" (collapsed annotation range) + CPPUNIT_ASSERT_EQUAL(OUString("Lorem "), sTextBeforeAnnotation); +} + DECLARE_ODFEXPORT_TEST(testFdo58949, "fdo58949.docx") { /* diff --git a/sw/source/core/doc/docbm.cxx b/sw/source/core/doc/docbm.cxx index 7adb634b281b..9061e7b853d1 100644 --- a/sw/source/core/doc/docbm.cxx +++ b/sw/source/core/doc/docbm.cxx @@ -1641,6 +1641,34 @@ namespace sw::mark CompareIMarkStartsAfter()); } + // restore text ranges of annotations of tracked deletions + // based on the helper bookmarks (which can survive I/O and hiding redlines) + void MarkManager::restoreAnnotationMarks() + { + for (auto iter = getBookmarksBegin(); + iter != getBookmarksEnd(); ) + { + const OUString & rBookmarkName = (**iter).GetName(); + sal_Int32 nPos; + if ( rBookmarkName.startsWith("__Annotation__") && + (nPos = rBookmarkName.indexOf("____")) > -1 ) + { + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + IDocumentMarkAccess::const_iterator_t pMark = findAnnotationMark(rBookmarkName.copy(0, nPos)); + if ( pMark != getAnnotationMarksEnd() ) + { + const SwPaM aPam((**iter).GetMarkStart(), (**pMark).GetMarkEnd()); + repositionMark(*pMark, aPam); + } + deleteMark(&**iter); + // this invalidates iter, have to start over... + iter = getBookmarksBegin(); + } + else + ++iter; + } + } + OUString MarkManager::getUniqueMarkName(const OUString& rName) const { OSL_ENSURE(rName.getLength(), diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx index 207ed07dd95c..292600a171d6 100644 --- a/sw/source/core/doc/docredln.cxx +++ b/sw/source/core/doc/docredln.cxx @@ -1442,6 +1442,35 @@ void SwRangeRedline::CopyToSection() SwNodeIndex aNdIdx( *pSttNd, 1 ); SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode(); SwPosition aPos( aNdIdx, SwIndex( pTextNd )); + + // tdf#115815 keep original start position of collapsed annotation ranges + // as temporary bookmarks (removed after file saving and file loading) + auto & rDMA(*rDoc.getIDocumentMarkAccess()); + for (auto iter = rDMA.getAnnotationMarksBegin(); + iter != rDMA.getAnnotationMarksEnd(); ) + { + SwPosition const& rStartPos((**iter).GetMarkStart()); + if ( *pStt <= rStartPos && rStartPos < *pEnd ) + { + // at start of redlines use a 1-character length bookmark range + // instead of a 0-character length bookmark position to avoid its losing + sal_Int32 nLen = (*pStt == rStartPos) ? 1 : 0; + SwPaM aPam( rStartPos.nNode, rStartPos.nContent.GetIndex(), + rStartPos.nNode, rStartPos.nContent.GetIndex() + nLen); + ::sw::mark::IMark* pMark = rDMA.makeMark( + aPam, + (**iter).GetName() + "____", + IDocumentMarkAccess::MarkType::BOOKMARK, sw::mark::InsertMode::New); + ::sw::mark::IBookmark* pBookmark = dynamic_cast< ::sw::mark::IBookmark* >(pMark); + if (pBookmark) + { + pBookmark->SetKeyCode(vcl::KeyCode()); + pBookmark->SetShortName(OUString()); + } + } + ++iter; + } + rDoc.getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly); // Take over the style from the EndNode if needed diff --git a/sw/source/core/inc/MarkManager.hxx b/sw/source/core/inc/MarkManager.hxx index 11db9719d439..4bc216ff3c7d 100644 --- a/sw/source/core/inc/MarkManager.hxx +++ b/sw/source/core/inc/MarkManager.hxx @@ -117,6 +117,8 @@ namespace sw::mark { typedef std::vector container_t; + virtual void restoreAnnotationMarks() override; + private: MarkManager(MarkManager const&) = delete; diff --git a/sw/source/filter/xml/swxml.cxx b/sw/source/filter/xml/swxml.cxx index c3d9c50556da..7893321829c6 100644 --- a/sw/source/filter/xml/swxml.cxx +++ b/sw/source/filter/xml/swxml.cxx @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -954,6 +955,9 @@ ErrCode XMLReader::Read( SwDoc &rDoc, const OUString& rBaseURL, SwPaM &rPaM, con } } + // tdf#115815 restore annotation ranges stored in temporary bookmarks + rDoc.getIDocumentMarkAccess()->restoreAnnotationMarks(); + if (xStatusIndicator.is()) { xStatusIndicator->end(); diff --git a/sw/source/filter/xml/wrtxml.cxx b/sw/source/filter/xml/wrtxml.cxx index 71445d932f4e..4ae5964a7d2e 100644 --- a/sw/source/filter/xml/wrtxml.cxx +++ b/sw/source/filter/xml/wrtxml.cxx @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -409,6 +410,9 @@ ErrCode SwXMLWriter::Write_( const uno::Reference < task::XStatusIndicator >& xS nRedlineFlags |= nOrigRedlineFlags & RedlineFlags::ShowMask; m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( nRedlineFlags ); + // tdf#115815 restore annotation ranges collapsed by hide redlines + m_pDoc->getIDocumentMarkAccess()->restoreAnnotationMarks(); + if (xStatusIndicator.is()) { xStatusIndicator->end(); -- cgit