sw/qa/extras/ooxmlexport/data/tdf165059_broken.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport12.cxx | 39 +++----- sw/qa/extras/ooxmlexport/ooxmlexport21.cxx | 13 ++ sw/source/filter/ww8/docxattributeoutput.cxx | 91 +++++++++++++------- sw/source/filter/ww8/docxattributeoutput.hxx | 8 + 5 files changed, 94 insertions(+), 57 deletions(-)
New commits: commit 7b57004cf20ae1715eabd6623ad9007a68a0a32e Author: Jaume Pujantell <jaume.pujant...@collabora.com> AuthorDate: Tue Mar 4 23:04:42 2025 +0100 Commit: Jaume Pujantell <jaume.pujant...@collabora.com> CommitDate: Fri Mar 7 17:31:26 2025 +0100 tdf#165059 sw fix not valid moveFrom/moveTo tag According to the ISO standard, w:moveFrom/To elements are only permitted within a move*RangeStart/End section. If not inside such delimited range only w:ins and w:del are valid. We still write w:move* in paragraph properties outside of move*Range. This is technically also not valid, but lots of tests seem to have documents with this, and it is outside the scope of the bug. Change-Id: Iaf097b8b017aac1f6182a91abe464433e7664620 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182503 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182633 Reviewed-by: Jaume Pujantell <jaume.pujant...@collabora.com> Tested-by: Jenkins diff --git a/sw/qa/extras/ooxmlexport/data/tdf165059_broken.docx b/sw/qa/extras/ooxmlexport/data/tdf165059_broken.docx new file mode 100644 index 000000000000..548d4d9c7414 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf165059_broken.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport12.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport12.cxx index 7eb8f39f4137..e34b17467348 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport12.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport12.cxx @@ -1229,28 +1229,23 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf145720) loadAndSave("tdf104797.docx"); xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); // These were 0 (missing move*FromRange* elements) - assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFrom/w:moveFromRangeStart", 1); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]//w:moveFromRangeStart", 1); assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFromRangeEnd", 1); - assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveTo/w:moveToRangeStart", 1); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]//w:moveToRangeStart", 1); assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveToRangeEnd", 1); // paired names - assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFrom/w:moveFromRangeStart", "name", - u"move471382752"); - assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveTo/w:moveToRangeStart", "name", + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFromRangeStart", "name", u"move471382752"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveToRangeStart", "name", u"move471382752"); // mandatory authors and dates - assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFrom/w:moveFromRangeStart", "author", - u"Tekijä"); - assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveTo/w:moveToRangeStart", "author", - u"Tekijä"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFromRangeStart", "author", u"Tekijä"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveToRangeStart", "author", u"Tekijä"); // no date (anonymized change) // This failed, date was exported as w:date="0-00-00T00:00:00Z", and later "1970-01-01T00:00:00Z" - assertXPathNoAttribute(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFrom/w:moveFromRangeStart", - "date"); - assertXPathNoAttribute(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveTo/w:moveToRangeStart", - "date"); + assertXPathNoAttribute(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFromRangeStart", "date"); + assertXPathNoAttribute(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveToRangeStart", "date"); } CPPUNIT_TEST_FIXTURE(Test, testTdf150166) @@ -1553,11 +1548,10 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf132271) loadAndSave("tdf149388.docx"); xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); // import change tracking in floating tables - assertXPath(pXmlDoc, "//w:del", 1); - assertXPath(pXmlDoc, "//w:ins", 1); - // tracked text moving recognized during the import - assertXPath(pXmlDoc, "//w:moveFrom", 1); - assertXPath(pXmlDoc, "//w:moveTo", 1); + assertXPath(pXmlDoc, "//w:del", 2); + assertXPath(pXmlDoc, "//w:ins", 2); + assertXPath(pXmlDoc, "//w:moveFrom", 0); + assertXPath(pXmlDoc, "//w:moveTo", 0); } CPPUNIT_TEST_FIXTURE(Test, testTdf149388_fly) @@ -1578,11 +1572,10 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf136667) loadAndSave("tdf149388_fly.docx"); xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); // import change tracking in floating tables - assertXPath(pXmlDoc, "//w:del", 1); - assertXPath(pXmlDoc, "//w:ins", 3); - // tracked text moving recognized during the import - assertXPath(pXmlDoc, "//w:moveFrom", 1); - assertXPath(pXmlDoc, "//w:moveTo", 1); + assertXPath(pXmlDoc, "//w:del", 2); + assertXPath(pXmlDoc, "//w:ins", 4); + assertXPath(pXmlDoc, "//w:moveFrom", 0); + assertXPath(pXmlDoc, "//w:moveTo", 0); } CPPUNIT_TEST_FIXTURE(Test, testTdf136850) diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport21.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport21.cxx index d6642b17daa7..d6cf96a8a830 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport21.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport21.cxx @@ -1282,6 +1282,19 @@ CPPUNIT_TEST_FIXTURE(Test, testMsWordUlTrailSpace) assertXPath(pXmlSettings, "/w:settings/w:compat/w:ulTrailSpace"); } +CPPUNIT_TEST_FIXTURE(Test, testTdf165059_moveFromTo) +{ + loadAndSave("tdf165059_broken.docx"); + // Without the fix, exported contains w:move* ouside of move ranges + // Outside move range tags ins/del are valid + xmlDocUniquePtr p_XmlDoc = parseExport("word/document.xml"); + CPPUNIT_ASSERT(p_XmlDoc); + assertXPath(p_XmlDoc, "//w:moveTo"_ostr, 0); + assertXPath(p_XmlDoc, "//w:ins"_ostr, 1); + assertXPath(p_XmlDoc, "//w:moveFrom"_ostr, 0); + assertXPath(p_XmlDoc, "//w:del"_ostr, 1); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 813f1031fd05..6f1aa08eca3f 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -1644,13 +1644,13 @@ void DocxAttributeOutput::EndParagraphProperties(const SfxItemSet& rParagraphMar if ( pRedlineParagraphMarkerDeleted ) { - StartRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true ); - EndRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true ); + StartRedline(pRedlineParagraphMarkerDeleted, /*bLastRun=*/true, /*bParagraphProps=*/true); + EndRedline(pRedlineParagraphMarkerDeleted, /*bLastRun=*/true, /*bParagraphProps=*/true); } if ( pRedlineParagraphMarkerInserted ) { - StartRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true ); - EndRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true ); + StartRedline(pRedlineParagraphMarkerInserted, /*bLastRun=*/true, /*bParagraphProps=*/true); + EndRedline(pRedlineParagraphMarkerInserted, /*bLastRun=*/true, /*bParagraphProps=*/true); } // mergeTopMarks() after paragraph mark properties child elements. @@ -1964,6 +1964,13 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_In m_nHyperLinkCount.back()++; } + // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks. + // The same is applied for permission ranges. + // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end. + DoWriteBookmarksStart(m_rBookmarksStart, m_pMoveRedlineData); + DoWriteBookmarksEnd(m_rBookmarksEnd, false, false); // Write non-moverange bookmarks + DoWritePermissionsStart(); + DoWriteAnnotationMarks(); // if there is some redlining in the document, output it bool bSkipRedline = false; if (nLen == 1) @@ -1982,14 +1989,6 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_In StartRedline(m_pRedlineData, bLastRun); } - // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks. - // The same is applied for permission ranges. - // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end. - DoWriteBookmarksStart(m_rBookmarksStart, m_pMoveRedlineData); - DoWriteBookmarksEnd(m_rBookmarksEnd); - DoWritePermissionsStart(); - DoWriteAnnotationMarks(); - if (m_closeHyperlinkInThisRun && m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty() && m_hyperLinkAnchor.startsWith("_Toc")) { @@ -2063,6 +2062,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_In { EndRedline(m_pRedlineData, bLastRun); } + DoWriteBookmarksEnd(m_rBookmarksEnd, false, true); // Write moverange bookmarks if (nLen != -1) { @@ -2194,7 +2194,7 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_In } DoWriteBookmarksStart(m_rFinalBookmarksStart); - DoWriteBookmarksEnd(m_rFinalBookmarksEnd); + DoWriteBookmarksEnd(m_rFinalBookmarksEnd); // Write all final bookmarks DoWriteBookmarkEndIfExist(nPos); } @@ -2309,28 +2309,37 @@ void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& rStarts, } /// export the end bookmarks -void DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds) +void DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds, bool bWriteAllBookmarks, + bool bWriteOnlyMoveRanges) { - for (const OUString & bookmarkName : rEnds) + auto bookmarkNameIt = rEnds.begin(); + while (bookmarkNameIt != rEnds.end()) { // Get the id of the bookmark - auto pPos = m_rOpenedBookmarksIds.find(bookmarkName); + auto pPos = m_rOpenedBookmarksIds.find(*bookmarkNameIt); if (pPos != m_rOpenedBookmarksIds.end()) { bool bMove = false; bool bFrom = false; - GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom); + GetExport().BookmarkToWord(*bookmarkNameIt, &bMove, &bFrom); // Output the bookmark (including MoveBookmark of the tracked moving) - if ( bMove ) - DoWriteMoveRangeTagEnd(pPos->second, bFrom); - else - DoWriteBookmarkTagEnd(pPos->second); + if (bWriteAllBookmarks || (bMove == bWriteOnlyMoveRanges)) + { + if (bMove) + DoWriteMoveRangeTagEnd(pPos->second, bFrom); + else + DoWriteBookmarkTagEnd(pPos->second); - m_rOpenedBookmarksIds.erase(bookmarkName); + m_rOpenedBookmarksIds.erase(*bookmarkNameIt); + bookmarkNameIt = rEnds.erase(bookmarkNameIt); + } + else + ++bookmarkNameIt; } + else + bookmarkNameIt = rEnds.erase(bookmarkNameIt); } - rEnds.clear(); } // For construction of the special bookmark name template for permissions: @@ -4255,7 +4264,8 @@ void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData) // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that: // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node) // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node) -void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData, bool bLastRun ) +void DocxAttributeOutput::StartRedline(const SwRedlineData* pRedlineData, bool bLastRun, + bool bParagraphProps) { if ( !pRedlineData ) return; @@ -4278,9 +4288,18 @@ void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData, bool const DateTime& aDateTime = pRedlineData->GetTimeStamp(); bool bNoDate = bRemovePersonalInfo || ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 ); - bool bMoved = pRedlineData->IsMoved() && - // tdf#150166 save tracked moving around TOC as w:ins, w:del - SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr; + bool isInMoveBookmark = false; + for (const auto& openedBookmark : m_rOpenedBookmarksIds) + { + if (openedBookmark.first.startsWith(u"__RefMove")) + { + isInMoveBookmark = true; + break; + } + } + bool bMoved = (isInMoveBookmark || bParagraphProps) && pRedlineData->IsMoved() && + // tdf#150166 save tracked moving around TOC as w:ins, w:del + SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr; switch ( pRedlineData->GetType() ) { case RedlineType::Insert: @@ -4308,14 +4327,24 @@ void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData, bool } } -void DocxAttributeOutput::EndRedline( const SwRedlineData * pRedlineData, bool bLastRun ) +void DocxAttributeOutput::EndRedline(const SwRedlineData* pRedlineData, bool bLastRun, + bool bParagraphProps) { if ( !pRedlineData || m_bWritingField ) return; - bool bMoved = pRedlineData->IsMoved() && - // tdf#150166 save tracked moving around TOC as w:ins, w:del - SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr; + bool isInMoveBookmark = false; + for (const auto& openedBookmark : m_rOpenedBookmarksIds) + { + if (openedBookmark.first.startsWith(u"__RefMove")) + { + isInMoveBookmark = true; + break; + } + } + bool bMoved = (isInMoveBookmark || bParagraphProps) && pRedlineData->IsMoved() && + // tdf#150166 save tracked moving around TOC as w:ins, w:del + SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr; switch ( pRedlineData->GetType() ) { case RedlineType::Insert: diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index e19940a673a2..3bd1544003a1 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -269,12 +269,13 @@ public: /// /// Start of the tag that encloses the run, fills the info according to /// the value of pRedlineData. - void StartRedline( const SwRedlineData * pRedlineData, bool bLastRun ); + void StartRedline(const SwRedlineData* pRedlineData, bool bLastRun, + bool bParagraphProps = false); /// Output redlining. /// /// End of the tag that encloses the run. - void EndRedline( const SwRedlineData * pRedlineData, bool bLastRun ); + void EndRedline(const SwRedlineData* pRedlineData, bool bLastRun, bool bParagraphProps = false); virtual void SetStateOfFlyFrame( FlyProcessingState nStateOfFlyFrame ) override; virtual void SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode ) override; @@ -779,7 +780,8 @@ private: bool bFrom, const SwRedlineData* pRedlineData); void DoWriteMoveRangeTagEnd(sal_Int32 nId, bool bFrom); void DoWriteBookmarksStart(std::vector<OUString>& rStarts, const SwRedlineData* pRedlineData = nullptr); - void DoWriteBookmarksEnd(std::vector<OUString>& rEnds); + void DoWriteBookmarksEnd(std::vector<OUString>& rEnds, bool bWriteAllBookmarks = true, + bool bWriteOnlyMoveRanges = false); void DoWriteBookmarkStartIfExist(sal_Int32 nRunPos); void DoWriteBookmarkEndIfExist(sal_Int32 nRunPos);