diff --git a/sw/CppunitTest_sw_filter_ascii.mk b/sw/CppunitTest_sw_filter_ascii.mk new file mode 100644 index 000000000000..85a648c8ec69 --- /dev/null +++ b/sw/CppunitTest_sw_filter_ascii.mk @@ -0,0 +1,76 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,sw_filter_ascii)) + +$(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_filter_ascii)) + +$(eval $(call gb_CppunitTest_add_exception_objects,sw_filter_ascii, \ + sw/qa/filter/ascii/ascii \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,sw_filter_ascii, \ + comphelper \ + cppu \ + cppuhelper \ + editeng \ + sal \ + sfx \ + subsequenttest \ + svl \ + svx \ + svxcore \ + sw \ + swqahelper \ + test \ + unotest \ + utl \ + vcl \ + tl \ +)) + +$(eval $(call gb_CppunitTest_use_externals,sw_filter_ascii,\ + boost_headers \ + libxml2 \ +)) + +$(eval $(call gb_CppunitTest_set_include,sw_filter_ascii,\ + -I$(SRCDIR)/sw/inc \ + -I$(SRCDIR)/sw/source/core/inc \ + -I$(SRCDIR)/sw/source/uibase/inc \ + -I$(SRCDIR)/sw/qa/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_CppunitTest_use_api,sw_filter_ascii,\ + udkapi \ + offapi \ + oovbaapi \ +)) + +$(eval $(call gb_CppunitTest_use_ure,sw_filter_ascii)) +$(eval $(call gb_CppunitTest_use_vcl,sw_filter_ascii)) + +$(eval $(call gb_CppunitTest_use_rdb,sw_filter_ascii,services)) + +$(eval $(call gb_CppunitTest_use_custom_headers,sw_filter_ascii,\ + officecfg/registry \ +)) + +$(eval $(call gb_CppunitTest_use_configuration,sw_filter_ascii)) + +$(eval $(call gb_CppunitTest_use_uiconfigs,sw_filter_ascii, \ + modules/swriter \ +)) + +$(eval $(call gb_CppunitTest_use_more_fonts,sw_filter_ascii)) + +# vim: set noet sw=4 ts=4: diff --git a/sw/Module_sw.mk b/sw/Module_sw.mk index fc2afe9fb7d9..2db6fbace5f5 100644 --- a/sw/Module_sw.mk +++ b/sw/Module_sw.mk @@ -167,6 +167,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sw,\ CppunitTest_sw_filter_ww8 \ CppunitTest_sw_filter_html \ CppunitTest_sw_filter_xml \ + CppunitTest_sw_filter_ascii \ CppunitTest_sw_a11y \ CppunitTest_sw_core_theme \ CppunitTest_sw_pdf_test \ diff --git a/sw/qa/filter/ascii/ascii.cxx b/sw/qa/filter/ascii/ascii.cxx new file mode 100644 index 000000000000..d6c4773ca40f --- /dev/null +++ b/sw/qa/filter/ascii/ascii.cxx @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +/** + * Covers sw/source/filter/ascii/ fixes. + * + * Note that these tests are meant to be simple: either load a file and assert some result or build + * a document model with code, export and assert that result. + * + * Keep using the various sw_import/export suites for multiple filter calls inside a single + * test. + */ +class Test : public SwModelTestBase +{ +public: + Test() + : SwModelTestBase("/sw/qa/filter/ascii/data/") + { + } +}; + +CPPUNIT_TEST_FIXTURE(Test, testTdf144576_ascii) +{ + // Given a document with a 2x2 table + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0); + pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/2); + + uno::Reference xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName("Table1")); + uno::Reference xTable1(xTableNames->getByName("Table1"), uno::UNO_QUERY); + + pWrtShell->GotoTable(u"Table1"_ustr); + pWrtShell->Insert(u"A"_ustr); + pWrtShell->GoNextCell(false); + pWrtShell->Insert(u"B"_ustr); + pWrtShell->GoNextCell(false); + pWrtShell->Insert(u"C"_ustr); + pWrtShell->GoNextCell(false); + pWrtShell->Insert(u"D"_ustr); + + // Select the whole table + dispatchCommand(mxComponent, ".uno:SelectAll", {}); + dispatchCommand(mxComponent, ".uno:SelectAll", {}); + dispatchCommand(mxComponent, ".uno:SelectAll", {}); + + rtl::Reference xTransferable(new SwTransferable(*pWrtShell)); + xTransferable->Copy(); // Ctl-C + xTransferable.get(); + + // Get the plain text version of the selection + datatransfer::DataFlavor aFlavor; + aFlavor.MimeType = "text/plain;charset=utf-16"; + aFlavor.DataType = cppu::UnoType::get(); + uno::Any aData = xTransferable->getTransferData(aFlavor); + OUString aActual; + aData >>= aActual; + pWrtShell->ClearMark(); + + CPPUNIT_ASSERT(aData.hasValue()); + + OUString aExpected = u"A\tB"_ustr SAL_NEWLINE_STRING u"C\tD"_ustr SAL_NEWLINE_STRING u""_ustr; + + // Without the fix in place, the test fails with: + // - Expected: A B + // C D + // + // - Actual : A + // B + // C + // D + // i.e. Each cell is seperated by a tab + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); + + // Add some newlines in the first two cells + // and test to see if they're handled correctly + uno::Reference xCellA1(xTable1->getCellByName("A1"), uno::UNO_QUERY); + xCellA1->setString(u""_ustr); + uno::Reference xCellB1(xTable1->getCellByName("B1"), uno::UNO_QUERY); + xCellB1->setString(u""_ustr); + + pWrtShell->GotoTable(u"Table1"_ustr); + pWrtShell->Insert(u"A"_ustr); + pWrtShell->SplitNode(); + pWrtShell->Insert(u"A1"_ustr); + pWrtShell->GoNextCell(false); + pWrtShell->Insert(u"B"_ustr); + pWrtShell->SplitNode(); + pWrtShell->Insert(u"B1"_ustr); + pWrtShell->SplitNode(); + pWrtShell->Insert(u"B2"_ustr); + + aExpected + = u"A"_ustr SAL_NEWLINE_STRING u"A1\tB"_ustr SAL_NEWLINE_STRING u"B1"_ustr SAL_NEWLINE_STRING u"B2"_ustr SAL_NEWLINE_STRING u"C\tD"_ustr SAL_NEWLINE_STRING u""_ustr; + + // Select the whole table + dispatchCommand(mxComponent, ".uno:SelectAll", {}); + dispatchCommand(mxComponent, ".uno:SelectAll", {}); + dispatchCommand(mxComponent, ".uno:SelectAll", {}); + + // Get the plain text version of the selection + rtl::Reference xNewTransferable(new SwTransferable(*pWrtShell)); + xNewTransferable->Copy(); // Ctl-C + xNewTransferable.get(); + aData = xNewTransferable->getTransferData(aFlavor); + CPPUNIT_ASSERT(aData.hasValue()); + aData >>= aActual; + + // Without the fix in place, the test fails with: + // - Expected: A + // A1 B + // B1 + // B2 + // C D + // + // - Actual : A + // A1 + // B + // B1 + // B2 + // C + // D + // i.e. Each cell is seperated by a tab, a newline inside + // a cell gets written to the next line in the furthest + // left spot available + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/ascii/wrtasc.cxx b/sw/source/filter/ascii/wrtasc.cxx index 0f1e368b928e..39f5ec9cfb91 100644 --- a/sw/source/filter/ascii/wrtasc.cxx +++ b/sw/source/filter/ascii/wrtasc.cxx @@ -186,7 +186,14 @@ ErrCode SwASCWriter::WriteStream() } bWriteSttTag = false; } - Out( aASCNodeFnTab, *pNd, *this ); + + SwTableNode* pTableNd = pNd->FindTableNode(); + + // Handle a table + if (pTableNd && m_bWriteAll) + WriteTable(pTableNd, pNd); + else + Out( aASCNodeFnTab, *pNd, *this ); } bTstFly = false; // Testing once is enough } @@ -221,6 +228,56 @@ void SwASCWriter::SetupFilterOptions(SfxMedium& rMedium) } } +void SwASCWriter::WriteTable(SwTableNode* pTableNd, SwTextNode* pNd) +{ + OUString sPreLineEnd = this->m_sLineEnd; + m_sLineEnd = u""_ustr; + + const SwTableLine* pEndTabLine = pTableNd->GetTable().GetTabLines().back(); + const SwTableBox* pEndTabBox = pEndTabLine->GetTabBoxes().back(); + + for( const SwTableLine* pLine : pTableNd->GetTable().GetTabLines() ) + { + for( const SwTableBox* pBox : pLine->GetTabBoxes() ) + { + Out( aASCNodeFnTab, *pNd, *this ); + + Point aPrevBoxPoint = pNd->GetTableBox()->GetCoordinates(); + m_pCurrentPam->Move(fnMoveForward, GoInNode); + pNd = m_pCurrentPam->GetPoint()->GetNode().GetTextNode(); + + // Line break in a box + // Each line is a new SwTextNode so we + // need to parse inside the current box + while (pNd->GetTableBox() && (pNd->GetTableBox()->GetCoordinates() == aPrevBoxPoint)) + { + Strm().WriteUnicodeOrByteText(sPreLineEnd); + Out(aASCNodeFnTab, *pNd, *this); + + m_pCurrentPam->Move(fnMoveForward, GoInNode); + pNd = m_pCurrentPam->GetPoint()->GetNode().GetTextNode(); + } + if (pBox != pLine->GetTabBoxes().back()) + Strm().WriteUChar( 0x9 ); + + if (pBox == pEndTabBox) + this->m_sLineEnd = sPreLineEnd; + + }// end for each Box + + if (pLine == pEndTabLine) + { + m_pCurrentPam->Move(fnMoveBackward, GoInNode); + pNd = m_pCurrentPam->GetPoint()->GetNode().GetTextNode(); + Strm().WriteUnicodeOrByteText( sPreLineEnd ); + } + if (pLine != pEndTabLine) + Strm().WriteUnicodeOrByteText( sPreLineEnd ); + + }// end For each row + this->m_sLineEnd = sPreLineEnd; +} + void GetASCWriter( std::u16string_view rFltNm, [[maybe_unused]] const OUString& /*rBaseURL*/, WriterRef& xRet ) { diff --git a/sw/source/filter/ascii/wrtasc.hxx b/sw/source/filter/ascii/wrtasc.hxx index c2e3dfa9a2d4..7464c4eccd52 100644 --- a/sw/source/filter/ascii/wrtasc.hxx +++ b/sw/source/filter/ascii/wrtasc.hxx @@ -31,6 +31,7 @@ class SwASCWriter : public Writer OUString m_sLineEnd; virtual ErrCode WriteStream() override; + void WriteTable(SwTableNode* pTableNd, SwTextNode* pNd); public: SwASCWriter(std::u16string_view rFilterName);