include/com/sun/star/uno/Any.h                                |    4 
 include/com/sun/star/uno/Any.hxx                              |    8 
 sw/qa/extras/ooxmlimport/data/tdf154319-ToC_with_s_and_d.docx |binary
 sw/qa/extras/ooxmlimport/ooxmlimport2.cxx                     |   50 +++
 writerfilter/source/dmapper/DomainMapper_Impl.cxx             |  150 ++++++----
 5 files changed, 162 insertions(+), 50 deletions(-)

New commits:
commit 76777c82fa4bb5080c135e2241c3f7122dcbb298
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Tue Mar 21 21:35:58 2023 +0300
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Thu Mar 23 04:39:28 2023 +0000

    tdf#154319: fix TOC field codes parsing
    
    Change-Id: I734697f52df14ca5b316481df8a58fef72ab9571
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149254
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/include/com/sun/star/uno/Any.h b/include/com/sun/star/uno/Any.h
index f232ccd90fe9..b453f5fa5b0c 100644
--- a/include/com/sun/star/uno/Any.h
+++ b/include/com/sun/star/uno/Any.h
@@ -451,6 +451,10 @@ template<>
 inline bool SAL_CALL operator >>= ( const Any & rAny, ::rtl::OUString & value 
);
 template<>
 inline bool SAL_CALL operator == ( const Any & rAny, const ::rtl::OUString & 
value );
+#if defined LIBO_INTERNAL_ONLY
+template<std::size_t N>
+inline bool SAL_CALL operator == (const Any& rAny, const 
rtl::OUStringLiteral<N>& value);
+#endif
 // type
 template<>
 inline bool SAL_CALL operator >>= ( const Any & rAny, Type & value );
diff --git a/include/com/sun/star/uno/Any.hxx b/include/com/sun/star/uno/Any.hxx
index d73b2a586d61..6267d41e733c 100644
--- a/include/com/sun/star/uno/Any.hxx
+++ b/include/com/sun/star/uno/Any.hxx
@@ -600,6 +600,14 @@ inline bool SAL_CALL operator == ( const Any & rAny, const 
::rtl::OUString & val
     return (typelib_TypeClass_STRING == rAny.pType->eTypeClass &&
             value == * static_cast< const ::rtl::OUString * >( rAny.pData ) );
 }
+
+#if defined LIBO_INTERNAL_ONLY
+template<std::size_t N>
+inline bool SAL_CALL operator == (const Any& rAny, const 
rtl::OUStringLiteral<N>& value)
+{
+    return operator ==(rAny, rtl::OUString(value));
+}
+#endif
 // type
 
 template<>
diff --git a/sw/qa/extras/ooxmlimport/data/tdf154319-ToC_with_s_and_d.docx 
b/sw/qa/extras/ooxmlimport/data/tdf154319-ToC_with_s_and_d.docx
new file mode 100644
index 000000000000..dc5a67824c9d
Binary files /dev/null and 
b/sw/qa/extras/ooxmlimport/data/tdf154319-ToC_with_s_and_d.docx differ
diff --git a/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx 
b/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx
index 09de11d4c0bb..e556d3e08625 100644
--- a/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx
+++ b/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx
@@ -1037,6 +1037,56 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf153791)
     CPPUNIT_ASSERT_EQUAL(COL_AUTO, getProperty<Color>(xRun, "CharColor"));
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf154319)
+{
+    createSwDoc("tdf154319-ToC_with_s_and_d.docx");
+
+    css::uno::Reference<css::text::XDocumentIndexesSupplier> 
xSupplier(mxComponent,
+                                                                       
css::uno::UNO_QUERY_THROW);
+    auto xIndexes = xSupplier->getDocumentIndexes();
+    css::uno::Reference<css::beans::XPropertySet> 
xTOCIndex(xIndexes->getByIndex(0),
+                                                            
css::uno::UNO_QUERY_THROW);
+    css::uno::Reference<css::container::XIndexReplace> xLevelFormats;
+    CPPUNIT_ASSERT(xTOCIndex->getPropertyValue("LevelFormat") >>= 
xLevelFormats);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(11), xLevelFormats->getCount());
+
+    const auto checkPropVal = [](const auto& expected, const 
css::beans::PropertyValues& entry,
+                                 const OUString& name) {
+        auto it
+            = std::find_if(entry.begin(), entry.end(),
+                           [&name](const css::beans::PropertyValue& p) { 
return p.Name == name; });
+        OString msg = "Property: " + name.toUtf8();
+        CPPUNIT_ASSERT_MESSAGE(msg.getStr(), it != entry.end());
+        CPPUNIT_ASSERT_EQUAL_MESSAGE(msg.getStr(), css::uno::Any(expected), 
it->Value);
+    };
+
+    //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());
+
+        checkPropVal(OUString("TokenHyperlinkStart"), aLevel[0], "TokenType");
+
+        checkPropVal(OUString("TokenEntryNumber"), aLevel[1], "TokenType");
+
+        checkPropVal(OUString("TokenEntryText"), aLevel[2], "TokenType");
+
+        checkPropVal(OUString("TokenTabStop"), aLevel[3], "TokenType");
+
+        checkPropVal(OUString("TokenChapterInfo"), aLevel[4], "TokenType");
+
+        checkPropVal(OUString("TokenText"), aLevel[5], "TokenType");
+        checkPropVal(OUString("\""), aLevel[5], "Text");
+
+        checkPropVal(OUString("TokenPageNumber"), aLevel[6], "TokenType");
+
+        checkPropVal(OUString("TokenHyperlinkEnd"), aLevel[7], "TokenType");
+    }
+}
+
 // tests should only be added to ooxmlIMPORT *if* they fail round-tripping in 
ooxmlEXPORT
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 37f7059118c8..01e244bd5e87 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -4796,26 +4796,52 @@ static OUString lcl_ExtractVariableAndHint( 
std::u16string_view rCommand, OUStri
     return OUString(sRet);
 }
 
+static size_t nextCode(std::u16string_view rCommand, size_t pos)
+{
+    bool inQuotes = false;
+    for (; pos < rCommand.size(); ++pos)
+    {
+        switch (rCommand[pos])
+        {
+        case '"':
+            inQuotes = !inQuotes;
+            break;
+        case '\\':
+            ++pos;
+            if (!inQuotes)
+                return pos;
+            break;
+        }
+    }
+    return std::u16string_view::npos;
+}
+
+// Returns the position of the field code
+static size_t findCode(std::u16string_view rCommand, sal_Unicode cSwitch)
+{
+    for (size_t i = nextCode(rCommand, 0); i < rCommand.size(); i = 
nextCode(rCommand, i))
+        if (rCommand[i] == cSwitch)
+            return i;
+
+    return std::u16string_view::npos;
+}
 
 static bool lcl_FindInCommand(
     std::u16string_view rCommand,
     sal_Unicode cSwitch,
     OUString& rValue )
 {
-    bool bRet = false;
-    OUString sSearch = "\\" + OUStringChar( cSwitch );
-    size_t nIndex = rCommand.find( sSearch  );
-    if( nIndex != std::u16string_view::npos )
-    {
-        bRet = true;
-        //find next '\' or end of string
-        size_t nEndIndex = rCommand.find( '\\', nIndex + 1);
-        if( nEndIndex == std::u16string_view::npos )
-            nEndIndex = rCommand.size() ;
-        if( nEndIndex - nIndex > 3 )
-            rValue = rCommand.substr( nIndex + 3, nEndIndex - nIndex - 3);
+    if (size_t i = findCode(rCommand, cSwitch); i < rCommand.size())
+    {
+        ++i;
+        size_t next = nextCode(rCommand, i);
+        if (next < rCommand.size())
+            --next; // get back before the next '\\'
+        rValue = o3tl::trim(rCommand.substr(i, next - i));
+        return true;
     }
-    return bRet;
+
+    return false;
 }
 
 static OUString lcl_trim(std::u16string_view sValue)
@@ -6049,45 +6075,49 @@ void DomainMapper_Impl::handleAuthor
 static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( 
bool bHyperlinks, const OUString& sChapterNoSeparator,
                                    const uno::Sequence< beans::PropertyValues 
>& aLevel )
 {
-    //create a copy of the level and add two new entries - hyperlink start and 
end
-    bool bChapterNoSeparator  = !sChapterNoSeparator.isEmpty();
-    sal_Int32 nAdd = (bHyperlinks && bChapterNoSeparator) ? 4 : 2;
-    uno::Sequence< beans::PropertyValues > aNewLevel( aLevel.getLength() + 
nAdd);
-    beans::PropertyValues* pNewLevel = aNewLevel.getArray();
-    if( bHyperlinks )
-    {
-        beans::PropertyValues aHyperlink{ comphelper::makePropertyValue(
-            getPropertyName( PROP_TOKEN_TYPE ), getPropertyName( 
PROP_TOKEN_HYPERLINK_START )) };
-        pNewLevel[0] = aHyperlink;
-        aHyperlink = { comphelper::makePropertyValue(
-            getPropertyName(PROP_TOKEN_TYPE), getPropertyName( 
PROP_TOKEN_HYPERLINK_END )) };
-        pNewLevel[aNewLevel.getLength() -1] = aHyperlink;
-    }
-    if( bChapterNoSeparator )
-    {
-        beans::PropertyValues aChapterNo{
-            comphelper::makePropertyValue(getPropertyName( PROP_TOKEN_TYPE ),
-                                          getPropertyName( 
PROP_TOKEN_CHAPTER_INFO )),
-            comphelper::makePropertyValue(getPropertyName( PROP_CHAPTER_FORMAT 
),
-                                          //todo: is ChapterFormat::Number 
correct?
-                                          
sal_Int16(text::ChapterFormat::NUMBER))
-        };
-        pNewLevel[aNewLevel.getLength() - (bHyperlinks ? 4 : 2) ] = aChapterNo;
+    //create a copy of the level and add new entries
 
-        beans::PropertyValues aChapterSeparator{
-            comphelper::makePropertyValue(getPropertyName( PROP_TOKEN_TYPE ),
-                                          getPropertyName( PROP_TOKEN_TEXT )),
-            comphelper::makePropertyValue(getPropertyName( PROP_TEXT ), 
sChapterNoSeparator)
-        };
-        pNewLevel[aNewLevel.getLength() - (bHyperlinks ? 3 : 1)] = 
aChapterSeparator;
+    std::vector<css::beans::PropertyValues> aNewLevel;
+    aNewLevel.reserve(aLevel.getLength() + 4); // at most 4 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");
+
+    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);
+                           }))
+            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; 
}))
+        {
+            // This is an existing page number token; insert the chapter and 
separator before it
+            aNewLevel.push_back(
+                { comphelper::makePropertyValue(tokType, 
OUString("TokenChapterInfo")),
+                  comphelper::makePropertyValue("ChapterFormat", 
text::ChapterFormat::NUMBER) });
+            aNewLevel.push_back({ comphelper::makePropertyValue(tokType, 
OUString("TokenText")),
+                                  comphelper::makePropertyValue("Text", 
sChapterNoSeparator) });
+        }
+
+        aNewLevel.push_back(item);
     }
-    //copy the 'old' entries except the last (page no)
-    std::copy(aLevel.begin(), std::prev(aLevel.end()), std::next(pNewLevel));
-    //copy page no entry (last or last but one depending on bHyperlinks
-    sal_Int32 nPageNo = aNewLevel.getLength() - (bHyperlinks ? 2 : 3);
-    pNewLevel[nPageNo] = aLevel[aLevel.getLength() - 1];
 
-    return aNewLevel;
+    if (bHyperlinks)
+        aNewLevel.push_back({ comphelper::makePropertyValue(tokType, tokHEnd) 
});
+
+    return comphelper::containerToSequence(aNewLevel);
 }
 
 /// Returns title of the TOC placed in paragraph(s) before TOC field inside 
STD-frame
@@ -6178,6 +6208,26 @@ static auto FilterChars(std::u16string_view const& 
rStyleName) -> OUString
     return msfilter::util::CreateDOCXStyleId(rStyleName);
 }
 
+static OUString UnquoteFieldText(std::u16string_view s)
+{
+    OUStringBuffer result(s.size());
+    for (size_t i = 0; i < s.size(); ++i)
+    {
+        switch (s[i])
+        {
+        case '"':
+            continue;
+        case '\\':
+            if (i < s.size() - 1)
+                ++i;
+            [[fallthrough]];
+        default:
+            result.append(s[i]);
+        }
+    }
+    return result.makeStringAndClear();
+}
+
 OUString DomainMapper_Impl::ConvertTOCStyleName(OUString const& rTOCStyleName)
 {
     assert(!rTOCStyleName.isEmpty());
@@ -6242,7 +6292,7 @@ void DomainMapper_Impl::handleToc
     if( lcl_FindInCommand( pContext->GetCommand(), 'd', sValue ))
     {
                         //todo: insert the chapter number into each level and 
insert the separator additionally
-        sChapterNoSeparator = sValue;
+        sChapterNoSeparator = UnquoteFieldText(sValue);
     }
 //                  \f Builds a table of contents using TC entries instead of 
outline levels
     if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue ))

Reply via email to