sw/qa/extras/unowriter/data/bookmark_1.html | 4 ++ sw/qa/extras/unowriter/unowriter.cxx | 37 +++++++++++++++++++++ sw/source/core/doc/docbm.cxx | 48 ++++++++++++++++++++++------ sw/source/core/inc/MarkManager.hxx | 5 ++ sw/source/filter/basflt/shellio.cxx | 14 ++++---- 5 files changed, 92 insertions(+), 16 deletions(-)
New commits: commit 8015a6c9e6b2e67184fdea6c897ff8036a488ca5 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Thu May 15 17:45:55 2025 +0500 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Fri May 16 13:00:15 2025 +0200 Related: tdf#165918 Avoid renaming pre-existing bookmarks Thanks Michael Stahl, for pointing this: > what happens when you first insert a bookmark, then insert a file that > contains a bookmark with the same name at a place in the document before > the pre-existing one - the SwHistoryBookmark uses the name of the bookmark > to find it, and this loop would rename the pre-exsiting bookmark, so on > undo of insert bookmark it can't be found? ( https://gerrit.libreoffice.org/c/core/+/185340/comment/274a3fba_aa363535/ ) This change avoids this problem by preparing a set of already existing names at the start of the performance mode, and only checking the marks that were added while in the performance mode. Change-Id: Id4dc1809871fd4a998396d091f2f920ecd71b7d9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185363 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> diff --git a/sw/qa/extras/unowriter/data/bookmark_1.html b/sw/qa/extras/unowriter/data/bookmark_1.html new file mode 100644 index 000000000000..225817dc6729 --- /dev/null +++ b/sw/qa/extras/unowriter/data/bookmark_1.html @@ -0,0 +1,4 @@ +<html> +<body><a name="Bookmark 1">abc</a> +</body> +</html> \ No newline at end of file diff --git a/sw/qa/extras/unowriter/unowriter.cxx b/sw/qa/extras/unowriter/unowriter.cxx index 0e74fb83cdde..32dd4ead2097 100644 --- a/sw/qa/extras/unowriter/unowriter.cxx +++ b/sw/qa/extras/unowriter/unowriter.cxx @@ -16,6 +16,7 @@ #include <com/sun/star/frame/XDispatchProviderInterception.hpp> #include <com/sun/star/frame/XDispatchProviderInterceptor.hpp> #include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/text/ControlCharacter.hpp> #include <com/sun/star/text/TextContentAnchorType.hpp> #include <com/sun/star/text/AutoTextContainer.hpp> #include <com/sun/star/text/VertOrientation.hpp> @@ -1560,6 +1561,42 @@ CPPUNIT_TEST_FIXTURE(SwUnoWriter, testTdf164921) } } +CPPUNIT_TEST_FIXTURE(SwUnoWriter, testMarkWithPreexistingNameInsertion) +{ + createSwDoc(); + + // Add a second paragraph, and create a bookmark there, with a specific name + auto xTextDocument = mxComponent.queryThrow<text::XTextDocument>(); + auto xText = xTextDocument->getText(); + xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false); + + auto xFac = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xMark = xFac->createInstance(u"com.sun.star.text.Bookmark"_ustr); + auto xNamed = xMark.queryThrow<container::XNamed>(); + xNamed->setName(u"Bookmark 1"_ustr); + xText->insertTextContent(xText->getEnd(), xMark.queryThrow<text::XTextContent>(), false); + + // Insert the content of a file, which has a bookmark with the same name, before existing one + dispatchCommand( + mxComponent, u".uno:InsertDoc"_ustr, + { comphelper::makePropertyValue(u"Name"_ustr, createFileURL(u"bookmark_1.html")) }); + + // The pre-existing bookmark's name must not change + // Before the fix, this would fail with "Actual : Bookmark 1 Copy 1" + CPPUNIT_ASSERT_EQUAL(u"Bookmark 1"_ustr, xNamed->getName()); + + auto xSupplier = mxComponent.queryThrow<text::XBookmarksSupplier>(); + auto xBookmarks = xSupplier->getBookmarks(); + auto names = xBookmarks->getElementNames(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), names.getLength()); + // names[1] is the pre-existing bookmark + CPPUNIT_ASSERT_EQUAL(u"Bookmark 1"_ustr, names[1]); + // names[0] is the bookmark coming from the inserted file + OUString rest; + CPPUNIT_ASSERT(names[0].startsWith("Bookmark 1", &rest)); + CPPUNIT_ASSERT(!rest.isEmpty()); // should be " Copy 1" +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/core/doc/docbm.cxx b/sw/source/core/doc/docbm.cxx index 4a17c6e198db..7a136dcceaf0 100644 --- a/sw/source/core/doc/docbm.cxx +++ b/sw/source/core/doc/docbm.cxx @@ -500,19 +500,37 @@ namespace sw::mark , m_pLastActiveFieldmark(nullptr) { } - void MarkManager::disableUniqueNameChecks() { m_bCheckUniqueNames = false; } + // In the mode where the names are not checked, we need to avoid a case where there was a + // bookmark, and a file is inserted at an earlier point, with the same-name bookmark, causing + // a rename of the pre-existing bookmark. m_aUsedNames and m_vUncheckedNameMarks are used for + // that. m_aUsedNames is pre-populated with the existing names (at the moment when the mode + // started); and m_vUncheckedNameMarks stores only the marks needing the checks. + + void MarkManager::disableUniqueNameChecks() + { + if (!m_bCheckUniqueNames) + return; // nested call + m_bCheckUniqueNames = false; + assert(m_aUsedNames.empty()); + + // Populate the pre-existing already deduplicated names + for (auto& pMark : m_vAllMarks) + m_aUsedNames.insert(pMark->GetName().toString()); + } void MarkManager::enableUniqueNameChecks() { if (m_bCheckUniqueNames) return; - // Make sure that all names are unique - std::unordered_set<OUString> usedNames; + // Make sure that all previously unchecked names are unique for (auto& pMark : m_vAllMarks) { - assert(pMark); - pMark->SetName(getUniqueMarkName(pMark->GetName(), [&usedNames](const SwMarkName& n) - { return usedNames.insert(n.toString()).second; })); + if (!m_vUncheckedNameMarks.contains(pMark)) + continue; // mark was added and removed while in the performance mode + pMark->SetName(getUniqueMarkName(pMark->GetName(), [this](const SwMarkName& n) + { return m_aUsedNames.insert(n.toString()).second; })); } + m_aUsedNames.clear(); + m_vUncheckedNameMarks.clear(); m_bCheckUniqueNames = true; } @@ -632,10 +650,19 @@ namespace sw::mark pMark->Swap(); // for performance reasons, we trust UnoMarks to have a (generated) unique name - if (eType != IDocumentMarkAccess::MarkType::UNO_BOOKMARK && m_bCheckUniqueNames) - pMark->SetName(getUniqueMarkName( - pMark->GetName(), [this](const SwMarkName& n) - { return lcl_FindMarkByName(n, m_vAllMarks) == m_vAllMarks.end(); })); + if (eType != IDocumentMarkAccess::MarkType::UNO_BOOKMARK) + { + if (m_bCheckUniqueNames) + { + pMark->SetName(getUniqueMarkName( + pMark->GetName(), [this](const SwMarkName& n) + { return lcl_FindMarkByName(n, m_vAllMarks) == m_vAllMarks.end(); })); + } + else + { + m_vUncheckedNameMarks.insert(pMark.get()); + } + } // insert any dummy chars before inserting into sorted vectors pMark->InitDoc(m_rDoc, eMode, pSepPos); @@ -1343,6 +1370,7 @@ namespace sw::mark m_vFieldmarks.clear(); m_vBookmarks.clear(); m_vAnnotationMarks.clear(); + m_vUncheckedNameMarks.clear(); for (const auto & p : m_vAllMarks) delete p; m_vAllMarks.clear(); diff --git a/sw/source/core/inc/MarkManager.hxx b/sw/source/core/inc/MarkManager.hxx index f4ee394d3f5b..729958192a02 100644 --- a/sw/source/core/inc/MarkManager.hxx +++ b/sw/source/core/inc/MarkManager.hxx @@ -153,6 +153,11 @@ namespace sw::mark { // container for all marks, this container owns the objects it points to container_t m_vAllMarks; + // container for all marks with possibly duplicating names (m_bCheckUniqueNames mode) + std::unordered_set<sw::mark::MarkBase*> m_vUncheckedNameMarks; + // container for deduplicating names (m_bCheckUniqueNames mode) + std::unordered_set<OUString> m_aUsedNames; + // additional container for bookmarks std::vector<sw::mark::Bookmark*> m_vBookmarks; // additional container for fieldmarks diff --git a/sw/source/filter/basflt/shellio.cxx b/sw/source/filter/basflt/shellio.cxx index abb516422b88..107176ec142c 100644 --- a/sw/source/filter/basflt/shellio.cxx +++ b/sw/source/filter/basflt/shellio.cxx @@ -18,6 +18,7 @@ */ #include <hintids.hxx> +#include <comphelper/scopeguard.hxx> #include <osl/diagnose.h> #include <tools/date.hxx> #include <tools/time.hxx> @@ -201,13 +202,14 @@ ErrCodeMsg SwReader::Read( const Reader& rOptions ) mxDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); - // Preformance mode: import all bookmarks names as defined in the document - mxDoc->getIDocumentMarkAccess()->disableUniqueNameChecks(); - - nError = po->Read( *mxDoc, msBaseURL, *pPam, maFileName ); + { + // Preformance mode: import all bookmarks names as defined in the document + mxDoc->getIDocumentMarkAccess()->disableUniqueNameChecks(); + comphelper::ScopeGuard perfModeGuard( + [this]() { mxDoc->getIDocumentMarkAccess()->enableUniqueNameChecks(); }); - // End performance mode: now make sure that all names are unique - mxDoc->getIDocumentMarkAccess()->enableUniqueNameChecks(); + nError = po->Read(*mxDoc, msBaseURL, *pPam, maFileName); + } // an ODF document may contain redline mode in settings.xml; save it! ePostReadRedlineFlags = mxDoc->getIDocumentRedlineAccess().GetRedlineFlags();