diff --git a/writerfilter/CppunitTest_writerfilter_ooxml.mk b/writerfilter/CppunitTest_writerfilter_ooxml.mk new file mode 100644 index 000000000000..8d5d214f2470 --- /dev/null +++ b/writerfilter/CppunitTest_writerfilter_ooxml.mk @@ -0,0 +1,54 @@ +# -*- 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,writerfilter_ooxml)) + +$(eval $(call gb_CppunitTest_use_externals,writerfilter_ooxml,\ + boost_headers \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,writerfilter_ooxml, \ + writerfilter/qa/cppunittests/ooxml/ooxml \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,writerfilter_ooxml, \ + basegfx \ + comphelper \ + cppu \ + cppuhelper \ + oox \ + sal \ + subsequenttest \ + test \ + unotest \ + utl \ + tl \ + vcl \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,writerfilter_ooxml)) + +$(eval $(call gb_CppunitTest_use_ure,writerfilter_ooxml)) +$(eval $(call gb_CppunitTest_use_vcl,writerfilter_ooxml)) + +$(eval $(call gb_CppunitTest_use_rdb,writerfilter_ooxml,services)) + +$(eval $(call gb_CppunitTest_use_custom_headers,writerfilter_ooxml,\ + officecfg/registry \ +)) + +$(eval $(call gb_CppunitTest_use_configuration,writerfilter_ooxml)) + +# we need to explicitly depend on library writerfilter because it is not implied +# by a link relation +$(call gb_CppunitTest_get_target,writerfilter_ooxml) : $(call gb_Library_get_target,writerfilter) + +# vim: set noet sw=4 ts=4: diff --git a/writerfilter/Module_writerfilter.mk b/writerfilter/Module_writerfilter.mk index 46d7af0ff63a..587bf4fa4dfc 100644 --- a/writerfilter/Module_writerfilter.mk +++ b/writerfilter/Module_writerfilter.mk @@ -18,6 +18,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,writerfilter,\ CppunitTest_writerfilter_filters_test \ CppunitTest_writerfilter_misc \ CppunitTest_writerfilter_dmapper \ + CppunitTest_writerfilter_ooxml \ CppunitTest_writerfilter_rtftok \ )) diff --git a/writerfilter/qa/cppunittests/ooxml/data/floattable-tables-lost.docx b/writerfilter/qa/cppunittests/ooxml/data/floattable-tables-lost.docx new file mode 100644 index 000000000000..5d1d9624c8a5 Binary files /dev/null and b/writerfilter/qa/cppunittests/ooxml/data/floattable-tables-lost.docx differ diff --git a/writerfilter/qa/cppunittests/ooxml/ooxml.cxx b/writerfilter/qa/cppunittests/ooxml/ooxml.cxx new file mode 100644 index 000000000000..fb790252becb --- /dev/null +++ b/writerfilter/qa/cppunittests/ooxml/ooxml.cxx @@ -0,0 +1,48 @@ +/* -*- 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 + +using namespace ::com::sun::star; + +namespace +{ +/// Tests for writerfilter/source/ooxml/. +class Test : public UnoApiTest +{ +public: + Test() + : UnoApiTest("/writerfilter/qa/cppunittests/ooxml/data/") + { + } +}; + +CPPUNIT_TEST_FIXTURE(Test, testFloatingTablesLost) +{ + // Given a document with 2 floating tables, the 2nd has an inner floating table as well: + loadFromURL(u"floattable-tables-lost.docx"); + + // When counting the created Writer tables: + uno::Reference xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference xTables(xTextDocument->getTextTables(), uno::UNO_QUERY); + + // Then make sure that all 3 tables are imported: + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 3 + // - Actual : 1 + // i.e. only the inner table was imported, the 2 others were lost. + CPPUNIT_ASSERT_EQUAL(static_cast(3), xTables->getCount()); +} +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx b/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx index 3db1a7d92338..a585bfb800d1 100644 --- a/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx +++ b/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx @@ -412,6 +412,11 @@ void OOXMLFastContextHandler::startParagraphGroup() if (!isForwardEvents()) return; + if (mpParserState->GetFloatingTableEnded()) + { + mpParserState->SetFloatingTableEnded(false); + } + if (mpParserState->isInParagraphGroup()) endParagraphGroup(); @@ -1612,6 +1617,14 @@ void OOXMLFastContextHandlerTextTable::lcl_startFastElement (Token_t /*Element*/, const uno::Reference< xml::sax::XFastAttributeList > & /*Attribs*/) { + if (mpParserState->GetFloatingTableEnded()) + { + // We're starting a new table, but the previous table was floating. Insert a dummy paragraph + // to ensure that the floating table has a suitable anchor. + startParagraphGroup(); + endOfParagraph(); + } + mpParserState->startTable(); mnTableDepth++; @@ -1638,6 +1651,20 @@ void OOXMLFastContextHandlerTextTable::lcl_endFastElement mpParserState->setCharacterProperties(pProps); mnTableDepth--; + + OOXMLPropertySet::Pointer_t pTableProps = mpParserState->GetTableProperties(); + if (pTableProps) + { + for (const auto& rTableProp : *pTableProps) + { + if (rTableProp->getId() == NS_ooxml::LN_CT_TblPrBase_tblpPr) + { + mpParserState->SetFloatingTableEnded(true); + break; + } + } + } + mpParserState->endTable(); } diff --git a/writerfilter/source/ooxml/OOXMLParserState.cxx b/writerfilter/source/ooxml/OOXMLParserState.cxx index fa87146e1169..b8cc55377b5b 100644 --- a/writerfilter/source/ooxml/OOXMLParserState.cxx +++ b/writerfilter/source/ooxml/OOXMLParserState.cxx @@ -207,6 +207,16 @@ void OOXMLParserState::setTableProperties(const OOXMLPropertySet::Pointer_t& pPr } } +OOXMLPropertySet::Pointer_t OOXMLParserState::GetTableProperties() const +{ + if (mTableProps.empty()) + { + return nullptr; + } + + return mTableProps.top(); +} + // tdf#108714 void OOXMLParserState::resolvePostponedBreak(Stream & rStream) { diff --git a/writerfilter/source/ooxml/OOXMLParserState.hxx b/writerfilter/source/ooxml/OOXMLParserState.hxx index 454cabb4c73c..626f8cdf326c 100644 --- a/writerfilter/source/ooxml/OOXMLParserState.hxx +++ b/writerfilter/source/ooxml/OOXMLParserState.hxx @@ -58,6 +58,8 @@ class OOXMLParserState final : public virtual SvRefBase std::vector maSavedAlternateStates; std::vector mvPostponedBreaks; bool mbStartFootnote; + /// We just ended a floating table. Starting a paragraph or table resets this. + bool m_bFloatingTableEnded = false; public: typedef tools::SvRef Pointer_t; @@ -101,6 +103,7 @@ public: void setRowProperties(const OOXMLPropertySet::Pointer_t& pProps); void resolveTableProperties(Stream& rStream); void setTableProperties(const OOXMLPropertySet::Pointer_t& pProps); + OOXMLPropertySet::Pointer_t GetTableProperties() const; // tdf#108714 void resolvePostponedBreak(Stream& rStream); void setPostponedBreak(const OOXMLPropertySet::Pointer_t& pProps); @@ -115,6 +118,12 @@ public: void setStartFootnote(bool bStartFootnote); bool isStartFootnote() const { return mbStartFootnote; } + + void SetFloatingTableEnded(bool bFloatingTableEnded) + { + m_bFloatingTableEnded = bFloatingTableEnded; + } + bool GetFloatingTableEnded() const { return m_bFloatingTableEnded; } }; }