tdf#144576 Copy a table from Writer to plain text editor as a Matrix

sw ascii filter
 - check for table nodes, output them seperately with formating
   to be displayed as a matrix when copy/pasted to a text file

sw qa filter ascii
 - add new test suite along with test to check for correct output

Change-Id: I8ca31bced3860e8e9752db8530ea6caaf31f2e5e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/164833
Reviewed-by: Hossein <hossein@libreoffice.org>
Tested-by: Jenkins
This commit is contained in:
Adam Seskunas 2024-03-14 06:09:08 -07:00 committed by Hossein
parent 779a3b26ed
commit 3e7de6b1bc
5 changed files with 289 additions and 1 deletions

View file

@ -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:

View file

@ -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 \

View file

@ -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 <swmodeltestbase.hxx>
#include <unotxdoc.hxx>
#include <itabenum.hxx>
#include <wrtsh.hxx>
#include <com/sun/star/text/XTextTable.hpp>
#include <ndtxt.hxx>
#include <swdtflvr.hxx>
#include <swmodule.hxx>
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_<format>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<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XNameAccess> xTableNames = xTablesSupplier->getTextTables();
CPPUNIT_ASSERT(xTableNames->hasByName("Table1"));
uno::Reference<text::XTextTable> 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<SwTransferable> 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<OUString>::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<text::XTextRange> xCellA1(xTable1->getCellByName("A1"), uno::UNO_QUERY);
xCellA1->setString(u""_ustr);
uno::Reference<text::XTextRange> 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<SwTransferable> 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: */

View file

@ -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 )
{

View file

@ -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);