tdf#143384 DOCX import: fix SAXException at header with table

Regression from commit d656191ec308d4280b93c7169372e543a255d108
"tdf#119952 DOCX import: fix negative page margins".

Add SwXHeadFootText::CreateTextCursor(bool bIgnoreTables = false)
(modeled after SwXBodyText::CreateTextCursor) to create text cursor
for copying the header/footer also started with table during
fly frame creation in convertoToTextFrame().

Note: add hidden property PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF
to use the new feature in domainmapper (followed
commit af4e5ee0f93c1ff442d08caed5c875f2b2c1fd43
"tdf#97128 DOCX import: fix frame direction").

Co-authored-by: Tibor Nagy (NISZ)

Change-Id: I96e2cf2dddcecd146c53c12d7fdb44fc4d90fa0d
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/119549
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/inc/unotextbodyhf.hxx b/sw/inc/unotextbodyhf.hxx
index fbccc98..99887c6 100644
--- a/sw/inc/unotextbodyhf.hxx
+++ b/sw/inc/unotextbodyhf.hxx
@@ -115,6 +115,8 @@ public:
    static css::uno::Reference< css::text::XText >
        CreateXHeadFootText(SwFrameFormat & rHeadFootFormat, const bool bIsHeader);

    css::uno::Reference<css::text::XTextCursor> CreateTextCursor(const bool bIgnoreTables = false);

    // XInterface
    virtual css::uno::Any SAL_CALL queryInterface(
            const css::uno::Type& rType) override;
diff --git a/sw/inc/unotextrange.hxx b/sw/inc/unotextrange.hxx
index f3ed06f..4d8ed2d 100644
--- a/sw/inc/unotextrange.hxx
+++ b/sw/inc/unotextrange.hxx
@@ -57,7 +57,8 @@ namespace sw {

    enum class TextRangeMode {
        RequireTextNode,
        AllowNonTextNode
        AllowNonTextNode,
        AllowTableNode
    };

    void DeepCopyPaM(SwPaM const & rSource, SwPaM & rTarget);
diff --git a/sw/qa/extras/ooxmlexport/data/tdf143384_tableInFoot_negativeMargins.docx b/sw/qa/extras/ooxmlexport/data/tdf143384_tableInFoot_negativeMargins.docx
new file mode 100644
index 0000000..918c102
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf143384_tableInFoot_negativeMargins.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
index de3eea4..08e0cbd 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
@@ -1618,6 +1618,13 @@ DECLARE_OOXMLEXPORT_TEST(testTdf119952_negativeMargins, "tdf119952_negativeMargi
    CPPUNIT_ASSERT_EQUAL(OUString("  aaaa   bbbb    cccc     dddd      eeee"), parseDump("/root/page[3]/header/txt/anchored/fly"));
}

DECLARE_OOXMLEXPORT_TEST(testTdf143384_tableInFoot_negativeMargins, "tdf143384_tableInFoot_negativeMargins.docx")
{
    // There should be no crash during loading of the document
    // so, let's check just how much pages we have
    CPPUNIT_ASSERT_EQUAL(1, getPages());
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/unocore/unoframe.cxx b/sw/source/core/unocore/unoframe.cxx
index 74d366d..8b925067 100644
--- a/sw/source/core/unocore/unoframe.cxx
+++ b/sw/source/core/unocore/unoframe.cxx
@@ -1418,7 +1418,13 @@ void SwXFrame::setPropertyValue(const OUString& rPropertyName, const ::uno::Any&
    const ::SfxItemPropertyMapEntry* pEntry = m_pPropSet->getPropertyMap().getByName(rPropertyName);

    if (!pEntry)
        throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) );
    {
        // Hack to skip the dummy CursorNotIgnoreTables property
        if (rPropertyName == "CursorNotIgnoreTables")
            return;
        else
            throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast <cppu::OWeakObject*> (this));
    }

    const sal_uInt8 nMemberId(pEntry->nMemberId);
    uno::Any aValue(_rValue);
diff --git a/sw/source/core/unocore/unoobj2.cxx b/sw/source/core/unocore/unoobj2.cxx
index 2103220..e994503 100644
--- a/sw/source/core/unocore/unoobj2.cxx
+++ b/sw/source/core/unocore/unoobj2.cxx
@@ -1116,6 +1116,7 @@ bool XTextRangeToSwPaM( SwUnoInternalPaM & rToFill,
    SwXTextPortion* pPortion = nullptr;
    SwXText* pText = nullptr;
    SwXParagraph* pPara = nullptr;
    SwXHeadFootText* pHeadText = nullptr;
    if(xRangeTunnel.is())
    {
        pRange  = ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel);
@@ -1125,12 +1126,26 @@ bool XTextRangeToSwPaM( SwUnoInternalPaM & rToFill,
            ::sw::UnoTunnelGetImplementation<SwXTextPortion>(xRangeTunnel);
        pText   = ::sw::UnoTunnelGetImplementation<SwXText>(xRangeTunnel);
        pPara   = ::sw::UnoTunnelGetImplementation<SwXParagraph>(xRangeTunnel);
        if (eMode == TextRangeMode::AllowTableNode)
            pHeadText = dynamic_cast<SwXHeadFootText*>(pText);
    }

    // if it's a text then create a temporary cursor there and re-use
    // the pCursor variable
    // #i108489#: Reference in outside scope to keep cursor alive
    uno::Reference< text::XTextCursor > xTextCursor;
    if (pHeadText)
    {
        // if it is a header / footer text, and eMode == TextRangeMode::AllowTableNode
        // then set the cursor to the beginning of the text
        // if it is started with a table then set into the table
        xTextCursor.set(pHeadText->CreateTextCursor(true));
        xTextCursor->gotoEnd(true);
        pCursor =
            comphelper::getUnoTunnelImplementation<OTextCursorHelper>(xTextCursor);
        pCursor->GetPaM()->Normalize();
    }
    else
    if (pText)
    {
        xTextCursor.set( pText->CreateCursor() );
diff --git a/sw/source/core/unocore/unotext.cxx b/sw/source/core/unocore/unotext.cxx
index 7e0c73d..fa779d9 100644
--- a/sw/source/core/unocore/unotext.cxx
+++ b/sw/source/core/unocore/unotext.cxx
@@ -1537,14 +1537,31 @@ SwXText::convertToTextFrame(
    {
        throw  uno::RuntimeException();
    }
    // tdf#143384 recognize dummy property, that was set to make createTextCursor
    // to not ignore tables.
    // It is enough to use this hack only for the range start,
    // because as far as I know, the range cannot end with table when this property is set.
    ::sw::TextRangeMode eMode = ::sw::TextRangeMode::RequireTextNode;
    for (const auto& rCellProperty : rFrameProperties)
    {
        if (rCellProperty.Name == "CursorNotIgnoreTables")
        {
            bool bAllowNonTextNode = false;
            rCellProperty.Value >>= bAllowNonTextNode;
            if (bAllowNonTextNode)
                eMode = ::sw::TextRangeMode::AllowTableNode;
            break;
        }
    }
    uno::Reference< text::XTextContent > xRet;
    std::optional<SwUnoInternalPaM> pTempStartPam(*GetDoc());
    std::optional<SwUnoInternalPaM> pEndPam(*GetDoc());
    if (!::sw::XTextRangeToSwPaM(*pTempStartPam, xStart) ||
        !::sw::XTextRangeToSwPaM(*pEndPam, xEnd))
    if (!::sw::XTextRangeToSwPaM(*pTempStartPam, xStart, eMode)
        || !::sw::XTextRangeToSwPaM(*pEndPam, xEnd))
    {
        throw lang::IllegalArgumentException();
    }

    auto pStartPam(GetDoc()->CreateUnoCursor(*pTempStartPam->GetPoint()));
    if (pTempStartPam->HasMark())
    {
@@ -2613,8 +2630,7 @@ uno::Any SAL_CALL SwXHeadFootText::queryInterface(const uno::Type& rType)
        : ret;
}

uno::Reference<text::XTextCursor> SAL_CALL
SwXHeadFootText::createTextCursor()
uno::Reference<text::XTextCursor> SwXHeadFootText::CreateTextCursor(const bool bIgnoreTables)
{
    SolarMutexGuard aGuard;

@@ -2632,18 +2648,22 @@ SwXHeadFootText::createTextCursor()
    // after the table - otherwise the cursor would be in the body text!
    SwStartNode const*const pOwnStartNode = rNode.FindSttNodeByType(
            (m_pImpl->m_bIsHeader) ? SwHeaderStartNode : SwFooterStartNode);
    // is there a table here?
    SwTableNode* pTableNode = rUnoCursor.GetNode().FindTableNode();
    SwContentNode* pCont = nullptr;
    while (pTableNode)

    if (!bIgnoreTables)
    {
        rUnoCursor.GetPoint()->nNode = *pTableNode->EndOfSectionNode();
        pCont = GetDoc()->GetNodes().GoNext(&rUnoCursor.GetPoint()->nNode);
        pTableNode = pCont->FindTableNode();
    }
    if (pCont)
    {
        rUnoCursor.GetPoint()->nContent.Assign(pCont, 0);
        // is there a table here?
        SwTableNode* pTableNode = rUnoCursor.GetNode().FindTableNode();
        SwContentNode* pCont = nullptr;
        while (pTableNode)
        {
            rUnoCursor.GetPoint()->nNode = *pTableNode->EndOfSectionNode();
            pCont = GetDoc()->GetNodes().GoNext(&rUnoCursor.GetPoint()->nNode);
            pTableNode = pCont->FindTableNode();
        }
        if (pCont)
        {
            rUnoCursor.GetPoint()->nContent.Assign(pCont, 0);
        }
    }
    SwStartNode const*const pNewStartNode = rUnoCursor.GetNode().FindSttNodeByType(
            (m_pImpl->m_bIsHeader) ? SwHeaderStartNode : SwFooterStartNode);
@@ -2656,6 +2676,12 @@ SwXHeadFootText::createTextCursor()
    return static_cast<text::XWordCursor*>(pXCursor.get());
}

uno::Reference<text::XTextCursor> SAL_CALL
SwXHeadFootText::createTextCursor()
{
    return CreateTextCursor(false);
}

uno::Reference<text::XTextCursor> SAL_CALL SwXHeadFootText::createTextCursorByRange(
    const uno::Reference<text::XTextRange>& xTextPosition)
{
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 8fffee9..c6c423e 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -2742,6 +2742,12 @@ void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, b
            aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_OPAQUE), false));
            aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), text::SizeType::MIN));
            aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN));
            // tdf#143384 If the header/footer started with a table, convertToTextFrame could not
            // convert the table, because it used createTextCursor() -which ignore tables-
            // to set the conversion range.
            // This dummy property is set to make convertToTextFrame to use an other CreateTextCursor
            // method that can be parameterized to not ignore tables.
            aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF), true));

            fillEmptyFrameProperties(aFrameProperties, false);

@@ -2751,7 +2757,7 @@ void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, b

            uno::Reference<text::XTextAppendAndConvert> xBodyText(
                xRangeStart->getText(), uno::UNO_QUERY);
            xBodyText->convertToTextFrame(xRangeStart, xRangeEnd,
            xBodyText->convertToTextFrame(xTextAppend, xRangeEnd,
                comphelper::containerToSequence(aFrameProperties));
        }
        m_aHeaderFooterTextAppendStack.pop();
diff --git a/writerfilter/source/dmapper/PropertyIds.cxx b/writerfilter/source/dmapper/PropertyIds.cxx
index c3e7cba..00d5b79 100644
--- a/writerfilter/source/dmapper/PropertyIds.cxx
+++ b/writerfilter/source/dmapper/PropertyIds.cxx
@@ -364,6 +364,7 @@ OUString getPropertyName( PropertyIds eId )
        case PROP_RTL_GUTTER:
            sName = "RtlGutter";
            break;
        case PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF: sName = "CursorNotIgnoreTables"; break;
    }
    assert(sName.getLength()>0);
    return sName;
diff --git a/writerfilter/source/dmapper/PropertyIds.hxx b/writerfilter/source/dmapper/PropertyIds.hxx
index f1bef16..8e23533 100644
--- a/writerfilter/source/dmapper/PropertyIds.hxx
+++ b/writerfilter/source/dmapper/PropertyIds.hxx
@@ -361,6 +361,7 @@ enum PropertyIds
        ,PROP_CELL_FORMULA_CONVERTED
        ,PROP_GUTTER_MARGIN
        ,PROP_RTL_GUTTER
        ,PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF
    };

//Returns the UNO string equivalent to eId.