tdf#81100 docx export: write tblHeader=false for first row

A whether a table repeats a header row
is often defined by a firstRow table style.

If our table was (grabbagged) as using that firstRow style,
but has been explicitly been set to override that
and not repeat the first row,
then that needs to be explicitly written out
to avoid inheriting the style's setting on the round-trip.

make CppunitTest_sw_ooxmlexport26 CPPUNIT_TEST_NAME=testTdf81100

Change-Id: Id581fb316ef9aa88cb53e87c50eb40fc0a3ecbc0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/202254
Tested-by: Jenkins
Reviewed-by: Justin Luth <jluth@mail.com>
This commit is contained in:
Justin Luth
2026-03-23 13:52:28 +01:00
parent 0caed612b3
commit be50d7da0a
4 changed files with 67 additions and 10 deletions
+4 -5
View File
@@ -382,23 +382,22 @@ DECLARE_OOXMLEXPORT_TEST(testTdf58944RepeatingTableHeader, "tdf58944-repeating-t
CPPUNIT_TEST_FIXTURE(Test, testTdf81100)
{
auto verify = [this](bool bIsExport = false) {
auto verify = [this]() {
xmlDocUniquePtr pDump = parseLayoutDump();
CPPUNIT_ASSERT_EQUAL(3, getPages());
// table starts on page 1 and finished on page 2
// and it has got only a single repeating header line
// with no repeating header line (explicit tblHeader="0")
assertXPath(pDump, "/root/page[2]/body/tab[1]", 1);
assertXPath(pDump, "/root/page[2]/body/tab[1]/row", 2);
assertXPath(pDump, "/root/page[3]/body/tab", 1);
if (!bIsExport) // TODO export tblHeader=false
assertXPath(pDump, "/root/page[3]/body/tab/row", 1);
assertXPath(pDump, "/root/page[3]/body/tab/row", 1);
};
createSwDoc("tdf81100.docx");
verify();
saveAndReload(TestFilter::DOCX);
verify(/*bIsExport*/ true);
verify();
xmlDocUniquePtr pXmlDoc = parseExport(u"word/styles.xml"_ustr);
CPPUNIT_ASSERT(pXmlDoc);
+41 -1
View File
@@ -457,6 +457,36 @@ bool lcl_hasParaSdtEndBefore(const SwNode& rNode)
return rMap.contains(u"ParaSdtEndBefore"_ustr);
}
bool lcl_HasTblHeaderFromStyle(const SwFrameFormat* pTableFormat,
const DocxTableStyleExport& rTableStyleExport)
{
// does the table actually apply a table style, and does it use the firstRow style?
const std::map<OUString, css::uno::Any>& rGrabBag
= pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG)->GetGrabBag();
bool bIsUsingFirstRow = false;
OUString sStyleId;
for (const auto& rElement : rGrabBag)
{
if (rElement.first == "TableStyleName")
sStyleId = rElement.second.get<OUString>();
else if (rElement.first == "TableStyleLook")
{
for (const auto& rTblLook : rElement.second.get<uno::Sequence<beans::PropertyValue>>())
{
if (rTblLook.Name == "val")
{
bIsUsingFirstRow = rTblLook.Value.get<sal_Int32>() & 0x020;
break;
}
}
}
}
if (!bIsUsingFirstRow || sStyleId.isEmpty())
return false;
return rTableStyleExport.FirstRowHasTblHeader(sStyleId);
}
} // end anonymous namespace
void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ )
@@ -5061,7 +5091,17 @@ void DocxAttributeOutput::StartTableRow( ww8::WW8TableNodeInfoInner::Pointer_t c
// Header row: tblHeader
const SwTable *pTable = pTableTextNodeInfoInner->getTable( );
if ( pTable->GetRowsToRepeat( ) > pTableTextNodeInfoInner->getRow( ) )
m_pSerializer->singleElementNS(XML_w, XML_tblHeader, FSNS(XML_w, XML_val), "true"); // TODO to overwrite table style may need explicit false
m_pSerializer->singleElementNS(XML_w, XML_tblHeader, FSNS(XML_w, XML_val), "true");
else if (!pTableTextNodeInfoInner->getRow()) // first row
{
// Perhaps there is a table style attached to this table,
// and perhaps that table style has a firstRow tblStylePr defined
// and perhaps our tblLook uses that firstRow style
// and perhaps the firstRow style defines a tblHeader
// then in that case we need to write an explicit false to dis-inherit.
if (lcl_HasTblHeaderFromStyle(pTable->GetFrameFormat(), *m_pTableStyleExport))
m_pSerializer->singleElementNS(XML_w, XML_tblHeader, FSNS(XML_w, XML_val), "false");
}
TableRowRedline( pTableTextNodeInfoInner );
TableHeight( pTableTextNodeInfoInner );
+19 -4
View File
@@ -31,6 +31,7 @@ struct DocxTableStyleExport::Impl
private:
SwDoc& m_rDoc;
sax_fastparser::FSHelperPtr m_pSerializer;
OUString m_sCurrentStyle;
public:
Impl(SwDoc& rDoc)
@@ -38,6 +39,8 @@ public:
{
}
std::vector<OUString> m_vStylesWithTblHeaderInFirstRow;
void TableStyle(const uno::Sequence<beans::PropertyValue>& rStyle);
void setSerializer(sax_fastparser::FSHelperPtr pSerializer)
@@ -559,7 +562,11 @@ void DocxTableStyleExport::Impl::tableStyleTrPr(const uno::Sequence<beans::Prope
for (const auto& rProp : rTrPr)
{
if (rProp.Name == "tblHeader")
{
m_pSerializer->singleElementNS(XML_w, XML_tblHeader);
if (!m_sCurrentStyle.isEmpty())
m_vStylesWithTblHeaderInFirstRow.push_back(m_sCurrentStyle);
}
}
m_pSerializer->endElementNS(XML_w, XML_trPr);
@@ -648,7 +655,6 @@ void DocxTableStyleExport::Impl::TableStyle(const uno::Sequence<beans::PropertyV
bool bQFormat = false;
bool bSemiHidden = false;
bool bUnhideWhenUsed = false;
OUString aStyleId;
OUString aName;
OUString aBasedOn;
OUString aRsid;
@@ -658,6 +664,7 @@ void DocxTableStyleExport::Impl::TableStyle(const uno::Sequence<beans::PropertyV
uno::Sequence<beans::PropertyValue> aTablePr;
uno::Sequence<beans::PropertyValue> aTcPr;
std::vector<uno::Sequence<beans::PropertyValue>> aTableStylePrs;
m_sCurrentStyle.clear();
for (const auto& rProp : rStyle)
{
if (rProp.Name == "default")
@@ -665,7 +672,7 @@ void DocxTableStyleExport::Impl::TableStyle(const uno::Sequence<beans::PropertyV
else if (rProp.Name == "customStyle")
bCustomStyle = rProp.Value.get<bool>();
else if (rProp.Name == "styleId")
aStyleId = rProp.Value.get<OUString>();
m_sCurrentStyle = rProp.Value.get<OUString>();
else if (rProp.Name == "name")
aName = rProp.Value.get<OUString>();
else if (rProp.Name == "basedOn")
@@ -699,8 +706,8 @@ void DocxTableStyleExport::Impl::TableStyle(const uno::Sequence<beans::PropertyV
pAttributeList->add(FSNS(XML_w, XML_default), "1");
if (bCustomStyle)
pAttributeList->add(FSNS(XML_w, XML_customStyle), "1");
if (!aStyleId.isEmpty())
pAttributeList->add(FSNS(XML_w, XML_styleId), aStyleId);
if (!m_sCurrentStyle.isEmpty())
pAttributeList->add(FSNS(XML_w, XML_styleId), m_sCurrentStyle);
m_pSerializer->startElementNS(XML_w, XML_style, pAttributeList);
m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), aName);
@@ -727,6 +734,14 @@ void DocxTableStyleExport::Impl::TableStyle(const uno::Sequence<beans::PropertyV
m_pSerializer->endElementNS(XML_w, XML_style);
}
bool DocxTableStyleExport::FirstRowHasTblHeader(const OUString& rStyleId) const
{
auto pIter = std::find_if(std::cbegin(m_pImpl->m_vStylesWithTblHeaderInFirstRow),
std::cend(m_pImpl->m_vStylesWithTblHeaderInFirstRow),
[rStyleId](const OUString& rName) { return rName == rStyleId; });
return (pIter != std::cend(m_pImpl->m_vStylesWithTblHeaderInFirstRow));
}
void DocxTableStyleExport::SetSerializer(const sax_fastparser::FSHelperPtr& pSerializer)
{
m_pImpl->setSerializer(pSerializer);
@@ -35,6 +35,9 @@ public:
void SetSerializer(const sax_fastparser::FSHelperPtr& pSerializer);
DocxTableStyleExport(SwDoc& rDoc, const sax_fastparser::FSHelperPtr& pSerializer);
~DocxTableStyleExport();
// This function only returns useful/cached results after EndStyles - i.e. in WriteMainText
bool FirstRowHasTblHeader(const OUString& rStyleId) const;
};
#endif // INCLUDED_SW_SOURCE_FILTER_WW8_DOCXTABLESTYLEEXPORT_HXX