tdf#160402 filter,writerfilter: import locale-dependent STYLEREF names

* Handle STYLEREF style the same way as TOC style on import.

* Word 2013 does not uppercase the first letter
  ("Überschrift 1" -> "berschrift1") and there doesn't
  appear to be any justification why the code does that.

* The style that's applied is actually the display style name
  so convert from source's m_sConvertedStyleName.

* Change the logic in DomainMapper_Impl::ConvertTOCStyleName() to
  explicitly check and clone only known built-in Word styles, which are
  the ones that are translated.

* This requires some refactoring, and to add the built-in styles in the
  bugdoc to the "StyleNameMap", which is probably still incomplete...

* Exporting to DOCX appears to work without changes.

* Somehow this causes the testFDO77715 to have an outlinelevel of 1 on
  the TOC paragraphs now, but that turns out to be a bugfix.

Change-Id: I73bc1d1819e5cecbba2fef9cd6d290682a02a638
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167097
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
Tested-by: Jenkins
(cherry picked from commit ab1697cb4c17fd7a2fbf8d374ac95fc03b4d00be)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167243
Reviewed-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
diff --git a/filter/source/msfilter/util.cxx b/filter/source/msfilter/util.cxx
index 4c9a065..4e37947 100644
--- a/filter/source/msfilter/util.cxx
+++ b/filter/source/msfilter/util.cxx
@@ -320,11 +320,8 @@ OUString CreateDOCXStyleId(std::u16string_view const aName)
        sal_Unicode nChar = aName[i];
        if (rtl::isAsciiAlphanumeric(nChar) || nChar == '-')
        {
            // first letter should be uppercase
            if (aStyleIdBuf.isEmpty())
                aStyleIdBuf.append(char(rtl::toAsciiUpperCase(nChar)));
            else
                aStyleIdBuf.append(char(nChar));
            // do not uppercase first letter
            aStyleIdBuf.append(char(nChar));
        }
    }
    return aStyleIdBuf.makeStringAndClear();
diff --git a/sw/qa/extras/ooxmlexport/data/StyleRef-DE.docx b/sw/qa/extras/ooxmlexport/data/StyleRef-DE.docx
new file mode 100644
index 0000000..800ef64
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/StyleRef-DE.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
index 49d974b..f0d5004 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -229,7 +229,7 @@ CPPUNIT_TEST_FIXTURE(Test, testParaStyleNumLevel)
    // - Expected: 1
    // - Actual  : 0
    // i.e. a custom list level in a para style was lost on import+export.
    assertXPath(pXmlDoc, "/w:styles/w:style[@w:styleId='Mystyle']/w:pPr/w:numPr/w:ilvl"_ostr, "val"_ostr, "1");
    assertXPath(pXmlDoc, "/w:styles/w:style[@w:styleId='mystyle']/w:pPr/w:numPr/w:ilvl"_ostr, "val"_ostr, "1");
}

CPPUNIT_TEST_FIXTURE(Test, testClearingBreak)
@@ -741,6 +741,16 @@ DECLARE_OOXMLEXPORT_TEST(testTdf153082_comma, "custom-styles-TOC-comma.docx")
    CPPUNIT_ASSERT(tocContent.indexOf("Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.") != -1);
}

DECLARE_OOXMLEXPORT_TEST(testTdf160402, "StyleRef-DE.docx")
{
    xmlDocUniquePtr pLayout = parseLayoutDump();
    assertXPath(pLayout, "/root/page[1]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr, "Heading 1");
    assertXPath(pLayout, "/root/page[2]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr, "Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus.");
    assertXPath(pLayout, "/root/page[3]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr, "Error: Reference source not found"); // TODO
    assertXPath(pLayout, "/root/page[4]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr, "Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus.");
    assertXPath(pLayout, "/root/page[5]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion"_ostr, "expand"_ostr, "Error: Reference source not found"); // TODO
}

DECLARE_OOXMLEXPORT_TEST(testTdf142407, "tdf142407.docx")
{
    uno::Reference<container::XNameAccess> xPageStyles = getStyles("PageStyles");
diff --git a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
index a97d8e8..66faba5 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
@@ -177,6 +177,8 @@ CPPUNIT_TEST_FIXTURE(Test, testFDO77715)
    xTOC->update();
    OUString const tocContent(xTOC->getAnchor()->getString());
    CPPUNIT_ASSERT(tocContent.startsWith("National Infrastructure Bank Aff/Neg Index"));
    // check that 1st paragraph has outline level 1
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), getProperty<sal_Int32>(getParagraph(1), "OutlineLevel"));
}

CPPUNIT_TEST_FIXTURE(Test, testTOCFlag_u)
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx
index 7e4eeda..344a36c 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -2601,7 +2601,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext )
    case NS_ooxml::LN_CT_PPrBase_pStyle:
    {
        StyleSheetTablePtr pStyleTable = m_pImpl->GetStyleSheetTable();
        const OUString sConvertedStyleName = pStyleTable->ConvertStyleName( sStringValue, true );
        const OUString sConvertedStyleName = pStyleTable->ConvertStyleNameExt(sStringValue);
        m_pImpl->SetCurrentParaStyleName( sConvertedStyleName );
        if (m_pImpl->GetTopContext() && m_pImpl->GetTopContextType() != CONTEXT_SECTION)
            m_pImpl->GetTopContext()->Insert( PROP_PARA_STYLE_NAME, uno::Any( sConvertedStyleName ));
@@ -2609,7 +2609,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext )
    break;
    case NS_ooxml::LN_EG_RPrBase_rStyle:
        {
            OUString sConvertedName( m_pImpl->GetStyleSheetTable()->ConvertStyleName( sStringValue, true ) );
            OUString const sConvertedName(m_pImpl->GetStyleSheetTable()->ConvertStyleNameExt(sStringValue));
            if (m_pImpl->CheckFootnoteStyle() && m_pImpl->GetFootnoteContext())
                m_pImpl->SetHasFootnoteStyle(m_pImpl->GetFootnoteContext()->GetFootnoteStyle() == sConvertedName);

diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 028b4b4..20aa003b 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -6908,21 +6908,18 @@ static OUString UnquoteFieldText(std::u16string_view s)
OUString DomainMapper_Impl::ConvertTOCStyleName(OUString const& rTOCStyleName)
{
    assert(!rTOCStyleName.isEmpty());
    if (auto const pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(rTOCStyleName))
    {   // theoretical case: what OOXML says
        return pStyle->m_sStyleName;
    }
    auto const pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(FilterChars(rTOCStyleName));
    if (pStyle && m_bIsNewDoc)
    {   // practical case: Word wrote i18n name to TOC field, but it doesn't
        // exist in styles.xml; tdf#153083 clone it for best roundtrip
        SAL_INFO("writerfilter.dmapper", "cloning TOC paragraph style (presumed built-in) " << rTOCStyleName << " from " << pStyle->m_sStyleName);
        return GetStyleSheetTable()->CloneTOCStyle(GetFontTable(), pStyle, rTOCStyleName);
    }
    else
    if (auto const pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(FilterChars(rTOCStyleName)))
    {
        return GetStyleSheetTable()->ConvertStyleName(rTOCStyleName);
        auto const [convertedStyleName, isBuiltIn] = StyleSheetTable::ConvertStyleName(pStyle->m_sStyleName);
        if (isBuiltIn && m_bIsNewDoc)
        {   // practical case: Word wrote i18n name to TOC field, but it doesn't
            // exist in styles.xml; tdf#153083 clone it for best roundtrip
            assert(convertedStyleName == pStyle->m_sConvertedStyleName);
            return GetStyleSheetTable()->CloneTOCStyle(GetFontTable(), pStyle, rTOCStyleName);
        }
    }
    // theoretical case: what OOXML says
    return StyleSheetTable::ConvertStyleName(rTOCStyleName).first;
}

void DomainMapper_Impl::handleToc
@@ -7992,29 +7989,8 @@ void DomainMapper_Impl::CloseFieldCommand()
                                getPropertyName(PROP_REFERENCE_FIELD_SOURCE),
                                uno::Any(sal_Int16(text::ReferenceFieldSource::STYLE)));

                            OUString sStyleSheetName
                                = GetStyleSheetTable()->ConvertStyleName(sFirstParam, true);

                            uno::Any aStyleDisplayName;

                            uno::Reference<style::XStyleFamiliesSupplier> xStylesSupplier(
                                GetTextDocument(), uno::UNO_QUERY_THROW);
                            uno::Reference<container::XNameAccess> xStyleFamilies
                                = xStylesSupplier->getStyleFamilies();
                            uno::Reference<container::XNameAccess> xStyles;
                            xStyleFamilies->getByName(getPropertyName(PROP_PARAGRAPH_STYLES))
                                >>= xStyles;
                            uno::Reference<css::beans::XPropertySet> xStyle;

                            try
                            {
                                xStyles->getByName(sStyleSheetName) >>= xStyle;
                                aStyleDisplayName = xStyle->getPropertyValue("DisplayName");
                            }
                            catch (css::container::NoSuchElementException)
                            {
                                aStyleDisplayName <<= sStyleSheetName;
                            }
                            aStyleDisplayName <<= ConvertTOCStyleName(sFirstParam);

                            xFieldProperties->setPropertyValue(
                                getPropertyName(PROP_SOURCE_NAME), aStyleDisplayName);
diff --git a/writerfilter/source/dmapper/StyleSheetTable.cxx b/writerfilter/source/dmapper/StyleSheetTable.cxx
index 711a902..f5c348d 100644
--- a/writerfilter/source/dmapper/StyleSheetTable.cxx
+++ b/writerfilter/source/dmapper/StyleSheetTable.cxx
@@ -813,7 +813,7 @@ void StyleSheetTable::lcl_entry(writerfilter::Reference<Properties>::Pointer_t r
    m_pImpl->m_rDMapper.PopStyleSheetProperties();
    if( !m_pImpl->m_rDMapper.IsOOXMLImport() || !m_pImpl->m_pCurrentEntry->m_sStyleName.isEmpty())
    {
        m_pImpl->m_pCurrentEntry->m_sConvertedStyleName = ConvertStyleName( m_pImpl->m_pCurrentEntry->m_sStyleName );
        m_pImpl->m_pCurrentEntry->m_sConvertedStyleName = ConvertStyleName(m_pImpl->m_pCurrentEntry->m_sStyleName).first;
        m_pImpl->m_aStyleSheetEntries.push_back( m_pImpl->m_pCurrentEntry );
        m_pImpl->m_aStyleSheetEntriesMap.emplace( m_pImpl->m_pCurrentEntry->m_sStyleIdentifierD, m_pImpl->m_pCurrentEntry );
    }
@@ -922,7 +922,7 @@ void StyleSheetTable::ApplyNumberingStyleNameToParaStyles()
                if (pStyleSheetProperties->props().GetListId() > -1)
                {
                    uno::Reference< style::XStyle > xStyle;
                    xParaStyles->getByName( ConvertStyleName(pEntry->m_sStyleName) ) >>= xStyle;
                    xParaStyles->getByName(ConvertStyleName(pEntry->m_sStyleName).first) >>= xStyle;

                    if ( !xStyle.is() )
                        break;
@@ -1076,14 +1076,19 @@ void StyleSheetTable::ApplyClonedTOCStyles()

OUString StyleSheetTable::CloneTOCStyle(FontTablePtr const& rFontTable, StyleSheetEntryPtr const pStyle, OUString const& rNewName)
{
    auto const it = m_pImpl->m_ClonedTOCStylesMap.find(pStyle->m_sConvertedStyleName);
    if (it != m_pImpl->m_ClonedTOCStylesMap.end())
    {
        return it->second;
    }
    SAL_INFO("writerfilter.dmapper", "cloning TOC paragraph style (presumed built-in) " << rNewName << " from " << pStyle->m_sStyleName);
    StyleSheetEntryPtr const pClone(new StyleSheetEntry(*pStyle));
    pClone->m_sStyleIdentifierD = rNewName;
    pClone->m_sStyleName = rNewName;
    pClone->m_sConvertedStyleName = ConvertStyleName(rNewName);
    pClone->m_sConvertedStyleName = ConvertStyleName(rNewName).first;
    m_pImpl->m_aStyleSheetEntries.push_back(pClone);
    // add it so it will be found if referenced from another TOC
    m_pImpl->m_aStyleSheetEntriesMap.emplace(rNewName, pClone);
    m_pImpl->m_ClonedTOCStylesMap.emplace(pStyle->m_sStyleName, pClone->m_sConvertedStyleName);
    // the old converted name is what is applied to paragraphs
    m_pImpl->m_ClonedTOCStylesMap.emplace(pStyle->m_sConvertedStyleName, pClone->m_sConvertedStyleName);
    std::vector<StyleSheetEntryPtr> const styles{ pClone };
    ApplyStyleSheetsImpl(rFontTable, styles);
    return pClone->m_sConvertedStyleName;
@@ -1127,7 +1132,7 @@ void StyleSheetTable::ApplyStyleSheetsImpl(const FontTablePtr& rFontTable, std::
                    bool bInsert = false;
                    uno::Reference< container::XNameContainer > xStyles = bParaStyle ? xParaStyles : (bListStyle ? xNumberingStyles : xCharStyles);
                    uno::Reference< style::XStyle > xStyle;
                    const OUString sConvertedStyleName = ConvertStyleName( pEntry->m_sStyleName );
                    const OUString sConvertedStyleName(ConvertStyleName(pEntry->m_sStyleName).first);

                    if(xStyles->hasByName( sConvertedStyleName ))
                    {
@@ -1194,7 +1199,7 @@ void StyleSheetTable::ApplyStyleSheetsImpl(const FontTablePtr& rFontTable, std::
                            // Writer core doesn't support numbering styles having a parent style, it seems
                            if (pParent && !bListStyle)
                            {
                                const OUString sParentStyleName = ConvertStyleName( pParent->m_sStyleName );
                                const OUString sParentStyleName(ConvertStyleName(pParent->m_sStyleName).first);
                                if ( !sParentStyleName.isEmpty() && !xStyles->hasByName( sParentStyleName ) )
                                    aMissingParent.emplace_back( sParentStyleName, xStyle );
                                else
@@ -1254,7 +1259,7 @@ void StyleSheetTable::ApplyStyleSheetsImpl(const FontTablePtr& rFontTable, std::
                            StyleSheetEntryPtr pLinkStyle
                                = FindStyleSheetByISTD(pEntry->m_sLinkStyleIdentifier);
                            if (pLinkStyle && !pLinkStyle->m_sStyleName.isEmpty())
                                aMissingLink.emplace_back(ConvertStyleName(pLinkStyle->m_sStyleName),
                                aMissingLink.emplace_back(ConvertStyleName(pLinkStyle->m_sStyleName).first,
                                                          xStyle);
                        }
                    }
@@ -1266,7 +1271,7 @@ void StyleSheetTable::ApplyStyleSheetsImpl(const FontTablePtr& rFontTable, std::
                        {
                            StyleSheetEntryPtr pFollowStyle = FindStyleSheetByISTD( pEntry->m_sNextStyleIdentifier );
                            if ( pFollowStyle && !pFollowStyle->m_sStyleName.isEmpty() )
                                aMissingFollow.emplace_back( ConvertStyleName( pFollowStyle->m_sStyleName ), xStyle );
                                aMissingFollow.emplace_back(ConvertStyleName(pFollowStyle->m_sStyleName).first, xStyle);
                        }

                        // Set the outline levels
@@ -1514,10 +1519,9 @@ const StyleSheetEntryPtr & StyleSheetTable::GetCurrentEntry() const
    return m_pImpl->m_pCurrentEntry;
}

OUString StyleSheetTable::ConvertStyleName( const OUString& rWWName, bool bExtendedSearch)
OUString StyleSheetTable::ConvertStyleNameExt(const OUString& rWWName)
{
    OUString sRet( rWWName );
    if( bExtendedSearch )
    {
        //search for the rWWName in the IdentifierD of the existing styles and convert the sStyleName member
        auto findIt = m_pImpl->m_aStyleSheetEntriesMap.find(rWWName);
@@ -1529,6 +1533,14 @@ OUString StyleSheetTable::ConvertStyleName( const OUString& rWWName, bool bExten
        }
    }

    return ConvertStyleName(sRet).first;
}

std::pair<OUString, bool>
StyleSheetTable::ConvertStyleName(const OUString& rWWName)
{
    OUString sRet(rWWName);

    // create a map only once
    // This maps Word's special style manes to Writer's (the opposite to what MSWordStyles::GetWWId
    // and ww::GetEnglishNameFromSti do on export). The mapping gives a Writer's style name, which
@@ -1656,6 +1668,7 @@ OUString StyleSheetTable::ConvertStyleName( const OUString& rWWName, bool bExten
//        { "Message Header", "" },
        { "Subtitle", "Subtitle" }, // RES_POOLCOLL_DOC_SUBTITLE
        { "Salutation", "Salutation" }, // RES_POOLCOLL_GREETING
        { "Intense Quote", "Intense Quote" }, // N/A
//        { "Date", "" },
        { "Body Text First Indent", "First line indent" }, // RES_POOLCOLL_TEXT_IDENT
//        { "Body Text First Indent 2", "" },
@@ -1667,6 +1680,7 @@ OUString StyleSheetTable::ConvertStyleName( const OUString& rWWName, bool bExten
//        { "Block Text", "" },
        { "Hyperlink", "Internet link" }, // RES_POOLCHR_INET_NORMAL
        { "FollowedHyperlink", "Visited Internet Link" }, // RES_POOLCHR_INET_VISIT
        { "Intense Emphasis", "Intense Emphasis" }, // N/A
        { "Strong", "Strong Emphasis" }, // RES_POOLCHR_HTML_STRONG
        { "Emphasis", "Emphasis" }, // RES_POOLCHR_HTML_EMPHASIS
//        { "Document Map", "" },
@@ -1683,6 +1697,7 @@ OUString StyleSheetTable::ConvertStyleName( const OUString& rWWName, bool bExten
    if (const auto aIt = StyleNameMap.find(sRet); aIt != StyleNameMap.end())
    {
        sRet = aIt->second;
        return { sRet, true };
    }
    else
    {
@@ -1704,9 +1719,8 @@ OUString StyleSheetTable::ConvertStyleName( const OUString& rWWName, bool bExten
        // the UI names of built-in styles.
        if (ReservedStyleNames.find(sRet) != ReservedStyleNames.end() || sRet.endsWith(" (WW)"))
            sRet += " (WW)";
        return { sRet, false };
    }

    return sRet;
}

void StyleSheetTable::applyDefaults(bool bParaProperties)
diff --git a/writerfilter/source/dmapper/StyleSheetTable.hxx b/writerfilter/source/dmapper/StyleSheetTable.hxx
index 5cffd7d..92024d8 100644
--- a/writerfilter/source/dmapper/StyleSheetTable.hxx
+++ b/writerfilter/source/dmapper/StyleSheetTable.hxx
@@ -98,7 +98,8 @@ public:
    StyleSheetEntryPtr FindStyleSheetByConvertedStyleName(std::u16string_view rIndex);
    StyleSheetEntryPtr FindDefaultParaStyle();

    OUString ConvertStyleName( const OUString& rWWName, bool bExtendedSearch = false );
    OUString ConvertStyleNameExt(const OUString& rWWName);
    static std::pair<OUString, bool> ConvertStyleName(const OUString& rWWName);
    OUString CloneTOCStyle(FontTablePtr const& rFontTable, StyleSheetEntryPtr const pStyle, OUString const& rName);
    void ApplyClonedTOCStyles();