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 ))