sw/qa/extras/ooxmlexport/data/tdf152206.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport14.cxx | 17 +++ writerfilter/source/dmapper/DomainMapper.cxx | 13 ++ writerfilter/source/dmapper/DomainMapper_Impl.cxx | 98 ++++++++++++++-------- writerfilter/source/dmapper/DomainMapper_Impl.hxx | 10 +- 5 files changed, 100 insertions(+), 38 deletions(-)
New commits: commit 96a856f87f16cca2e039c973c18d57c8b9dca362 Author: László Németh <nem...@numbertext.org> AuthorDate: Fri Dec 16 13:20:25 2022 +0100 Commit: László Németh <nem...@numbertext.org> CommitDate: Mon Dec 19 07:34:36 2022 +0000 tdf#152206 DOCX import: fix mixed first footnote Also the first footnote can be at arbitrary place in footnote.xml. Follow-up to commit 09ae3c01940bbc25ffde51963683b04e3cb4bb6a "tdf#152203 DOCX import: fix mixed footnotes/endnotes". Change-Id: Iab356f7373483d812f0e802a994357fdad831d9d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144380 Tested-by: Jenkins Reviewed-by: László Németh <nem...@numbertext.org> diff --git a/sw/qa/extras/ooxmlexport/data/tdf152206.docx b/sw/qa/extras/ooxmlexport/data/tdf152206.docx new file mode 100644 index 000000000000..34f0130fdd7b Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf152206.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx index 9ce06be528e9..3916866b9ff4 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx @@ -1290,6 +1290,23 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf152203) CPPUNIT_ASSERT_EQUAL( OUString("Footnote for pg5"), xLastButOne->getString().trim() ); } +CPPUNIT_TEST_FIXTURE(Test, testTdf152206) +{ + loadAndSave("tdf152206.docx"); + xmlDocUniquePtr pXml = parseExport("word/footnotes.xml"); + CPPUNIT_ASSERT(pXml); + + uno::Reference<text::XFootnotesSupplier> xFootnotesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XIndexAccess> xFootnotes = xFootnotesSupplier->getFootnotes(); + uno::Reference<text::XTextRange> xLastFootnote(xFootnotes->getByIndex(1), uno::UNO_QUERY); + // This was "Footnote for pg5" (replaced footnotes) + CPPUNIT_ASSERT_EQUAL( OUString("Footnote for pg 6"), xLastFootnote->getString().trim() ); + + uno::Reference<text::XTextRange> xLastButOne(xFootnotes->getByIndex(0), uno::UNO_QUERY); + // This was "Footnote for pg 6" (replaced footnotes) + CPPUNIT_ASSERT_EQUAL( OUString("Footnote for pg5"), xLastButOne->getString().trim() ); +} + // skip test for macOS (missing fonts?) #if !defined(MACOSX) DECLARE_OOXMLEXPORT_TEST(testTdf146346, "tdf146346.docx") diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx index c6cf0d796434..3d5762c82cdd 100644 --- a/writerfilter/source/dmapper/DomainMapper.cxx +++ b/writerfilter/source/dmapper/DomainMapper.cxx @@ -3773,11 +3773,18 @@ void DomainMapper::lcl_checkId(const sal_Int32 nId) { if (m_pImpl->IsInFootnote()) { - if (m_pImpl->GetFootnoteCount() > -1) - m_pImpl->m_aFootnoteIds.push_back(nId); + m_pImpl->m_aFootnoteIds.push_back(nId); + // keep only the first real footnote + if (m_pImpl->GetFootnoteCount() == -1 && m_pImpl->m_aFootnoteIds.size() == 2) + m_pImpl->m_aFootnoteIds.pop_front(); } - else if (m_pImpl->GetEndnoteCount() > -1) + else + { m_pImpl->m_aEndnoteIds.push_back(nId); + // keep only the first real endnote + if (m_pImpl->GetEndnoteCount() == -1 && m_pImpl->m_aEndnoteIds.size() == 2) + m_pImpl->m_aEndnoteIds.pop_front(); + } } void DomainMapper::lcl_utext(const sal_uInt8 * data_, size_t len) diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx index e7f757f42055..09b1ba422aec 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -356,6 +356,8 @@ DomainMapper_Impl::DomainMapper_Impl( m_eSkipFootnoteState(SkipFootnoteSeparator::OFF), m_nFootnotes(-1), m_nEndnotes(-1), + m_nFirstFootnoteIndex(-1), + m_nFirstEndnoteIndex(-1), m_bLineNumberingSet( false ), m_bIsInFootnoteProperties( false ), m_bIsParaMarkerChange( false ), @@ -3628,6 +3630,36 @@ static void lcl_PasteRedlines( } } +bool DomainMapper_Impl::CopyTemporaryNotes( + uno::Reference< text::XFootnote > xNoteSrc, + uno::Reference< text::XFootnote > xNoteDest ) +{ + if (!m_bSaxError && xNoteSrc != xNoteDest) + { + uno::Reference< text::XText > xSrc( xNoteSrc, uno::UNO_QUERY_THROW ); + uno::Reference< text::XText > xDest( xNoteDest, uno::UNO_QUERY_THROW ); + uno::Reference< text::XTextCopy > xTxt, xTxt2; + xTxt.set( xSrc, uno::UNO_QUERY_THROW ); + xTxt2.set( xDest, uno::UNO_QUERY_THROW ); + xTxt2->copyText( xTxt ); + + // copy its redlines + std::vector<sal_Int32> redPos, redLen; + sal_Int32 redIdx; + enum StoredRedlines eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE; + lcl_CopyRedlines(xSrc, m_aStoredRedlines[eType], redPos, redLen, redIdx); + lcl_PasteRedlines(xDest, m_aStoredRedlines[eType], redPos, redLen, redIdx); + + // remove processed redlines + for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx) + 2; i++) + m_aStoredRedlines[eType].pop_front(); + + return true; + } + + return false; +} + void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes() { uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); @@ -3636,6 +3668,15 @@ void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes() if (GetFootnoteCount() > 0) { auto xFootnotes = xFootnotesSupplier->getFootnotes(); + if ( m_nFirstFootnoteIndex > 0 ) + { + uno::Reference< text::XFootnote > xFirstNote; + xFootnotes->getByIndex(0) >>= xFirstNote; + uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW ); + xText->setString(""); + xFootnotes->getByIndex(m_nFirstFootnoteIndex) >>= xNote; + CopyTemporaryNotes(xNote, xFirstNote); + } for (sal_Int32 i = GetFootnoteCount(); i > 0; --i) { xFootnotes->getByIndex(i) >>= xNote; @@ -3645,6 +3686,15 @@ void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes() if (GetEndnoteCount() > 0) { auto xEndnotes = xEndnotesSupplier->getEndnotes(); + if ( m_nFirstEndnoteIndex > 0 ) + { + uno::Reference< text::XFootnote > xFirstNote; + xEndnotes->getByIndex(0) >>= xFirstNote; + uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW ); + xText->setString(""); + xEndnotes->getByIndex(m_nFirstEndnoteIndex) >>= xNote; + CopyTemporaryNotes(xNote, xFirstNote); + } for (sal_Int32 i = GetEndnoteCount(); i > 0; --i) { xEndnotes->getByIndex(i) >>= xNote; @@ -3653,7 +3703,7 @@ void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes() } } -static void lcl_convertToNoteIndices(std::deque<sal_Int32>& rNoteIds) +static void lcl_convertToNoteIndices(std::deque<sal_Int32>& rNoteIds, sal_Int32& rFirstNoteIndex) { // convert arbitrary footnote identifiers to 0, 1, 2... // indices, keeping their possible random order @@ -3664,6 +3714,8 @@ static void lcl_convertToNoteIndices(std::deque<sal_Int32>& rNoteIds) aMapIds[aSortedIds[i]] = i; for (size_t i = 0; i < rNoteIds.size(); ++i) rNoteIds[i] = aMapIds[rNoteIds[i]]; + rFirstNoteIndex = rNoteIds.front(); + rNoteIds.pop_front(); } void DomainMapper_Impl::PopFootOrEndnote() @@ -3677,30 +3729,30 @@ void DomainMapper_Impl::PopFootOrEndnote() if ( IsInFootOrEndnote() && ( ( IsInFootnote() && GetFootnoteCount() > -1 && xFootnotesSupplier.is() ) || ( !IsInFootnote() && GetEndnoteCount() > -1 && xEndnotesSupplier.is() ) ) ) { - uno::Reference< text::XFootnote > xFootnoteFirst, xFootnoteLast; + uno::Reference< text::XFootnote > xNoteFirst, xNoteLast; auto xFootnotes = xFootnotesSupplier->getFootnotes(); auto xEndnotes = xEndnotesSupplier->getEndnotes(); if ( ( ( IsInFootnote() && xFootnotes->getCount() > 1 && - ( xFootnotes->getByIndex(xFootnotes->getCount()-1) >>= xFootnoteLast ) ) || + ( xFootnotes->getByIndex(xFootnotes->getCount()-1) >>= xNoteLast ) ) || ( !IsInFootnote() && xEndnotes->getCount() > 1 && - ( xEndnotes->getByIndex(xEndnotes->getCount()-1) >>= xFootnoteLast ) ) - ) && xFootnoteLast->getLabel().isEmpty() ) + ( xEndnotes->getByIndex(xEndnotes->getCount()-1) >>= xNoteLast ) ) + ) && xNoteLast->getLabel().isEmpty() ) { // copy content of the next temporary footnote try { if ( IsInFootnote() && !m_aFootnoteIds.empty() ) { - if ( m_aFootnoteIds.size() == sal::static_int_cast<size_t>(GetFootnoteCount()) ) - lcl_convertToNoteIndices(m_aFootnoteIds); - xFootnotes->getByIndex(m_aFootnoteIds.front() + 1) >>= xFootnoteFirst; + if ( m_nFirstFootnoteIndex == -1 ) + lcl_convertToNoteIndices(m_aFootnoteIds, m_nFirstFootnoteIndex); + xFootnotes->getByIndex(m_aFootnoteIds.front()) >>= xNoteFirst; m_aFootnoteIds.pop_front(); } else if ( !IsInFootnote() && !m_aEndnoteIds.empty() ) { - if ( m_aEndnoteIds.size() == sal::static_int_cast<size_t>(GetEndnoteCount()) ) - lcl_convertToNoteIndices(m_aEndnoteIds); - xEndnotes->getByIndex(m_aEndnoteIds.front() + 1) >>= xFootnoteFirst; + if ( m_nFirstEndnoteIndex == -1 ) + lcl_convertToNoteIndices(m_aEndnoteIds, m_nFirstEndnoteIndex); + xEndnotes->getByIndex(m_aEndnoteIds.front()) >>= xNoteFirst; m_aEndnoteIds.pop_front(); } else @@ -3711,28 +3763,8 @@ void DomainMapper_Impl::PopFootOrEndnote() TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert footnote/endnote"); m_bSaxError = true; } - if (!m_bSaxError && xFootnoteFirst != xFootnoteLast) - { - uno::Reference< text::XText > xSrc( xFootnoteFirst, uno::UNO_QUERY_THROW ); - uno::Reference< text::XText > xDest( xFootnoteLast, uno::UNO_QUERY_THROW ); - uno::Reference< text::XTextCopy > xTxt, xTxt2; - xTxt.set( xSrc, uno::UNO_QUERY_THROW ); - xTxt2.set( xDest, uno::UNO_QUERY_THROW ); - xTxt2->copyText( xTxt ); - - // copy its redlines - std::vector<sal_Int32> redPos, redLen; - sal_Int32 redIdx; - enum StoredRedlines eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE; - lcl_CopyRedlines(xSrc, m_aStoredRedlines[eType], redPos, redLen, redIdx); - lcl_PasteRedlines(xDest, m_aStoredRedlines[eType], redPos, redLen, redIdx); - - // remove processed redlines - for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx) + 2; i++) - m_aStoredRedlines[eType].pop_front(); - - bCopied = true; - } + + bCopied = CopyTemporaryNotes(xNoteFirst, xNoteLast); } } diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx index 1c0c9654c1b0..7c5792b7e4e4 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx @@ -558,8 +558,11 @@ private: /// Skip paragraphs from the <w:separator/> footnote SkipFootnoteSeparator m_eSkipFootnoteState; /// preload footnotes and endnotes - sal_Int32 m_nFootnotes; - sal_Int32 m_nEndnotes; + sal_Int32 m_nFootnotes; // footnote count + sal_Int32 m_nEndnotes; // endnote count + // these are the real first notes, use their content in the first notes + sal_Int32 m_nFirstFootnoteIndex; + sal_Int32 m_nFirstEndnoteIndex; bool m_bLineNumberingSet; bool m_bIsInFootnoteProperties; @@ -886,6 +889,9 @@ public: void IncrementFootnoteCount() { ++m_nFootnotes; } sal_Int32 GetEndnoteCount() const { return m_nEndnotes; } void IncrementEndnoteCount() { ++m_nEndnotes; } + bool CopyTemporaryNotes( + css::uno::Reference< css::text::XFootnote > xNoteSrc, + css::uno::Reference< css::text::XFootnote > xNoteDest ); void RemoveTemporaryFootOrEndnotes(); void PushAnnotation();