tdf#129475 DOCX: fix gridAfter with shape-only cells

and in last row of tables, i.e. regressions caused
by the following commit.

This reverts commit b2c6d2d961a6113d0f111fab45ae12a40d389a23
(fdo#38414 tdf#44986: DOCX table import: handle gridBefore/After),
except some unit testing.

Change-Id: Icb2d65b7a0766cf8dd00511cde500af3f94d2a94
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/86125
Reviewed-by: László Németh <nemeth@numbertext.org>
Tested-by: László Németh <nemeth@numbertext.org>
diff --git a/writerfilter/source/dmapper/DomainMapperTableManager.cxx b/writerfilter/source/dmapper/DomainMapperTableManager.cxx
index e288a1e..b8fd339 100644
--- a/writerfilter/source/dmapper/DomainMapperTableManager.cxx
+++ b/writerfilter/source/dmapper/DomainMapperTableManager.cxx
@@ -48,7 +48,7 @@
    m_nCell(),
    m_nGridSpan(1),
    m_aGridBefore(),
    m_aGridAfter(),
    m_nGridAfter(0),
    m_nHeaderRepeat(0),
    m_nTableWidth(0),
    m_bIsInShape(false),
@@ -357,7 +357,7 @@
                m_aGridBefore.back( ) = nIntValue;
                break;
            case NS_ooxml::LN_CT_TrPrBase_gridAfter:
                m_aGridAfter.back() = nIntValue;
                m_nGridAfter = nIntValue;
                break;
            case NS_ooxml::LN_CT_TblPrBase_tblCaption:
                // To-Do: Not yet preserved
@@ -399,11 +399,6 @@
    return m_aGridBefore.back( );
}

sal_uInt32 DomainMapperTableManager::getCurrentGridAfter( )
{
    return m_aGridAfter.back( );
}

DomainMapperTableManager::IntVectorPtr const & DomainMapperTableManager::getCurrentSpans( )
{
    return m_aGridSpans.back( );
@@ -462,7 +457,6 @@
    m_aTmpTableProperties.push_back( pTmpProperties );
    m_nCell.push_back( 0 );
    m_aGridBefore.push_back( 0 );
    m_aGridAfter.push_back( 0 );
    m_nTableWidth = 0;
    m_nLayoutType = 0;

@@ -493,7 +487,6 @@

    m_nCell.pop_back( );
    m_aGridBefore.pop_back( );
    m_aGridAfter.pop_back( );
    m_nTableWidth = 0;
    m_nLayoutType = 0;

@@ -550,7 +543,6 @@
        IntVectorPtr pTmpCellWidths = m_aCellWidths.back();
        sal_uInt32 nTmpCell = m_nCell.back();
        sal_uInt32 nTmpGridBefore = m_aGridBefore.back();
        sal_uInt32 nTmpGridAfter = m_aGridAfter.back();

        // endLevel and startLevel are taking care of the non finished row
        // to carry it over to the next table
@@ -564,13 +556,11 @@
        m_aCellWidths.pop_back();
        m_nCell.pop_back();
        m_aGridBefore.pop_back();
        m_aGridAfter.pop_back();
        m_aTableGrid.push_back(pTmpTableGrid);
        m_aGridSpans.push_back(pTmpGridSpans);
        m_aCellWidths.push_back(pTmpCellWidths);
        m_nCell.push_back(nTmpCell);
        m_aGridBefore.push_back(nTmpGridBefore);
        m_aGridAfter.push_back(nTmpGridAfter);
    }

    // Push the tmp position now that we compared it
@@ -617,10 +607,10 @@
        //fill missing gridBefore elements with '1'
        pCurrentSpans->insert( pCurrentSpans->begin( ), m_aGridBefore.back(), 1 );
    }
    if( pCurrentSpans->size() < m_aGridBefore.back() + m_nCell.back( ) + m_aGridAfter.back() )
    if( pCurrentSpans->size() < m_aGridBefore.back() + m_nCell.back( ))
    {
        //fill missing elements with '1'
        pCurrentSpans->insert( pCurrentSpans->end( ), m_aGridBefore.back() + m_nCell.back( ) + m_aGridAfter.back() - pCurrentSpans->size(), 1 );
        pCurrentSpans->insert( pCurrentSpans->end( ), m_aGridBefore.back() + m_nCell.back( ) - pCurrentSpans->size(), 1 );
    }

#ifdef DBG_UTIL
@@ -647,7 +637,7 @@
    for (int i : (*pTableGrid))
        nFullWidthRelative = o3tl::saturating_add(nFullWidthRelative, i);

    if( pTableGrid->size() == nGrids && m_nCell.back( ) > 0 )
    if( pTableGrid->size() == ( nGrids + m_nGridAfter ) && m_nCell.back( ) > 0 )
    {
        /*
         * If table width property set earlier is smaller than the current table width,
@@ -677,12 +667,12 @@
                }
            }
        }
        uno::Sequence< text::TableColumnSeparator > aSeparators( m_aGridBefore.back() + m_nCell.back( ) + m_aGridAfter.back() - 1 );
        uno::Sequence< text::TableColumnSeparator > aSeparators( m_aGridBefore.back() + m_nCell.back( ) - 1 );
        text::TableColumnSeparator* pSeparators = aSeparators.getArray();
        double nLastRelPos = 0.0;
        sal_uInt32 nBorderGridIndex = 0;

        size_t nWidthsBound = m_aGridBefore.back() + m_nCell.back() + m_aGridAfter.back() - 1;
        size_t nWidthsBound = m_aGridBefore.back() + m_nCell.back() - 1;
        if (nWidthsBound)
        {
            if (nFullWidthRelative == 0)
@@ -715,7 +705,7 @@
    }
    else if ( !pCellWidths->empty() &&
               ( m_nLayoutType == NS_ooxml::LN_Value_doc_ST_TblLayout_fixed
                 || pCellWidths->size() == nGrids )
                 || pCellWidths->size() == ( nGrids + m_nGridAfter ) )
             )
    {
        // If we're here, then the number of cells does not equal to the amount
@@ -770,11 +760,11 @@
    ++m_nRow;
    m_nCell.back( ) = 0;
    m_aGridBefore.back( ) = 0;
    m_aGridAfter.back( ) = 0;
    getCurrentGrid()->clear();
    pCurrentSpans->clear();
    pCellWidths->clear();

    m_nGridAfter = 0;
    m_bTableSizeTypeInserted = false;

#ifdef DBG_UTIL
diff --git a/writerfilter/source/dmapper/DomainMapperTableManager.hxx b/writerfilter/source/dmapper/DomainMapperTableManager.hxx
index 6f248d1..1020d67 100644
--- a/writerfilter/source/dmapper/DomainMapperTableManager.hxx
+++ b/writerfilter/source/dmapper/DomainMapperTableManager.hxx
@@ -43,7 +43,7 @@
    ::std::vector< sal_uInt32 > m_nCell;
    sal_uInt32      m_nGridSpan;
    ::std::vector< sal_uInt32 > m_aGridBefore; ///< number of grid columns in the parent table's table grid which must be skipped before the contents of this table row are added to the parent table
    ::std::vector< sal_uInt32 > m_aGridAfter; ///< number of grid columns in the parent table's table grid which shall be left after the last cell in the table row
    sal_uInt32      m_nGridAfter; ///< number of grid columns in the parent table's table grid which shall be left after the last cell in the table row
    sal_Int32       m_nHeaderRepeat; //counter of repeated headers - if == -1 then the repeating stops
    sal_Int32       m_nTableWidth; //might be set directly or has to be calculated from the column positions
    /// Are we in a shape (text append stack is not empty) or in the body document?
@@ -93,7 +93,6 @@
    IntVectorPtr const & getCurrentSpans( );
    IntVectorPtr const & getCurrentCellWidths( );
    sal_uInt32 getCurrentGridBefore( );
    sal_uInt32 getCurrentGridAfter( );

    /// Turn the attributes collected so far in m_aTableLook into a property and clear the container.
    void finishTableLook();
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 0ca7ffa..7842acb 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -283,7 +283,6 @@
        m_bIsFirstParaInSection( true ),
        m_bIsFirstParaInSectionAfterRedline( true ),
        m_bDummyParaAddedForTableInSection( false ),
        m_bDummyCharAddedForTableRowGridAfter( false ),
        m_bTextFrameInserted(false),
        m_bIsPreviousParagraphFramed( false ),
        m_bIsLastParaInSection( false ),
@@ -1642,17 +1641,6 @@
                        }
                    }

                    // remove dummy character added for gridAfter table cells
                    if (GetIsDummyCharAddedForTableRowGridAfter())
                    {
                        SetIsDummyCharAddedForTableRowGridAfter(false);
                        uno::Reference<text::XParagraphCursor> xParaCursor(
                            xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW);
                        xParaCursor->gotoStartOfParagraph( false);
                        xParaCursor->goRight(1, true);
                        xParaCursor->setString("");
                    }

                    xTextRange = xTextAppend->finishParagraph( comphelper::containerToSequence(aProperties) );
                    m_xPreviousParagraph.set(xTextRange, uno::UNO_QUERY);

diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index fde0749..b050e95 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -549,7 +549,6 @@
    bool                            m_bIsFirstParaInSectionAfterRedline;
    bool                            m_bIsFirstParaInShape = false;
    bool                            m_bDummyParaAddedForTableInSection;
    bool                            m_bDummyCharAddedForTableRowGridAfter;
    bool                            m_bTextFrameInserted;
    bool                            m_bIsPreviousParagraphFramed;
    bool                            m_bIsLastParaInSection;
@@ -648,8 +647,6 @@
    bool GetIsFirstParagraphInShape() const { return m_bIsFirstParaInShape; }
    void SetIsDummyParaAddedForTableInSection( bool bIsAdded );
    bool GetIsDummyParaAddedForTableInSection() const { return m_bDummyParaAddedForTableInSection;}
    void SetIsDummyCharAddedForTableRowGridAfter( bool bIsAdded ) { m_bDummyCharAddedForTableRowGridAfter = bIsAdded; }
    bool GetIsDummyCharAddedForTableRowGridAfter() const { return m_bDummyCharAddedForTableRowGridAfter;}

    /// Track if a textframe has been inserted into this section
    void SetIsTextFrameInserted( bool bIsInserted );
diff --git a/writerfilter/source/dmapper/TableManager.cxx b/writerfilter/source/dmapper/TableManager.cxx
index facae6e..b9384b9e 100644
--- a/writerfilter/source/dmapper/TableManager.cxx
+++ b/writerfilter/source/dmapper/TableManager.cxx
@@ -408,43 +408,23 @@
    TagLogger::getInstance().element("tablemanager.endRow");
#endif
    TableData::Pointer_t pTableData = mTableDataStack.top();
    sal_uInt32 nGridBefore = mpTableDataHandler->getDomainMapperImpl().getTableManager().getCurrentGridBefore();
    sal_uInt32 nGridAfter = mpTableDataHandler->getDomainMapperImpl().getTableManager().getCurrentGridAfter();

    // Add borderless w:gridBefore and w:gridAfter cell(s) to the row
    if (pTableData && (nGridBefore > 0 || nGridAfter > 0))
    // Add borderless w:gridBefore cell(s) to the row
    if (pTableData)
    {
        css::table::BorderLine2 aBorderLine;
        aBorderLine.Color = 0;
        aBorderLine.InnerLineWidth = 0;
        aBorderLine.OuterLineWidth = 0;
        TablePropertyMapPtr pCellProperties(new TablePropertyMap);
        pCellProperties->Insert(PROP_TOP_BORDER, css::uno::makeAny(aBorderLine));
        pCellProperties->Insert(PROP_LEFT_BORDER, css::uno::makeAny(aBorderLine));
        pCellProperties->Insert(PROP_BOTTOM_BORDER, css::uno::makeAny(aBorderLine));
        pCellProperties->Insert(PROP_RIGHT_BORDER, css::uno::makeAny(aBorderLine));
        if (nGridBefore > 0)
        sal_uInt32 nGridBefore = mpTableDataHandler->getDomainMapperImpl().getTableManager().getCurrentGridBefore();
        for (unsigned int i = 0; i < nGridBefore; ++i)
        {
            const css::uno::Reference<css::text::XTextRange>& rFirstCellStartHandle = pTableData->getCurrentRow()->getCellStart(0);
            for (unsigned int i = 0; i < nGridBefore; ++i)
                pTableData->getCurrentRow()->addCell(rFirstCellStartHandle, pCellProperties, /*bAddBefore=*/true);
        }
        if (nGridAfter > 0)
        {
            const css::uno::Reference<css::text::XTextRange>& rEndCellEndHandle = pTableData->getCurrentRow()->getCellEnd(pTableData->getCurrentRow()->getCellCount()-1);
            auto xCursor(rEndCellEndHandle->getText()->createTextCursorByRange(rEndCellEndHandle));
            if (!xCursor->getString().isEmpty())
            {
                // add a dummy character to get a right handle for gridAfter cells after the last non-empty cell, removing that later
                xCursor->gotoEnd(false);
                xCursor->setString("x");
                mpTableDataHandler->getDomainMapperImpl().SetIsDummyCharAddedForTableRowGridAfter(true);
            }
            for (unsigned int i = 0; i < nGridAfter; ++i)
            {
                pTableData->getCurrentRow()->addCell(xCursor, pCellProperties, /*bAddBefore=*/false);
                pTableData->getCurrentRow()->endCell(xCursor);
            }
            css::table::BorderLine2 aBorderLine;
            aBorderLine.Color = 0;
            aBorderLine.InnerLineWidth = 0;
            aBorderLine.OuterLineWidth = 0;
            TablePropertyMapPtr pCellProperties(new TablePropertyMap);
            pCellProperties->Insert(PROP_TOP_BORDER, css::uno::makeAny(aBorderLine));
            pCellProperties->Insert(PROP_LEFT_BORDER, css::uno::makeAny(aBorderLine));
            pCellProperties->Insert(PROP_BOTTOM_BORDER, css::uno::makeAny(aBorderLine));
            pCellProperties->Insert(PROP_RIGHT_BORDER, css::uno::makeAny(aBorderLine));
            pTableData->getCurrentRow()->addCell(pTableData->getCurrentRow()->getCellStart(0), pCellProperties, /*bAddBefore=*/true);
        }
    }

diff --git a/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx b/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx
index 31fb077..6f7839d0 100644
--- a/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx
+++ b/writerfilter/source/ooxml/OOXMLFastContextHandler.cxx
@@ -1397,6 +1397,14 @@

void OOXMLFastContextHandlerTextTableRow::endRow()
{
    if (mpGridAfter)
    {
        // Grid after is the same as grid before, the empty cells are just
        // inserted after the real ones, not before.
        handleGridBefore(mpGridAfter);
        mpGridAfter = nullptr;
    }

    startParagraphGroup();

    if (isForwardEvents())
@@ -1427,6 +1435,81 @@
    endParagraphGroup();
}

void OOXMLFastContextHandlerTextTableRow::handleGridAfter(const OOXMLValue::Pointer_t& rValue)
{
    if (OOXMLFastContextHandler* pTableRowProperties = getParent())
    {
        if (OOXMLFastContextHandler* pTableRow = pTableRowProperties->getParent())
            // Save the value into the table row context, so it can be handled
            // right before the end of the row.
            pTableRow->setGridAfter(rValue);
    }
}

namespace {
OOXMLValue::Pointer_t fakeNoBorder()
{
    OOXMLPropertySet::Pointer_t pProps( new OOXMLPropertySet );
    OOXMLValue::Pointer_t pVal = OOXMLIntegerValue::Create(0);
    pProps->add(NS_ooxml::LN_CT_Border_val, pVal, OOXMLProperty::ATTRIBUTE);
    OOXMLValue::Pointer_t pValue( new OOXMLPropertySetValue( pProps ));
    return pValue;
}
}

// Handle w:gridBefore here by faking necessary input that'll fake cells. I'm apparently
// not insane enough to find out how to add cells in dmapper.
void OOXMLFastContextHandlerTextTableRow::handleGridBefore( const OOXMLValue::Pointer_t& val )
{
    // start removing: disable for w:gridBefore
    if (!mpGridAfter)
        return;

    int count = val->getInt();
    for( int i = 0;
         i < count;
         ++i )
    {
        endOfParagraph();

        if (isForwardEvents())
        {
            // This whole part is OOXMLFastContextHandlerTextTableCell::endCell() .
            OOXMLPropertySet::Pointer_t pProps(new OOXMLPropertySet);
            {
                OOXMLValue::Pointer_t pVal = OOXMLIntegerValue::Create(mnTableDepth);
                pProps->add(NS_ooxml::LN_tblDepth, pVal, OOXMLProperty::SPRM);
            }
            {
                OOXMLValue::Pointer_t pVal = OOXMLIntegerValue::Create(1);
                pProps->add(NS_ooxml::LN_inTbl, pVal, OOXMLProperty::SPRM);
            }
            {
                OOXMLValue::Pointer_t pVal = OOXMLBooleanValue::Create(mnTableDepth > 0);
                pProps->add(NS_ooxml::LN_tblCell, pVal, OOXMLProperty::SPRM);
            }

            mpStream->props(pProps.get());

            // fake <w:tcBorders> with no border
            OOXMLPropertySet::Pointer_t pCellProps( new OOXMLPropertySet );
            {
                OOXMLPropertySet::Pointer_t pBorderProps( new OOXMLPropertySet );
                static Id borders[] = { NS_ooxml::LN_CT_TcBorders_top, NS_ooxml::LN_CT_TcBorders_bottom,
                    NS_ooxml::LN_CT_TcBorders_start, NS_ooxml::LN_CT_TcBorders_end };
                for(sal_uInt32 border : borders)
                    pBorderProps->add(border, fakeNoBorder(), OOXMLProperty::SPRM);
                OOXMLValue::Pointer_t pValue( new OOXMLPropertySetValue( pBorderProps ));
                pCellProps->add(NS_ooxml::LN_CT_TcPrBase_tcBorders, pValue, OOXMLProperty::SPRM);
                mpParserState->setCellProperties(pCellProps);
            }
        }

        sendCellProperties();
        endParagraphGroup();
    }
}

/*
  class OOXMLFastContextHandlerTextTable
 */
diff --git a/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx b/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx
index 0da8b9c..d5de5a7 100644
--- a/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx
+++ b/writerfilter/source/ooxml/OOXMLFastContextHandler.hxx
@@ -186,6 +186,8 @@
    virtual void setDefaultStringValue();

    void sendPropertyToParent();
    OOXMLFastContextHandler* getParent() const { return mpParent; }
    void setGridAfter(const OOXMLValue::Pointer_t& pGridAfter) { mpGridAfter = pGridAfter; }

protected:
    OOXMLFastContextHandler * mpParent;
@@ -224,6 +226,7 @@
    const css::uno::Reference< css::uno::XComponentContext >& getComponentContext() const { return m_xContext;}

    bool inPositionV;
    OOXMLValue::Pointer_t mpGridAfter;

private:
    void operator =(OOXMLFastContextHandler const &) = delete;
@@ -404,6 +407,8 @@

    static void startRow();
    void endRow();
    void handleGridBefore( const OOXMLValue::Pointer_t& val );
    void handleGridAfter(const OOXMLValue::Pointer_t& rValue);
};

class OOXMLFastContextHandlerTextTable : public OOXMLFastContextHandler
diff --git a/writerfilter/source/ooxml/factoryimpl_ns.py b/writerfilter/source/ooxml/factoryimpl_ns.py
index 5bdf25c..41fa714 100644
--- a/writerfilter/source/ooxml/factoryimpl_ns.py
+++ b/writerfilter/source/ooxml/factoryimpl_ns.py
@@ -440,6 +440,9 @@
    elif actionNode.getAttribute("action") in ("startRow", "endRow"):
        ret.append("    %sif (OOXMLFastContextHandlerTextTableRow* pTextTableRow = dynamic_cast<OOXMLFastContextHandlerTextTableRow*>(pHandler))" % extra_space)
        ret.append("    %s    pTextTableRow->%s();" % (extra_space, actionNode.getAttribute("action")))
    elif actionNode.getAttribute("action") == "handleGridBefore" or actionNode.getAttribute("action") == "handleGridAfter":
        ret.append("    %sif (OOXMLFastContextHandlerTextTableRow* pTextTableRow = dynamic_cast<OOXMLFastContextHandlerTextTableRow*>(pHandler))" % extra_space)
        ret.append("    %s    pTextTableRow->%s();" % (extra_space, actionNode.getAttribute("action")))
    # tdf#111550
    elif actionNode.getAttribute("action") in ("start_P_Tbl"):
        ret.append("    %sif (OOXMLFastContextHandlerTextTable* pTextTable = dynamic_cast<OOXMLFastContextHandlerTextTable*>(pHandler))" % extra_space)
diff --git a/writerfilter/source/ooxml/model.xml b/writerfilter/source/ooxml/model.xml
index 4c712e0..17c8f52 100644
--- a/writerfilter/source/ooxml/model.xml
+++ b/writerfilter/source/ooxml/model.xml
@@ -14445,7 +14445,7 @@
            <ref name="CT_DecimalNumber"/>
          </element>
          <element name="gridAfter">
            <ref name="CT_DecimalNumber"/>
            <ref name="CT_TrPrBaseGridAfter"/>
          </element>
          <element name="wBefore">
            <ref name="CT_TblWidth"/>
@@ -14473,6 +14473,16 @@
          </element>
        </choice>
      </define>
      <define name="CT_TrPrBaseGridBefore">
        <attribute name="val">
          <ref name="ST_DecimalNumber"/>
        </attribute>
      </define>
      <define name="CT_TrPrBaseGridAfter">
        <attribute name="val">
          <ref name="ST_DecimalNumber"/>
        </attribute>
      </define>
      <define name="CT_TrPr">
        <ref name="CT_TrPrBase"/>
        <element name="ins">
@@ -18395,7 +18405,6 @@
      <element name="cnfStyle" tokenid="ooxml:CT_TrPrBase_cnfStyle"/>
      <element name="divId" tokenid="ooxml:CT_TrPrBase_divId"/>
      <element name="gridBefore" tokenid="ooxml:CT_TrPrBase_gridBefore"/>
      <element name="gridAfter" tokenid="ooxml:CT_TrPrBase_gridAfter"/>
      <element name="wBefore" tokenid="ooxml:CT_TrPrBase_wBefore"/>
      <element name="wAfter" tokenid="ooxml:CT_TrPrBase_wAfter"/>
      <element name="cantSplit" tokenid="ooxml:CT_TrPrBase_cantSplit"/>
@@ -18405,6 +18414,12 @@
      <element name="jc" tokenid="ooxml:CT_TrPrBase_jc"/>
      <element name="hidden" tokenid="ooxml:CT_TrPrBase_hidden"/>
    </resource>
    <resource name="CT_TrPrBaseGridBefore" resource="TextTableRow">
      <attribute name="val" tokenid="ooxml:CT_TrPrBase_gridBefore"/>
    </resource>
    <resource name="CT_TrPrBaseGridAfter" resource="TextTableRow">
      <attribute name="val" tokenid="ooxml:CT_TrPrBase_gridAfter" action="handleGridAfter"/>
    </resource>
    <resource name="CT_TrPr" resource="Properties">
      <element name="ins" tokenid="ooxml:CT_TrPr_ins"/>
      <element name="del" tokenid="ooxml:CT_TrPr_del"/>