sw/qa/extras/ooxmlexport/ooxmlexport17.cxx | 34 ++++++++++++++++++++++ sw/source/filter/ww8/attributeoutputbase.hxx | 6 +++ sw/source/filter/ww8/docxattributeoutput.cxx | 41 +++++++++++++++++++++++++++ sw/source/filter/ww8/docxattributeoutput.hxx | 11 +++++++ sw/source/filter/ww8/wrtw8nds.cxx | 19 ++++++++++++ 5 files changed, 111 insertions(+)
New commits: commit 7c146a4dfaaf4c8e295803d1deb4d8d0be9cc9d5 Author: Miklos Vajna <[email protected]> AuthorDate: Wed Apr 13 08:34:33 2022 +0200 Commit: Miklos Vajna <[email protected]> CommitDate: Fri Apr 22 14:59:12 2022 +0200 sw content controls, DOCX export: handle SDT end at para end Once the DOCX import maps Run SDTs to SwContentControl, make -C sw -sr CppunitTest_sw_ooxmlexport17 CPPUNIT_TEST_NAME=testTdf148361 starts to fail without this fix. Previously we only tested SwContentControl end in the middle of a paragraph. Fix the paragraph end case similar to how hyperlinks are handled to be consistent. (cherry picked from commit ada2d449175afd9a6aa1db9281739e5ad6ac6fef) Change-Id: I0633ca8d7d909a2cb3c70e7edce3a85df5fac3f7 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133318 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 1caa49ed4036..038a5bd86520 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -368,7 +368,10 @@ void DocxAttributeOutput::StartContentControl(const SwFormatContentControl& rFor m_pContentControl = rFormatContentControl.GetContentControl(); } -void DocxAttributeOutput::EndContentControl() { ++m_nCloseContentControl; } +void DocxAttributeOutput::EndContentControl() +{ + ++m_nCloseContentControlInThisRun; +} static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutput) { @@ -1629,8 +1632,9 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool / m_bEndCharSdt = false; } - for (; m_nCloseContentControl > 0; --m_nCloseContentControl) + for (; m_nCloseContentControlInPreviousRun > 0; --m_nCloseContentControlInPreviousRun) { + // Not the last run of this paragraph. m_pSerializer->endElementNS(XML_w, XML_sdtContent); m_pSerializer->endElementNS(XML_w, XML_sdt); } @@ -1855,6 +1859,13 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool / m_pRedlineData = nullptr; } + for (; m_nCloseContentControlInThisRun > 0; --m_nCloseContentControlInThisRun) + { + // Last run of this paragraph. + m_pSerializer->endElementNS(XML_w, XML_sdtContent); + m_pSerializer->endElementNS(XML_w, XML_sdt); + } + if ( m_closeHyperlinkInThisRun ) { if ( m_startedHyperlink ) @@ -3267,6 +3278,10 @@ void DocxAttributeOutput::RunText( const OUString& rText, rtl_TextEncoding /*eCh { m_closeHyperlinkInPreviousRun = true; } + if (m_nCloseContentControlInThisRun > 0) + { + ++m_nCloseContentControlInPreviousRun; + } m_bRunTextIsOn = true; // one text can be split into more <w:t>blah</w:t>'s by line breaks etc. const sal_Unicode *pBegin = rText.getStr(); diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index 6a486dc76f6a..88ed18db6a6b 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -911,7 +911,8 @@ private: o3tl::sorted_vector<const SwFrameFormat*> m_aFloatingTablesOfParagraph; sal_Int32 m_nTextFrameLevel; - sal_Int32 m_nCloseContentControl = 0; + sal_Int32 m_nCloseContentControlInThisRun = 0; + sal_Int32 m_nCloseContentControlInPreviousRun = 0; // close of hyperlink needed bool m_closeHyperlinkInThisRun; commit 856667fa9b36701ba1ff4a45b3a5e505c37a2f26 Author: Miklos Vajna <[email protected]> AuthorDate: Tue Apr 12 08:25:16 2022 +0200 Commit: Miklos Vajna <[email protected]> CommitDate: Fri Apr 22 14:58:57 2022 +0200 sw content controls: add initial DOCX export Wrap the text portions inside the content control inside <w:sdtContent> and <w:sdt>. Also map the (so far) single property of it to <w:showingPlcHdr>. This is just initial export for inline text content controls, more properties are to be added in follow-up commits. (cherry picked from commit b40f9c536c1cefae8404a3f1c0080473151913d2) Conflicts: sw/qa/extras/ooxmlexport/ooxmlexport17.cxx Change-Id: I21e085496b4c79114b158656c5611aff8ffdd08a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133317 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx index 9a31041d6465..a3bd1a18094f 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx @@ -16,6 +16,8 @@ #include <com/sun/star/text/XTextFieldsSupplier.hpp> #include <com/sun/star/text/XTextField.hpp> #include <com/sun/star/util/XRefreshable.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/frame/XStorable.hpp> #include <comphelper/configuration.hxx> #include <comphelper/scopeguard.hxx> @@ -112,6 +114,38 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf148494) assertXPathContent(pXmlDoc, "/w:document/w:body/w:p/w:r[3]/w:instrText", " MACROBUTTON AllCaps Hello World "); } +CPPUNIT_TEST_FIXTURE(Test, testContentControlExport) +{ + // Given a document with a content control around one or more text portions: + mxComponent = loadFromDesktop("private:factory/swriter"); + uno::Reference<lang::XMultiServiceFactory> xMSF(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + xText->insertString(xCursor, "test", /*bAbsorb=*/false); + xCursor->gotoStart(/*bExpand=*/false); + xCursor->gotoEnd(/*bExpand=*/true); + uno::Reference<text::XTextContent> xContentControl( + xMSF->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY); + xContentControlProps->setPropertyValue("ShowingPlaceHolder", uno::makeAny(true)); + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + + // When exporting to DOCX: + save("Office Open XML Text", maTempFile); + mbExported = true; + + // Then make sure the expected markup is used: + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 0 + // XPath '//w:sdt/w:sdtPr/w:showingPlcHdr' number of nodes is incorrect + // i.e. the SDT elements were missing on export. + assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:showingPlcHdr", 1); + assertXPath(pXmlDoc, "//w:sdt/w:sdtContent", 1); +} + DECLARE_OOXMLEXPORT_TEST(testTdf137466, "tdf137466.docx") { xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx b/sw/source/filter/ww8/attributeoutputbase.hxx index 8461f2e1510c..a9331c9b628b 100644 --- a/sw/source/filter/ww8/attributeoutputbase.hxx +++ b/sw/source/filter/ww8/attributeoutputbase.hxx @@ -369,6 +369,12 @@ public: const OUString &rNumberingString, const SvxBrushItem* pBrush) = 0; // #i120928 export graphic of bullet + /// Output content control start. + virtual void StartContentControl(const SwFormatContentControl& /*rFormatContentControl*/) {} + + /// Output content control end. + virtual void EndContentControl() {} + protected: static void GetNumberPara( OUString& rStr, const SwField& rField ); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 4e58196ea74e..1caa49ed4036 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -363,6 +363,13 @@ void DocxAttributeOutput::WriteFloatingTable(ww8::Frame const* pParentFrame) m_rExport.SetFloatingTableFrame(nullptr); } +void DocxAttributeOutput::StartContentControl(const SwFormatContentControl& rFormatContentControl) +{ + m_pContentControl = rFormatContentControl.GetContentControl(); +} + +void DocxAttributeOutput::EndContentControl() { ++m_nCloseContentControl; } + static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutput) { const auto& rExport = rDocxAttributeOutput.GetExport(); @@ -1622,6 +1629,12 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool / m_bEndCharSdt = false; } + for (; m_nCloseContentControl > 0; --m_nCloseContentControl) + { + m_pSerializer->endElementNS(XML_w, XML_sdtContent); + m_pSerializer->endElementNS(XML_w, XML_sdt); + } + if ( m_closeHyperlinkInPreviousRun ) { if ( m_startedHyperlink ) @@ -1719,6 +1732,19 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool / m_nHyperLinkCount++; } + if (m_pContentControl) + { + m_pSerializer->startElementNS(XML_w, XML_sdt); + m_pSerializer->startElementNS(XML_w, XML_sdtPr); + if (m_pContentControl->GetShowingPlaceHolder()) + { + m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr); + } + m_pSerializer->endElementNS(XML_w, XML_sdtPr); + m_pSerializer->startElementNS(XML_w, XML_sdtContent); + m_pContentControl = nullptr; + } + // if there is some redlining in the document, output it StartRedline( m_pRedlineData ); diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index 80e1e0a5244d..6a486dc76f6a 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -52,6 +52,7 @@ class SwGrfNode; class SdrObject; enum class SvxBoxItemLine; enum class SwLineBreakClear; +class SwContentControl; namespace docx { class FootnotesList; } namespace oox::drawingml { class DrawingML; } @@ -404,6 +405,12 @@ public: void WriteFloatingTable(ww8::Frame const* pParentFrame); + /// See AttributeOutputBase::StartContentControl(). + void StartContentControl(const SwFormatContentControl& rFormatContentControl) override; + + /// See AttributeOutputBase::EndContentControl(). + void EndContentControl() override; + private: /// Initialize the structures where we are going to collect some of the paragraph properties. /// @@ -780,6 +787,7 @@ private: rtl::Reference<sax_fastparser::FastAttributeList> m_pSectionSpacingAttrList; rtl::Reference<sax_fastparser::FastAttributeList> m_pParagraphSpacingAttrList; rtl::Reference<sax_fastparser::FastAttributeList> m_pHyperlinkAttrList; + const SwContentControl* m_pContentControl = nullptr; /// If the current SDT around runs should be ended before the current run. bool m_bEndCharSdt; /// Attributes of the run color @@ -903,6 +911,8 @@ private: o3tl::sorted_vector<const SwFrameFormat*> m_aFloatingTablesOfParagraph; sal_Int32 m_nTextFrameLevel; + sal_Int32 m_nCloseContentControl = 0; + // close of hyperlink needed bool m_closeHyperlinkInThisRun; bool m_closeHyperlinkInPreviousRun; diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx index 6081ab30af1d..62a220a70f08 100644 --- a/sw/source/filter/ww8/wrtw8nds.cxx +++ b/sw/source/filter/ww8/wrtw8nds.cxx @@ -1408,6 +1408,14 @@ int SwWW8AttrIter::OutAttrWithRange(const SwTextNode& rNode, sal_Int32 nPos) --nRet; } break; + case RES_TXTATR_CONTENTCONTROL: + pEnd = pHt->End(); + if (nPos == *pEnd && nPos != pHt->GetStart()) + { + m_rExport.AttrOutput().EndContentControl(); + --nRet; + } + break; } if (nPos < pHt->GetAnyEnd()) break; // sorted by end @@ -1462,6 +1470,17 @@ int SwWW8AttrIter::OutAttrWithRange(const SwTextNode& rNode, sal_Int32 nPos) --nRet; } break; + case RES_TXTATR_CONTENTCONTROL: + if (nPos == pHt->GetStart()) + { + auto pFormatContentControl + = static_cast<const SwFormatContentControl*>(pItem); + m_rExport.AttrOutput().StartContentControl(*pFormatContentControl); + ++nRet; + } + // We know that the content control is never empty as it has a dummy character + // at least. + break; } if (nPos < pHt->GetStart()) break; // sorted by start
