tdf#95806 tdf#125877 tdf#141172 DOCX: fix tables in footnotes
and endnotes by converting them to floating tables during the
import, and removing floating at the DOCX export.
Writer core doesn't support non-floating tables in footnotes
and endnotes officially, (flowfrm:cxx: "Tables in footnotes
are not truly supported"), so their DOCX import resulted
serious problems:
– missing table paint (tdf#95806);
– table loss during saving to ODT (tdf#125877);
– table loss during copying them or their footnotes and
endnotes in the document (this resulted the regression
of the optimized footnote and endnote import: tdf#141172);
– table loss during changing the order of the chapters in
the Navigator.
Change-Id: Ife8af41936ae3ab003a3a9ad33b98c1d813e9c82
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/113162
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/qa/extras/odfexport/data/tdf95806.docx b/sw/qa/extras/odfexport/data/tdf95806.docx
new file mode 100644
index 0000000..65bfaae
--- /dev/null
+++ b/sw/qa/extras/odfexport/data/tdf95806.docx
Binary files differ
diff --git a/sw/qa/extras/odfexport/odfexport.cxx b/sw/qa/extras/odfexport/odfexport.cxx
index 841019b..f570e9f 100644
--- a/sw/qa/extras/odfexport/odfexport.cxx
+++ b/sw/qa/extras/odfexport/odfexport.cxx
@@ -263,6 +263,23 @@
CPPUNIT_ASSERT_EQUAL(OUString("** Expression is faulty **"), xE2->getString());
}
DECLARE_ODFEXPORT_TEST(testTdf125877, "tdf95806.docx")
{
CPPUNIT_ASSERT_EQUAL(1, getPages());
uno::Reference<text::XTextTablesSupplier> xSupplier(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XIndexAccess> xTables(xSupplier->getTextTables(), uno::UNO_QUERY);
uno::Reference<text::XTextTablesSupplier> xTextTablesSupplier(mxComponent, uno::UNO_QUERY);
// This was 0 (lost table during ODT export in footnotes)
// Note: fix also tdf#95806: painting table layout is correct
CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTables->getCount());
// floating table: there is a frame now
uno::Reference<text::XTextFramesSupplier> xTextFramesSupplier(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XIndexAccess> xIndexAccess(xTextFramesSupplier->getTextFrames(), uno::UNO_QUERY);
CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());
}
DECLARE_ODFEXPORT_TEST(testTdf103567, "tdf103567.odt")
{
CPPUNIT_ASSERT_EQUAL(1, getShapes());
diff --git a/sw/qa/extras/ooxmlexport/data/tdf141172.docx b/sw/qa/extras/ooxmlexport/data/tdf141172.docx
new file mode 100644
index 0000000..0e1647a
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf141172.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
index 77fcb3f..cd3ca2f 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
@@ -1169,6 +1169,14 @@
assertXPath(pXml, "/w:document/w:body/w:tbl", 2);
}
DECLARE_OOXMLEXPORT_EXPORTONLY_TEST(testTdf141172, "tdf141172.docx")
{
xmlDocUniquePtr pXml = parseExport("word/endnotes.xml");
CPPUNIT_ASSERT(pXml);
// This was 1 (lost table during copying endnote content)
assertXPath(pXml, "/w:endnotes/w:endnote/w:tbl", 2);
}
DECLARE_OOXMLEXPORT_TEST(testContSectBreakHeaderFooter, "cont-sect-break-header-footer.docx")
{
// Load a document with a continuous section break on page 2.
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index 4b9c728..f4688e5f 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -3982,7 +3982,10 @@
m_pSerializer->singleElementNS(XML_w, XML_tblLook, pAttributeList);
}
else if (rGrabBagElement.first == "TablePosition" )
else if (rGrabBagElement.first == "TablePosition" &&
// skip empty table position (tables in footnotes converted to
// floating tables temporarily, don't export this)
rGrabBagElement.second != uno::Any() )
{
rtl::Reference<FastAttributeList> attrListTablePos = FastSerializerHelper::createAttrList( );
const uno::Sequence<beans::PropertyValue> aTablePosition = rGrabBagElement.second.get<uno::Sequence<beans::PropertyValue> >();
diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
index fd21827..55c4874 100644
--- a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
+++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
@@ -32,6 +32,7 @@
#include <com/sun/star/table/XCellRange.hpp>
#include <com/sun/star/text/HoriOrientation.hpp>
#include <com/sun/star/text/SizeType.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/XTextField.hpp>
#include <com/sun/star/text/XTextRangeCompare.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
@@ -339,9 +340,43 @@
rInfo.nRightBorderDistance = nActualR;
}
void lcl_fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties)
{
// fill empty frame properties to create an invisible frame around the table:
// hide frame borders and zero inner and outer frame margins
beans::PropertyValue aValue;
aValue.Name = getPropertyName( PROP_ANCHOR_TYPE );
aValue.Value <<= text::TextContentAnchorType_AS_CHARACTER;
rFrameProperties.push_back(aValue);
table::BorderLine2 aEmptyBorder;
static const std::vector<std::u16string_view> aBorderNames
= { u"TopBorder", u"LeftBorder", u"BottomBorder", u"RightBorder" };
for (size_t i = 0; i < aBorderNames.size(); ++i)
{
beans::PropertyValue aBorderValue;
aBorderValue.Name = aBorderNames[i];
aBorderValue.Value <<= aEmptyBorder;
rFrameProperties.push_back(aBorderValue);
}
static const std::vector<std::u16string_view> aMarginNames
= { u"TopBorderDistance", u"LeftBorderDistance",
u"BottomBorderDistance", u"RightBorderDistance",
u"TopMargin", u"LeftMargin", u"BottomMargin", u"RightMargin" };
for (size_t i = 0; i < aMarginNames.size(); ++i)
{
beans::PropertyValue aMarginValue;
aMarginValue.Name = aMarginNames[i];
aMarginValue.Value <<= sal_Int32(10);
rFrameProperties.push_back(aMarginValue);
}
}
TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo & rInfo, std::vector<beans::PropertyValue>& rFrameProperties)
}
TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo & rInfo,
std::vector<beans::PropertyValue>& rFrameProperties,
bool bConvertToFloatingInFootnote)
{
// will receive the table style if any
TableStyleSheetEntry* pTableStyle = nullptr;
@@ -394,6 +429,11 @@
aGrabBag["TablePosition"] <<= aGrabBagTS;
}
else if (bConvertToFloatingInFootnote)
{
// define empty "TablePosition" to avoid export temporary floating
aGrabBag["TablePosition"] = uno::Any();
}
std::optional<PropertyMap::Property> aTableStyleVal = m_aTableProperties->getProperty(META_PROP_TABLE_STYLE_NAME);
if(aTableStyleVal)
@@ -1375,7 +1415,15 @@
(m_rDMapper_Impl.getTableManager().getCurrentTablePosition());
TableInfo aTableInfo;
aTableInfo.nNestLevel = nestedTableLevel;
aTableInfo.pTableStyle = endTableGetTableStyle(aTableInfo, aFrameProperties);
// non-floating tables need floating in footnotes and endnotes, because
// Writer core cannot handle (i.e. save in ODT, copy, edit etc.) them otherwise
bool bConvertToFloating = aFrameProperties.empty() &&
nestedTableLevel <= 1 &&
m_rDMapper_Impl.IsInFootOrEndnote();
bool bFloating = !aFrameProperties.empty() || bConvertToFloating;
aTableInfo.pTableStyle = endTableGetTableStyle(aTableInfo, aFrameProperties, bConvertToFloating);
// expands to uno::Sequence< Sequence< beans::PropertyValues > >
std::vector<HorizontallyMergedCell> aMerges;
@@ -1392,7 +1440,8 @@
uno::Reference<text::XTextRange> xStart;
uno::Reference<text::XTextRange> xEnd;
bool bFloating = !aFrameProperties.empty();
if ( bConvertToFloating )
lcl_fillEmptyFrameProperties(aFrameProperties);
// OOXML table style may contain paragraph properties, apply these on cell paragraphs
if ( m_aTableRanges[0].hasElements() && m_aTableRanges[0][0].hasElements() )
@@ -1585,7 +1634,11 @@
sal_Int32 nTableWidthType = text::SizeType::FIX;
m_aTableProperties->getValue(TablePropertyMap::TABLE_WIDTH_TYPE, nTableWidthType);
if (m_rDMapper_Impl.GetSectionContext() && nestedTableLevel <= 1 && !m_rDMapper_Impl.IsInHeaderFooter())
m_rDMapper_Impl.m_aPendingFloatingTables.emplace_back(xStart, xEnd, comphelper::containerToSequence(aFrameProperties), nTableWidth, nTableWidthType);
{
m_rDMapper_Impl.m_aPendingFloatingTables.emplace_back(xStart, xEnd,
comphelper::containerToSequence(aFrameProperties),
nTableWidth, nTableWidthType, bConvertToFloating);
}
else
{
// m_xText points to the body text, get the current xText from m_rDMapper_Impl, in case e.g. we would be in a header.
diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.hxx b/writerfilter/source/dmapper/DomainMapperTableHandler.hxx
index 900ff74..5a2a37a 100644
--- a/writerfilter/source/dmapper/DomainMapperTableHandler.hxx
+++ b/writerfilter/source/dmapper/DomainMapperTableHandler.hxx
@@ -70,7 +70,9 @@
/// Did we have a foot or endnote in this table?
bool m_bHadFootOrEndnote;
TableStyleSheetEntry * endTableGetTableStyle(TableInfo & rInfo, std::vector<css::beans::PropertyValue>& rFrameProperties);
TableStyleSheetEntry * endTableGetTableStyle(TableInfo & rInfo,
std::vector<css::beans::PropertyValue>& rFrameProperties,
bool bConvertToFloating);
CellPropertyValuesSeq_t endTableGetCellProperties(TableInfo & rInfo, std::vector<HorizontallyMergedCell>& rMerges);
css::uno::Sequence<css::beans::PropertyValues> endTableGetRowProperties();
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index 6aa3391..1b29e96 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -395,16 +395,19 @@
sal_Int32 m_nTableWidthType;
/// Break type of the section that contains this table.
sal_Int32 m_nBreakType = -1;
/// Tables in footnotes and endnotes are always floating
bool m_bConvertToFloatingInFootnote = false;
FloatingTableInfo(css::uno::Reference<css::text::XTextRange> const& xStart,
css::uno::Reference<css::text::XTextRange> const& xEnd,
const css::uno::Sequence<css::beans::PropertyValue>& aFrameProperties,
sal_Int32 nTableWidth, sal_Int32 nTableWidthType)
sal_Int32 nTableWidth, sal_Int32 nTableWidthType, bool bConvertToFloatingInFootnote)
: m_xStart(xStart),
m_xEnd(xEnd),
m_aFrameProperties(aFrameProperties),
m_nTableWidth(nTableWidth),
m_nTableWidthType(nTableWidthType)
m_nTableWidthType(nTableWidthType),
m_bConvertToFloatingInFootnote(bConvertToFloatingInFootnote)
{
}
css::uno::Any getPropertyValue(std::u16string_view propertyName);
diff --git a/writerfilter/source/dmapper/PropertyMap.cxx b/writerfilter/source/dmapper/PropertyMap.cxx
index f7a8f80..cc556dd 100644
--- a/writerfilter/source/dmapper/PropertyMap.cxx
+++ b/writerfilter/source/dmapper/PropertyMap.cxx
@@ -1138,6 +1138,9 @@
bool SectionPropertyMap::FloatingTableConversion( const DomainMapper_Impl& rDM_Impl, FloatingTableInfo& rInfo )
{
// always convert non-floating tables to floating ones in footnotes and endnotes
if ( rInfo.m_bConvertToFloatingInFootnote )
return true;
// This is OOXML version of the code deciding if the table needs to be
// in a floating frame.
// For ww8 code, see SwWW8ImplReader::FloatingTableConversion in
@@ -1388,12 +1391,15 @@
// Text area width is known at the end of a section: decide if tables should be converted or not.
std::vector<FloatingTableInfo>& rPendingFloatingTables = rDM_Impl.m_aPendingFloatingTables;
uno::Reference<text::XTextAppendAndConvert> xBodyText( rDM_Impl.GetBodyText(), uno::UNO_QUERY );
for ( FloatingTableInfo & rInfo : rPendingFloatingTables )
{
rInfo.m_nBreakType = m_nBreakType;
if ( FloatingTableConversion( rDM_Impl, rInfo ) )
{
uno::Reference<text::XTextAppendAndConvert> xBodyText(
rInfo.m_bConvertToFloatingInFootnote
? rInfo.m_xStart->getText()
: rDM_Impl.GetBodyText(), uno::UNO_QUERY );
std::deque<css::uno::Any> aFramedRedlines = rDM_Impl.m_aStoredRedlines[StoredRedlines::FRAME];
try
{