sw/qa/filter/html/html.cxx         |   37 +++++++++++++++++++++++++++
 sw/source/filter/html/htmltabw.cxx |   49 +++++++++++++++++++++++++++++++------
 2 files changed, 78 insertions(+), 8 deletions(-)

New commits:
commit ccec05262ad389dd3d50ff75499ae8e5a877fc0f
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Thu Jan 12 13:57:16 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Jan 13 10:54:51 2023 +0100

    sw HTML export: fix invalid HTML when all cells of a row have the same 
rowspan
    
    The bugdoc has a table with 2 columns and 2 rows, but both the A1 and
    the A2 cell has rowspan=2, so there are only covered cells in the second
    row. It seems there is no valid HTML markup to express this Writer doc
    model.
    
    What HTML seems to suggest instead is to simply decrease the amount of
    rowspan attribute values and then the empty <tr> elements can be simply
    skipped.
    
    This fixes the
    
            Row 2 of a row group established by a tbody element has no cells 
beginning on it.
    
    from the w3c HTML validator.
    
    Note that you can't create such problematic tables on the UI: the UI
    will delete the row only containing covered cells for you when the last
    cell gets merged. Such documents can be created by importers, though.
    
    (cherry picked from commit a2fb3a135425bbc14375e1edfcc1e09a41d760f9)
    
    Conflicts:
            sw/source/filter/html/htmltabw.cxx
    
    Change-Id: Ice4fa3636e8b780d374f3d319b198aaaada9f5e0

diff --git a/sw/qa/filter/html/html.cxx b/sw/qa/filter/html/html.cxx
index f3b31efa7fe6..ef44aa39817a 100644
--- a/sw/qa/filter/html/html.cxx
+++ b/sw/qa/filter/html/html.cxx
@@ -8,6 +8,7 @@
  */
 
 #include <swmodeltestbase.hxx>
+#include <test/htmltesttools.hxx>
 
 #include <comphelper/propertyvalue.hxx>
 #include <vcl/gdimtf.hxx>
@@ -33,7 +34,7 @@ const char DATA_DIRECTORY[] = "/sw/qa/filter/html/data/";
  * Keep using the various sw_<format>import/export suites for multiple filter 
calls inside a single
  * test.
  */
-class Test : public SwModelTestBase
+class Test : public SwModelTestBase, public HtmlTestTools
 {
 };
 
@@ -181,6 +182,40 @@ CPPUNIT_TEST_FIXTURE(Test, testTableCellFloatValueType)
     assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:td", "sdval");
     assertXPathNoAttribute(pXmlDoc, "//reqif-xhtml:td", "sdnum");
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testTableRowSpanInAllCells)
+{
+    // Given a document with a 2x2 table, A1:A2 and B1:B2 is merged:
+    loadURL("private:factory/swriter", nullptr);
+    auto pTextDocument = dynamic_cast<SwXTextDocument*>(mxComponent.get());
+    SwDoc* pDoc = pTextDocument->GetDocShell()->GetDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0);
+    pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/2);
+    pWrtShell->MoveTable(GotoPrevTable, fnTableStart);
+    SwTableNode* pTableNode = 
pWrtShell->GetCursor()->GetNode().FindTableNode();
+    SwTable& rTable = pTableNode->GetTable();
+    auto pBox = const_cast<SwTableBox*>(rTable.GetTableBox("A1"));
+    pBox->setRowSpan(2);
+    pBox = const_cast<SwTableBox*>(rTable.GetTableBox("B1"));
+    pBox->setRowSpan(2);
+    pBox = const_cast<SwTableBox*>(rTable.GetTableBox("A2"));
+    pBox->setRowSpan(-1);
+    pBox = const_cast<SwTableBox*>(rTable.GetTableBox("B2"));
+    pBox->setRowSpan(-1);
+
+    // When exporting to HTML:
+    save("HTML (StarWriter)", maTempFile);
+
+    // Then make sure that the output is simplified to valid HTML, by omitting 
the rowspan attribute
+    // & the empty <tr> element:
+    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);
+    // Without the accompanying fix in place, this test would have failed with:
+    // - XPath '//tr[1]/td[1]' unexpected 'rowspan' attribute
+    // i.e. a combination of rowspan + empty <tr> was emitted.
+    assertXPathNoAttribute(pHtmlDoc, "//tr[1]/td[1]", "rowspan");
+    assertXPath(pHtmlDoc, "//tr", 1);
+}
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/filter/html/htmltabw.cxx 
b/sw/source/filter/html/htmltabw.cxx
index 73c1e1336e98..5cdbb8a5c205 100644
--- a/sw/source/filter/html/htmltabw.cxx
+++ b/sw/source/filter/html/htmltabw.cxx
@@ -63,12 +63,20 @@ class SwHTMLWrtTable : public SwWriteTable
     static void Pixelize( sal_uInt16& rValue );
     void PixelizeBorders();
 
+    /// Writes a single table cell.
+    ///
+    /// bCellRowSpan decides if the cell's row span should be written or not.
     void OutTableCell( SwHTMLWriter& rWrt, const SwWriteTableCell *pCell,
-                       bool bOutVAlign ) const;
+                       bool bOutVAlign,
+                       bool bCellRowSpan ) const;
 
+    /// Writes a single table row.
+    ///
+    /// rSkipRows decides if the next N rows should be skipped or written.
     void OutTableCells( SwHTMLWriter& rWrt,
                         const SwWriteTableCells& rCells,
-                        const SvxBrushItem *pBrushItem ) const;
+                        const SvxBrushItem *pBrushItem,
+                        sal_uInt16& rSkipRows ) const;
 
     virtual bool ShouldExpandSub( const SwTableBox *pBox,
                             bool bExpandedBefore, sal_uInt16 nDepth ) const 
override;
@@ -254,7 +262,8 @@ bool SwHTMLWrtTable::ShouldExpandSub( const SwTableBox 
*pBox,
 // Write a box as single cell
 void SwHTMLWrtTable::OutTableCell( SwHTMLWriter& rWrt,
                                    const SwWriteTableCell *pCell,
-                                   bool bOutVAlign ) const
+                                   bool bOutVAlign,
+                                   bool bCellRowSpan ) const
 {
     const SwTableBox *pBox = pCell->GetBox();
     sal_uInt16 nRow = pCell->GetRow();
@@ -307,7 +316,7 @@ void SwHTMLWrtTable::OutTableCell( SwHTMLWriter& rWrt,
     sOut.append(rWrt.GetNamespace() + aTag);
 
     // output ROW- and COLSPAN
-    if( nRowSpan>1 )
+    if (nRowSpan > 1 && bCellRowSpan)
     {
         sOut.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_rowspan).
             
append("=\"").append(static_cast<sal_Int32>(nRowSpan)).append("\"");
@@ -510,7 +519,8 @@ void SwHTMLWrtTable::OutTableCell( SwHTMLWriter& rWrt,
 // output a line as lines
 void SwHTMLWrtTable::OutTableCells( SwHTMLWriter& rWrt,
                                     const SwWriteTableCells& rCells,
-                                    const SvxBrushItem *pBrushItem ) const
+                                    const SvxBrushItem *pBrushItem,
+                                    sal_uInt16& rSkipRows ) const
 {
     // If the line contains more the one cell and all cells have the same
     // alignment, then output the VALIGN at the line instead of the cell.
@@ -560,9 +570,26 @@ void SwHTMLWrtTable::OutTableCells( SwHTMLWriter& rWrt,
 
     rWrt.IncIndentLevel(); // indent content of <TR>...</TR>
 
+    bool bCellRowSpan = true;
+    if (!rCells.empty() && rCells[0]->GetRowSpan() > 1)
+    {
+        // Skip the rowspan attrs of <td> elements if they are the same for 
every cell of this row.
+        bCellRowSpan = std::adjacent_find(rCells.begin(), rCells.end(),
+                                          [](const 
std::unique_ptr<SwWriteTableCell>& pA,
+                                             const 
std::unique_ptr<SwWriteTableCell>& pB)
+                                          { return pA->GetRowSpan() != 
pB->GetRowSpan(); })
+                       != rCells.end();
+        if (!bCellRowSpan)
+        {
+            // If no rowspan is written, then skip rows which would only 
contain covered cells, but
+            // not the current row.
+            rSkipRows = rCells[0]->GetRowSpan() - 1;
+        }
+    }
+
     for (const auto &rpCell : rCells)
     {
-        OutTableCell(rWrt, rpCell.get(), text::VertOrientation::NONE == 
eRowVertOri);
+        OutTableCell(rWrt, rpCell.get(), text::VertOrientation::NONE == 
eRowVertOri, bCellRowSpan);
     }
 
     rWrt.DecIndentLevel(); // indent content of <TR>...</TR>
@@ -826,11 +853,19 @@ void SwHTMLWrtTable::Write( SwHTMLWriter& rWrt, sal_Int16 
eAlign,
         rWrt.IncIndentLevel(); // indent content of <THEAD>/<TDATA>
     }
 
+    sal_uInt16 nSkipRows = 0;
     for( SwWriteTableRows::size_type nRow = 0; nRow < m_aRows.size(); ++nRow )
     {
         const SwWriteTableRow *pRow2 = m_aRows[nRow].get();
 
-        OutTableCells( rWrt, pRow2->GetCells(), pRow2->GetBackground() );
+        if (nSkipRows == 0)
+        {
+            OutTableCells(rWrt, pRow2->GetCells(), pRow2->GetBackground(), 
nSkipRows);
+        }
+        else
+        {
+            --nSkipRows;
+        }
         if( !m_nCellSpacing && nRow < m_aRows.size()-1 && pRow2->bBottomBorder 
&&
             pRow2->nBottomBorder > DEF_LINE_WIDTH_1 )
         {

Reply via email to