tdf#73499 DOCX import: fix grouped linked textbox

Only ungrouped text boxes were imported correctly.
Grouped textboxes lost their linking, resulting
broken layout. Now the linking is fixed for DrawingML.

Note: in old MSO versions, linking needed grouping.
To import these DOC documents correctly, convert them
to DOCX/DrawingML in MSO before opening them in Writer.

Change-Id: Ib5a8744d783a9c95c42447d204e17891b3aea7bd
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/130950
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/qa/extras/ooxmlexport/data/tdf73499.docx b/sw/qa/extras/ooxmlexport/data/tdf73499.docx
new file mode 100644
index 0000000..605c01e
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf73499.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
index 486dc6c..53e80df 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -346,6 +346,34 @@ DECLARE_OOXMLEXPORT_TEST(testTdf148111, "tdf148111.docx")
    CPPUNIT_ASSERT(!xFields->hasMoreElements());
}

DECLARE_OOXMLEXPORT_TEST(TestTdf73499, "tdf73499.docx")
{
    // Ensure, the bugdoc is opened
    CPPUNIT_ASSERT(mxComponent);
    // Get the groupshape
    uno::Reference<drawing::XShapes> xGroup(getShape(1), uno::UNO_QUERY_THROW);

    // Get the textboxes of the groupshape
    uno::Reference<text::XText> xTextBox1(xGroup->getByIndex(0), uno::UNO_QUERY_THROW);
    uno::Reference<text::XText> xTextBox2(xGroup->getByIndex(1), uno::UNO_QUERY_THROW);

    // Get the properties of the textboxes
    uno::Reference<beans::XPropertySet> xTextBox1Properties(xTextBox1, uno::UNO_QUERY_THROW);
    uno::Reference<beans::XPropertySet> xTextBox2Properties(xTextBox2, uno::UNO_QUERY_THROW);

    // Get the name of the textboxes
    uno::Reference<container::XNamed> xTextBox1Name(xTextBox1, uno::UNO_QUERY_THROW);
    uno::Reference<container::XNamed> xTextBox2Name(xTextBox2, uno::UNO_QUERY_THROW);

    // Check for the links, before the fix that were missing
    CPPUNIT_ASSERT_EQUAL_MESSAGE(
        "Link name missing!", xTextBox2Name->getName(),
        xTextBox1Properties->getPropertyValue("ChainNextName").get<OUString>());
    CPPUNIT_ASSERT_EQUAL_MESSAGE(
        "Link name missing!", xTextBox1Name->getName(),
        xTextBox2Properties->getPropertyValue("ChainPrevName").get<OUString>());
}

DECLARE_OOXMLEXPORT_TEST(testTdf81507, "tdf81507.docx")
{
    xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index a3d6a4f..f8700fa 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -4634,20 +4634,103 @@ void DomainMapper_Impl::PopTextBoxContent()

void DomainMapper_Impl::AttachTextBoxContentToShape(css::uno::Reference<css::drawing::XShape> xShape)
{
    // Without textbox or shape pointless to continue
    if (m_xPendingTextBoxFrames.empty() || !xShape)
        return;

    uno::Reference< drawing::XShapes >xGroup(xShape, uno::UNO_QUERY);
    uno::Reference< beans::XPropertySet >xProps(xShape, uno::UNO_QUERY);

    // If this is a group go inside
    if (xGroup)
        for (sal_Int32 i = 0; i < xGroup->getCount(); ++i)
            AttachTextBoxContentToShape(uno::Reference<drawing::XShape>(xGroup->getByIndex(i),uno::UNO_QUERY_THROW));
            AttachTextBoxContentToShape(
                uno::Reference<drawing::XShape>(xGroup->getByIndex(i), uno::UNO_QUERY));

    if (xProps->getPropertyValue("TextBox").get<bool>())
    // if this shape has to be a textbox, attach the frame
    if (!xProps->getPropertyValue("TextBox").get<bool>())
        return;

    // if this is a textbox there must be a waiting frame
    auto xTextBox = m_xPendingTextBoxFrames.front();
    if (!xTextBox)
        return;

    // Pop the pending frames
    m_xPendingTextBoxFrames.pop();

    // Attach the textbox to the shape
    try
    {
        xProps->setPropertyValue("TextBoxContent", uno::Any(m_xPendingTextBoxFrames.front()));
        m_xPendingTextBoxFrames.pop();
        xProps->setPropertyValue("TextBoxContent", uno::Any(xTextBox));
    }
    catch (...)
    {
        SAL_WARN("writerfilter.dmapper", "Exception while trying to attach textboxes!");
        return;
    }

    // If attaching is successful, then do the linking
    try
    {
        // Get the name of the textbox
        OUString sTextBoxName;
        uno::Reference<container::XNamed> xName(xTextBox, uno::UNO_QUERY);
        if (xName && !xName->getName().isEmpty())
            sTextBoxName = xName->getName();

        // Try to get the grabbag
        uno::Sequence<beans::PropertyValue> aOldGrabBagSeq;
        if (xProps->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
            xProps->getPropertyValue("InteropGrabBag") >>= aOldGrabBagSeq;

        // If the grabbag successfully get...
        if (!aOldGrabBagSeq.hasElements())
            return;

        // Check for the existing linking information
        bool bSuccess = false;
        beans::PropertyValues aNewGrabBagSeq;
        const auto& aHasLink = lcl_getGrabBagValue(aOldGrabBagSeq, "TxbxHasLink");

        // If there must be a link, do it
        if (aHasLink.hasValue() && aHasLink.get<bool>())
        {
            auto aLinkProp = comphelper::makePropertyValue("LinkChainName", sTextBoxName);
            for (sal_uInt32 i = 0; i < aOldGrabBagSeq.size(); ++i)
            {
                aNewGrabBagSeq.realloc(i + 1);
                // If this is the link name replace it
                if (!aOldGrabBagSeq[i].Name.isEmpty() && !aLinkProp.Name.isEmpty()
                    && (aOldGrabBagSeq[i].Name == aLinkProp.Name))
                {
                    aNewGrabBagSeq.getArray()[i] = aLinkProp;
                    bSuccess = true;
                }
                // else copy
                else
                    aNewGrabBagSeq.getArray()[i] = aOldGrabBagSeq[i];
            }

            // If there was no replacement, append the linking data
            if (!bSuccess)
            {
                aNewGrabBagSeq.realloc(aNewGrabBagSeq.size() + 1);
                aNewGrabBagSeq.getArray()[aNewGrabBagSeq.size() - 1] = aLinkProp;
                bSuccess = true;
            }
        }

        // If the linking changed the grabbag, apply the modifications
        if (aNewGrabBagSeq.hasElements() && bSuccess)
        {
            xProps->setPropertyValue("InteropGrabBag", uno::Any(aNewGrabBagSeq));
            m_vTextFramesForChaining.push_back(xShape);
        }
    }
    catch (...)
    {
        SAL_WARN("writerfilter.dmapper", "Exception while trying to link textboxes!");
    }
}