sw/qa/extras/globalfilter/data/char_background_editing.docx |binary sw/qa/extras/globalfilter/data/fieldmark_QUOTE_nest.docx |binary sw/qa/extras/layout/layout.cxx | 2 sw/qa/extras/ooxmlexport/data/tdf123642.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport17.cxx | 21 +++ sw/qa/extras/ooxmlexport/ooxmlexport8.cxx | 2 sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx | 6 writerfilter/source/dmapper/DomainMapper_Impl.cxx | 75 +++++++++++- 8 files changed, 100 insertions(+), 6 deletions(-)
New commits: commit daa1469e3e7bfe840efb6f3fad1bac63a2577e3f Author: Vasily Melenchuk <vasily.melenc...@cib.de> AuthorDate: Thu Sep 9 11:41:33 2021 +0300 Commit: Thorsten Behrens <thorsten.behr...@allotropia.de> CommitDate: Mon Sep 13 00:36:44 2021 +0200 tdf#123642: keep last bookmark at the document end In some cases DomainMapper_Impl::RemoveLastParagraph() can also remove last bookmark from real last paragraph. This does never happens when we use xParagraph->dispose(), but pretty always during older way with xCursor->setString(OUString()). Unfortunately without deep refactoring of redlines, bookmarks, etc. I see no other way to avoid this removal except given hack which is trying to store last bookmark and if it did disappear restore it. Some existing unittests were adjusted: corresponding original DOCX files did contain final bookmarks not taken into account by the code. In some cases test code is modified, in some just removed final bookmark in DOCX: such multi-format tests as in ooxmlfieldexport.cxx will be more identical to RTF & DOC variants. Change-Id: Ie9948b58cda705a0b85fa8e5e08b72fbb7d682b2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/121409 Tested-by: Jenkins Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> diff --git a/sw/qa/extras/globalfilter/data/char_background_editing.docx b/sw/qa/extras/globalfilter/data/char_background_editing.docx index 3e3302d00c71..5e92fe8bbd49 100644 Binary files a/sw/qa/extras/globalfilter/data/char_background_editing.docx and b/sw/qa/extras/globalfilter/data/char_background_editing.docx differ diff --git a/sw/qa/extras/globalfilter/data/fieldmark_QUOTE_nest.docx b/sw/qa/extras/globalfilter/data/fieldmark_QUOTE_nest.docx index ba886edfa777..9fbec01e0ab6 100644 Binary files a/sw/qa/extras/globalfilter/data/fieldmark_QUOTE_nest.docx and b/sw/qa/extras/globalfilter/data/fieldmark_QUOTE_nest.docx differ diff --git a/sw/qa/extras/layout/layout.cxx b/sw/qa/extras/layout/layout.cxx index bb5a860cab07..46176b2cc420 100644 --- a/sw/qa/extras/layout/layout.cxx +++ b/sw/qa/extras/layout/layout.cxx @@ -1316,7 +1316,7 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter, testTdf116486) { SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf116486.docx"); CPPUNIT_ASSERT(pDoc); - OUString aTop = parseDump("/root/page/body/txt/Special", "nHeight"); + OUString aTop = parseDump("/root/page/body/txt/Special[1]", "nHeight"); CPPUNIT_ASSERT_EQUAL(OUString("4006"), aTop); } diff --git a/sw/qa/extras/ooxmlexport/data/tdf123642.docx b/sw/qa/extras/ooxmlexport/data/tdf123642.docx new file mode 100644 index 000000000000..9817093e02f8 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf123642.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx index 1b6f6cf713d1..10d7bab40a23 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx @@ -10,6 +10,7 @@ #include <sal/config.h> #include <string_view> +#include <com/sun/star/text/XBookmarksSupplier.hpp> #include <swmodeltestbase.hxx> @@ -53,6 +54,26 @@ CPPUNIT_TEST_FIXTURE(Test, testParaStyleNumLevel) assertXPath(pXmlDoc, "/w:styles/w:style[@w:styleId='Mystyle']/w:pPr/w:numPr/w:ilvl", "val", "1"); } +DECLARE_OOXMLEXPORT_TEST(testTdf123642_BookmarkAtDocEnd, "tdf123642.docx") +{ + // get bookmark interface + uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XIndexAccess> xBookmarksByIdx(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xBookmarksByName = xBookmarksSupplier->getBookmarks(); + + // check: we have 1 bookmark (previously there were 0) + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xBookmarksByIdx->getCount()); + CPPUNIT_ASSERT(xBookmarksByName->hasByName("Bookmark1")); + + // and it is really in exprted DOCX (let's ensure) + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + if (!pXmlDoc) + return; // initial import, no futher checks + + CPPUNIT_ASSERT_EQUAL(OUString("Bookmark1"), getXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:bookmarkStart[1]", "name")); +} + + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport8.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport8.cxx index 1c2250afc7bb..7cc19abd58b6 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport8.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport8.cxx @@ -1036,7 +1036,7 @@ DECLARE_OOXMLEXPORT_TEST(testN820509, "n820509.docx") CPPUNIT_ASSERT(pTextDoc); SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc(); IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); - CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMarkAccess->getAllMarksCount()); ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*pMarkAccess->getAllMarksBegin()); diff --git a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx index d92c29fefbe7..f6e6ea78bc99 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx @@ -455,7 +455,7 @@ DECLARE_OOXMLEXPORT_TEST(testSdtDateDuplicate, "sdt-date-duplicate.docx") CPPUNIT_ASSERT(pTextDoc); SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc(); IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); - CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMarkAccess->getAllMarksCount()); ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*pMarkAccess->getAllMarksBegin()); @@ -614,7 +614,7 @@ DECLARE_OOXMLEXPORT_TEST( testDateFieldInShape, "date_field_in_shape.docx" ) CPPUNIT_ASSERT(pTextDoc); SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc(); IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); - CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMarkAccess->getAllMarksCount()); ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*pMarkAccess->getAllMarksBegin()); @@ -630,7 +630,7 @@ DECLARE_OOXMLEXPORT_TEST( testDateFieldAtEndOfParagraph, "date_field_at_end_of_p CPPUNIT_ASSERT(pTextDoc); SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc(); IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); - CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMarkAccess->getAllMarksCount()); ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*pMarkAccess->getAllMarksBegin()); diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx index 77c040fb9f3d..616b09755a81 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -556,6 +556,52 @@ void DomainMapper_Impl::AddDummyParaForTableInSection() } } + static OUString lcl_FindLastBookmark(const uno::Reference<text::XTextCursor>& xCursor) + { + OUString sName; + if (!xCursor.is()) + return sName; + + // Select 1 previous element + xCursor->goLeft(1, true); + uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xCursor, uno::UNO_QUERY); + if (!xParaEnumAccess.is()) + { + xCursor->goRight(1, true); + return sName; + } + + // Iterate through selection paragraphs + uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration(); + if (!xParaEnum->hasMoreElements()) + { + xCursor->goRight(1, true); + return sName; + } + + // Iterate through first para portions + uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParaEnum->nextElement(), + uno::UNO_QUERY_THROW); + uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xProps(xRunEnum->nextElement(), uno::UNO_QUERY_THROW); + uno::Any aType(xProps->getPropertyValue("TextPortionType")); + OUString sType; + aType >>= sType; + if (sType == "Bookmark") + { + uno::Reference<container::XNamed> xBookmark(xProps->getPropertyValue("Bookmark"), + uno::UNO_QUERY_THROW); + sName = xBookmark->getName(); + // Do not stop the scan here. Maybe there are 2 bookmarks? + } + } + + xCursor->goRight(1, true); + return sName; + } + void DomainMapper_Impl::RemoveLastParagraph( ) { if (m_bDiscardHeaderFooter) @@ -585,6 +631,13 @@ void DomainMapper_Impl::RemoveLastParagraph( ) // (but only for paste/insert, not load; otherwise it can happen that // flys anchored at the disposed paragraph are deleted (fdo47036.rtf)) bool const bEndOfDocument(m_aTextAppendStack.size() == 1); + + // Try to find and remember last bookmark in document: it potentially + // can be deleted by xCursor->setString() but not by xParagraph->dispose() + OUString sLastBookmarkName; + if (bEndOfDocument) + sLastBookmarkName = lcl_FindLastBookmark(xCursor); + if ((IsInHeaderFooter() || (bEndOfDocument && !m_bIsNewDoc)) && xEnumerationAccess.is()) { @@ -612,7 +665,27 @@ void DomainMapper_Impl::RemoveLastParagraph( ) // delete xCursor->setString(OUString()); - // restore again + // call to xCursor->setString possibly did remove final bookmark + // from previous paragraph. We need to restore it, if there was any. + if (sLastBookmarkName.getLength()) + { + OUString sBookmarkNameAfterRemoval = lcl_FindLastBookmark(xCursor); + if (sBookmarkNameAfterRemoval.isEmpty()) + { + // Yes, it was removed. Restore + uno::Reference<text::XTextContent> xBookmark( + m_xTextFactory->createInstance("com.sun.star.text.Bookmark"), + uno::UNO_QUERY_THROW); + + uno::Reference<container::XNamed> xBkmNamed(xBookmark, + uno::UNO_QUERY_THROW); + xBkmNamed->setName(sLastBookmarkName); + xTextAppend->insertTextContent( + uno::Reference<text::XTextRange>(xCursor, uno::UNO_QUERY_THROW), + xBookmark, !xCursor->isCollapsed()); + } + } + // restore redline options again xDocProps->setPropertyValue(aRecordChanges, aPreviousValue); } }