sw/inc/IDocumentContentOperations.hxx | 3 +- sw/qa/core/doc/data/copy-flag-skip-bookmarks.docx |binary sw/qa/core/doc/doc.cxx | 16 +++++++++++ sw/source/core/doc/DocumentContentOperationsManager.cxx | 16 ++++++++--- sw/source/core/doc/docdesc.cxx | 4 +- sw/source/core/doc/docfmt.cxx | 2 - sw/source/core/inc/DocumentContentOperationsManager.hxx | 2 - sw/source/core/unocore/unotext.cxx | 22 +++++++++++++--- writerfilter/source/dmapper/PropertyMap.cxx | 11 ++++++++ 9 files changed, 64 insertions(+), 12 deletions(-)
New commits: commit 3ec224dcb15e0e663ba85077b8ea0e632f8f03f8 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Thu Sep 8 16:41:46 2022 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Thu Sep 8 17:46:05 2022 +0200 DOCX import: avoid duplicated bookmarks in copied header/footer text In case a DOCX document has 2 sections and the 2nd section has a linked header, writerfilter/ copies the header text from the 1st page style to the 2nd page style. This leads to duplicated bookmarks in the doc model, which causes an unexpected increase in bookmark count on export. This went wrong with 8885bdf2f564bb7b56181c50baf39ff99d551ec9 (tdf#128106 sw: copy bookmarks at start if whole node is copied, 2021-08-27), previously at least bookmarks at the start of the header were not duplicated, though that was also inconsistent, since other bookmarks were still duplicated. Interestingly the DOC import doesn't have this problem, because it first copies header text and only later imports the bookmarks. Fix the problem by introducing a new SwCopyFlags::SkipBookmarks flag, add UNO API to set this from writerfilter/ code and skip copying bookmarks (but not other marks) in sw::CopyBookmarks() accordingly. Copying other marks is still needed, because e.g. fieldmarks have a mark and a field, and it would be inconsistent to copy the field, but not the mark. Note that the text is still duplicated in header/footer, linked header/footer is a missing feature on the Writer side. Change-Id: I40e18f231ef2c0d91ae9582621684ef5b6284904 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139697 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/inc/IDocumentContentOperations.hxx b/sw/inc/IDocumentContentOperations.hxx index 5a95d0ba95b8..60d45d47a0a3 100644 --- a/sw/inc/IDocumentContentOperations.hxx +++ b/sw/inc/IDocumentContentOperations.hxx @@ -75,11 +75,12 @@ enum class SwCopyFlags CopyAll = (1<<0), ///< copy break attributes even when source is single node CheckPosInFly = (1<<1), ///< check if target position is in fly anchored at source range IsMoveToFly = (1<<2), ///< MakeFlyAndMove + SkipBookmarks = (1<<3), ///< skip bookmarks, but not other kind of marks // TODO: mbCopyIsMove? mbIsRedlineMove? }; namespace o3tl { - template<> struct typed_flags<SwCopyFlags> : is_typed_flags<SwCopyFlags, 0x07> {}; + template<> struct typed_flags<SwCopyFlags> : is_typed_flags<SwCopyFlags, 0x0f> {}; } enum class SwDeleteFlags diff --git a/sw/qa/core/doc/data/copy-flag-skip-bookmarks.docx b/sw/qa/core/doc/data/copy-flag-skip-bookmarks.docx new file mode 100644 index 000000000000..3fb27b430a17 Binary files /dev/null and b/sw/qa/core/doc/data/copy-flag-skip-bookmarks.docx differ diff --git a/sw/qa/core/doc/doc.cxx b/sw/qa/core/doc/doc.cxx index 87bfd2697621..2ee2ee342bd3 100644 --- a/sw/qa/core/doc/doc.cxx +++ b/sw/qa/core/doc/doc.cxx @@ -255,6 +255,22 @@ CPPUNIT_TEST_FIXTURE(SwCoreDocTest, testContentControlDelete) CPPUNIT_ASSERT_EQUAL(OUString("\x0001test\x0001"), pTextNode->GetText()); } +CPPUNIT_TEST_FIXTURE(SwCoreDocTest, testCopyFlagSkipBookmarks) +{ + // Given a document with a bookmark in a header that is linked later: + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "copy-flag-skip-bookmarks.docx"); + + // When checking the # of bookmarks in the resulting doc model: + sal_Int32 nActual = pDoc->getIDocumentMarkAccess()->getAllMarksCount(); + + // Then make sure we have a single bookmark, with no duplications: + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 2 + // i.e. the 2nd header had a duplicated bookmark. + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), nActual); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx index c642cde1c8c7..62c362eba181 100644 --- a/sw/source/core/doc/DocumentContentOperationsManager.cxx +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -232,7 +232,7 @@ namespace namespace sw { // TODO: use SaveBookmark (from DelBookmarks) - void CopyBookmarks(const SwPaM& rPam, const SwPosition& rCpyPam) + void CopyBookmarks(const SwPaM& rPam, const SwPosition& rCpyPam, SwCopyFlags eFlags) { const SwDoc& rSrcDoc = rPam.GetDoc(); SwDoc& rDestDoc = rCpyPam.GetDoc(); @@ -283,6 +283,7 @@ namespace sw // We have to count the "non-copied" nodes... SwNodeOffset nDelCount; SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + auto bSkipBookmarks = static_cast<bool>(eFlags & SwCopyFlags::SkipBookmarks); for(const sw::mark::IMark* const pMark : vMarksToCopy) { SwPaM aTmpPam(*pCpyStt); @@ -295,10 +296,17 @@ namespace sw lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount); } + IDocumentMarkAccess::MarkType eType = IDocumentMarkAccess::GetType(*pMark); + if (bSkipBookmarks && eType == IDocumentMarkAccess::MarkType::BOOKMARK) + { + // It was requested to skip bookmarks while copying. Do that, but continue to copy + // other kind of marks, like fieldmarks. + continue; + } ::sw::mark::IMark* const pNewMark = rDestDoc.getIDocumentMarkAccess()->makeMark( aTmpPam, pMark->GetName(), - IDocumentMarkAccess::GetType(*pMark), + eType, ::sw::mark::InsertMode::CopyText); // Explicitly try to get exactly the same name as in the source // because NavigatorReminders, DdeBookmarks etc. ignore the proposed name @@ -3672,7 +3680,7 @@ void DocumentContentOperationsManager::CopyWithFlyInFly( targetPos = pCopiedPaM->second; } - sw::CopyBookmarks(pCopiedPaM ? pCopiedPaM->first : aRgTmp, targetPos); + sw::CopyBookmarks(pCopiedPaM ? pCopiedPaM->first : aRgTmp, targetPos, flags); } if (rRg.aStart != rRg.aEnd) @@ -5301,7 +5309,7 @@ bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPo // Also copy all bookmarks if( bCopyBookmarks && m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() ) { - sw::CopyBookmarks(rPam, *pCopyPam->Start()); + sw::CopyBookmarks(rPam, *pCopyPam->Start(), SwCopyFlags::Default); } if( RedlineFlags::DeleteRedlines & eOld ) diff --git a/sw/source/core/doc/docdesc.cxx b/sw/source/core/doc/docdesc.cxx index d75caf8cac0b..a5642f053b7c 100644 --- a/sw/source/core/doc/docdesc.cxx +++ b/sw/source/core/doc/docdesc.cxx @@ -302,7 +302,7 @@ void SwDoc::CopyMasterHeader(const SwPageDesc &rChged, const SwFormatHeader &rHe GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, *pSttNd); SwPaM const source(aRange.aStart, aRange.aEnd); SwPosition dest(*pSttNd); - sw::CopyBookmarks(source, dest); + sw::CopyBookmarks(source, dest, SwCopyFlags::Default); pFormat->SetFormatAttr( SwFormatContent( pSttNd ) ); rDescFrameFormat.SetFormatAttr( SwFormatHeader( pFormat ) ); } @@ -379,7 +379,7 @@ void SwDoc::CopyMasterFooter(const SwPageDesc &rChged, const SwFormatFooter &rFo GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, *pSttNd); SwPaM const source(aRange.aStart, aRange.aEnd); SwPosition dest(*pSttNd); - sw::CopyBookmarks(source, dest); + sw::CopyBookmarks(source, dest, SwCopyFlags::Default); pFormat->SetFormatAttr( SwFormatContent( pSttNd ) ); rDescFrameFormat.SetFormatAttr( SwFormatFooter( pFormat ) ); } diff --git a/sw/source/core/doc/docfmt.cxx b/sw/source/core/doc/docfmt.cxx index 357ca66aeb8d..51c0e935e7b2 100644 --- a/sw/source/core/doc/docfmt.cxx +++ b/sw/source/core/doc/docfmt.cxx @@ -1406,7 +1406,7 @@ void SwDoc::CopyPageDescHeaderFooterImpl( bool bCpyHeader, // TODO: investigate calling CopyWithFlyInFly? SwPaM const source(aRg.aStart, aRg.aEnd); SwPosition dest(*pSttNd); - sw::CopyBookmarks(source, dest); + sw::CopyBookmarks(source, dest, SwCopyFlags::Default); pNewFormat->SetFormatAttr( SwFormatContent( pSttNd )); } else diff --git a/sw/source/core/inc/DocumentContentOperationsManager.hxx b/sw/source/core/inc/DocumentContentOperationsManager.hxx index 49473930f7fe..d2b61500c8c6 100644 --- a/sw/source/core/inc/DocumentContentOperationsManager.hxx +++ b/sw/source/core/inc/DocumentContentOperationsManager.hxx @@ -182,7 +182,7 @@ private: }; -void CopyBookmarks(const SwPaM& rPam, const SwPosition& rTarget); +void CopyBookmarks(const SwPaM& rPam, const SwPosition& rTarget, SwCopyFlags eFlags); void CalcBreaks(std::vector<std::pair<SwNodeOffset, sal_Int32>> & rBreaks, SwPaM const & rPam, bool const isOnlyFieldmarks = false); diff --git a/sw/source/core/unocore/unotext.cxx b/sw/source/core/unocore/unotext.cxx index c68a1a331c90..5d65f5ef8b63 100644 --- a/sw/source/core/unocore/unotext.cxx +++ b/sw/source/core/unocore/unotext.cxx @@ -86,6 +86,7 @@ public: const CursorType m_eType; SwDoc * m_pDoc; bool m_bIsValid; + bool m_bCopySkipsBookmarks; Impl( SwXText & rThis, SwDoc *const pDoc, const CursorType eType) @@ -94,6 +95,7 @@ public: , m_eType(eType) , m_pDoc(pDoc) , m_bIsValid(nullptr != pDoc) + , m_bCopySkipsBookmarks(false) { } @@ -1121,9 +1123,18 @@ SwXText::getPropertySetInfo() } void SAL_CALL -SwXText::setPropertyValue(const OUString& /*aPropertyName*/, - const uno::Any& /*aValue*/) +SwXText::setPropertyValue(const OUString& aPropertyName, + const uno::Any& aValue) { + if (aPropertyName == "CopySkipsBookmarks") + { + bool bValue{}; + if (aValue >>= bValue) + { + m_pImpl->m_bCopySkipsBookmarks = bValue; + } + return; + } throw lang::IllegalArgumentException(); } @@ -2402,7 +2413,12 @@ SwXText::copyText( // Explicitly request copy text mode, so // sw::DocumentContentOperationsManager::CopyFlyInFlyImpl() will copy shapes anchored to // us, even if we have only a single paragraph. - m_pImpl->m_pDoc->getIDocumentContentOperations().CopyRange(temp, rPos, SwCopyFlags::CheckPosInFly); + SwCopyFlags eFlags = SwCopyFlags::CheckPosInFly; + if (m_pImpl->m_bCopySkipsBookmarks) + { + eFlags |= SwCopyFlags::SkipBookmarks; + } + m_pImpl->m_pDoc->getIDocumentContentOperations().CopyRange(temp, rPos, eFlags); } if (!pFirstNode) { // the node at rPos was split; get rid of the first empty one so diff --git a/writerfilter/source/dmapper/PropertyMap.cxx b/writerfilter/source/dmapper/PropertyMap.cxx index eec9b678a209..e848a5780ebf 100644 --- a/writerfilter/source/dmapper/PropertyMap.cxx +++ b/writerfilter/source/dmapper/PropertyMap.cxx @@ -874,7 +874,18 @@ void SectionPropertyMap::CopyHeaderFooterTextProperty( const uno::Reference< bea if ( xPrevStyle.is() ) xPrevTxt.set( xPrevStyle->getPropertyValue( sName ), uno::UNO_QUERY_THROW ); + uno::Reference<beans::XPropertySet> xTxtProps(xTxt, uno::UNO_QUERY); + if (xTxtProps.is()) + { + // Skip copying bookmarks, so the number of bookmarks in the source document and in the + // resulting doc model match. + xTxtProps->setPropertyValue("CopySkipsBookmarks", uno::Any(true)); + } xTxt->copyText( xPrevTxt ); + if (xTxtProps.is()) + { + xTxtProps->setPropertyValue("CopySkipsBookmarks", uno::Any(false)); + } } catch ( const uno::Exception& ) {