tdf#154360: use TOC(N) styles in DOCX as the source of ToC tab stop position

Change-Id: Iab8001f8be8a8af437e8898079d55ff57dd6fa3b
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149494
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149548
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
diff --git a/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx b/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx
index 406812a..04d23a4 100644
--- a/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx
+++ b/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx
@@ -1035,39 +1035,46 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf154319)
    CPPUNIT_ASSERT_EQUAL(sal_Int32(11), xLevelFormats->getCount());

    const auto checkPropVal = [](const auto& expected, const css::beans::PropertyValues& entry,
                                 const OUString& name) {
                                 const OUString& name, sal_Int32 level) {
        auto it
            = std::find_if(entry.begin(), entry.end(),
                           [&name](const css::beans::PropertyValue& p) { return p.Name == name; });
        OString msg = "Property: " + name.toUtf8();
        OString msg = "Property: " + name.toUtf8() + ", level: " + OString::number(level);
        CPPUNIT_ASSERT_MESSAGE(msg.getStr(), it != entry.end());
        CPPUNIT_ASSERT_EQUAL_MESSAGE(msg.getStr(), css::uno::Any(expected), it->Value);
    };

    // tdf#154360: check tab stops between the number and the entry text
    constexpr sal_Int32 levelTabStops[]
        = { 776, 1270, 1270, 1270, 1270, 1270, 1270, 1270, 1270, 1270 };

    //start with level 1, 0 is the header level
    for (sal_Int32 nLevel = 1; nLevel < xLevelFormats->getCount(); ++nLevel)
    {
        css::uno::Sequence<css::beans::PropertyValues> aLevel;
        xLevelFormats->getByIndex(nLevel) >>= aLevel;

        CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aLevel.getLength());
        CPPUNIT_ASSERT_EQUAL(sal_Int32(9), aLevel.getLength());

        checkPropVal(OUString("TokenHyperlinkStart"), aLevel[0], "TokenType");
        checkPropVal(OUString("TokenHyperlinkStart"), aLevel[0], "TokenType", nLevel);

        checkPropVal(OUString("TokenEntryNumber"), aLevel[1], "TokenType");
        checkPropVal(OUString("TokenEntryNumber"), aLevel[1], "TokenType", nLevel);

        checkPropVal(OUString("TokenEntryText"), aLevel[2], "TokenType");
        checkPropVal(OUString("TokenTabStop"), aLevel[2], "TokenType", nLevel);
        checkPropVal(levelTabStops[nLevel - 1], aLevel[2], "TabStopPosition", nLevel);

        checkPropVal(OUString("TokenTabStop"), aLevel[3], "TokenType");
        checkPropVal(OUString("TokenEntryText"), aLevel[3], "TokenType", nLevel);

        checkPropVal(OUString("TokenChapterInfo"), aLevel[4], "TokenType");
        checkPropVal(OUString("TokenTabStop"), aLevel[4], "TokenType", nLevel);

        checkPropVal(OUString("TokenText"), aLevel[5], "TokenType");
        checkPropVal(OUString("\""), aLevel[5], "Text");
        checkPropVal(OUString("TokenChapterInfo"), aLevel[5], "TokenType", nLevel);

        checkPropVal(OUString("TokenPageNumber"), aLevel[6], "TokenType");
        checkPropVal(OUString("TokenText"), aLevel[6], "TokenType", nLevel);
        checkPropVal(OUString("\""), aLevel[6], "Text", nLevel);

        checkPropVal(OUString("TokenHyperlinkEnd"), aLevel[7], "TokenType");
        checkPropVal(OUString("TokenPageNumber"), aLevel[7], "TokenType", nLevel);

        checkPropVal(OUString("TokenHyperlinkEnd"), aLevel[8], "TokenType", nLevel);
    }
}

diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 663e47a..ebcd8be 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -6029,35 +6029,34 @@ void DomainMapper_Impl::handleAuthor
}

static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( bool bHyperlinks, const OUString& sChapterNoSeparator,
                                   const uno::Sequence< beans::PropertyValues >& aLevel )
                                   const uno::Sequence< beans::PropertyValues >& aLevel, const uno::Sequence<style::TabStop>& tabs)
{
    //create a copy of the level and add new entries

    std::vector<css::beans::PropertyValues> aNewLevel;
    aNewLevel.reserve(aLevel.getLength() + 4); // at most 4 added items
    aNewLevel.reserve(aLevel.getLength() + 5); // at most 5 added items

    static constexpr OUStringLiteral tokType(u"TokenType");
    static constexpr OUStringLiteral tokHStart(u"TokenHyperlinkStart");
    static constexpr OUStringLiteral tokHEnd(u"TokenHyperlinkEnd");
    static constexpr OUStringLiteral tokPNum(u"TokenPageNumber");
    static constexpr OUStringLiteral tokENum(u"TokenEntryNumber");

    if (bHyperlinks)
        aNewLevel.push_back({ comphelper::makePropertyValue(tokType, tokHStart) });

    for (const auto& item : aLevel)
    {
        if (bHyperlinks
            && std::any_of(item.begin(), item.end(),
                           [](const css::beans::PropertyValue& p) {
                               return p.Name == tokType
                                      && (p.Value == tokHStart || p.Value == tokHEnd);
                           }))
        OUString tokenType;
        if (auto it = std::find_if(item.begin(), item.end(),
                                   [](const auto& p) { return p.Name == tokType; });
            it != item.end())
            it->Value >>= tokenType;

        if (bHyperlinks && (tokenType == tokHStart || tokenType == tokHEnd))
            continue; // We add hyperlink ourselves, so just skip existing hyperlink start / end

        if (!sChapterNoSeparator.isEmpty()
            && std::any_of(item.begin(), item.end(),
                           [](const css::beans::PropertyValue& p)
                           { return p.Name == tokType && p.Value == tokPNum; }))
        if (!sChapterNoSeparator.isEmpty() && tokenType == tokPNum)
        {
            // This is an existing page number token; insert the chapter and separator before it
            aNewLevel.push_back(
@@ -6068,6 +6067,14 @@ static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( bool
        }

        aNewLevel.push_back(item);

        if (tabs.hasElements() && tokenType == tokENum)
        {
            // There is a fixed tab stop position needed in the level after the numbering
            aNewLevel.push_back(
                { comphelper::makePropertyValue(tokType, OUString("TokenTabStop")),
                  comphelper::makePropertyValue("TabStopPosition", tabs[0].Position) });
        }
    }

    if (bHyperlinks)
@@ -6425,22 +6432,39 @@ void DomainMapper_Impl::handleToc
            xTOC->setPropertyValue(getPropertyName(PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), uno::Any( true ));

        }
        if(bHyperlinks  || !sChapterNoSeparator.isEmpty())
        {
            uno::Reference< container::XIndexReplace> xLevelFormats;
            xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats;
            sal_Int32 nLevelCount = xLevelFormats->getCount();
                            //start with level 1, 0 is the header level
            for( sal_Int32 nLevel = 1; nLevel < nLevelCount; ++nLevel)
            {
                uno::Sequence< beans::PropertyValues > aLevel;
                xLevelFormats->getByIndex( nLevel ) >>= aLevel;

                uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
                                                    bHyperlinks, sChapterNoSeparator,
                                                    aLevel );
                xLevelFormats->replaceByIndex( nLevel, uno::Any( aNewLevel ) );
        uno::Reference<container::XNameContainer> xStyles;
        if (uno::Reference<style::XStyleFamiliesSupplier> xStylesSupplier{ GetTextDocument(),
                                                                           uno::UNO_QUERY })
        {
            auto xStyleFamilies = xStylesSupplier->getStyleFamilies();
            xStyleFamilies->getByName(getPropertyName(PROP_PARAGRAPH_STYLES)) >>= xStyles;
        }

        uno::Reference< container::XIndexReplace> xLevelFormats;
        xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats;
        sal_Int32 nLevelCount = xLevelFormats->getCount();
                        //start with level 1, 0 is the header level
        for( sal_Int32 nLevel = 1; nLevel < nLevelCount; ++nLevel)
        {
            uno::Sequence< beans::PropertyValues > aLevel;
            xLevelFormats->getByIndex( nLevel ) >>= aLevel;

            // Get the tab stops coming from the styles; store to the level definitions
            uno::Sequence<style::TabStop> tabStops;
            if (xStyles)
            {
                OUString style;
                xTOC->getPropertyValue("ParaStyleLevel" + OUString::number(nLevel)) >>= style;
                uno::Reference<beans::XPropertySet> xStyle;
                if (xStyles->getByName(style) >>= xStyle)
                    xStyle->getPropertyValue("ParaTabStops") >>= tabStops;
            }

            uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
                                                bHyperlinks, sChapterNoSeparator,
                                                aLevel, tabStops);
            xLevelFormats->replaceByIndex( nLevel, uno::Any( aNewLevel ) );
        }
    }
    else if (bTableOfFigures && xTOC.is())
@@ -6464,7 +6488,7 @@ void DomainMapper_Impl::handleToc

            uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
                                                bHyperlinks, sChapterNoSeparator,
                                                aLevel );
                                                aLevel, {});
            xLevelFormats->replaceByIndex( 1, uno::Any( aNewLevel ) );
        }
    }