sw/CppunitTest_sw_writerfilter_dmapper.mk | 1 sw/qa/writerfilter/dmapper/NumberingManager.cxx | 75 ++++++++++++++++++ sw/qa/writerfilter/dmapper/data/clipboard-bullets.rtf | 48 +++++++++++ sw/source/writerfilter/dmapper/DomainMapper.cxx | 5 + sw/source/writerfilter/dmapper/DomainMapper.hxx | 1 sw/source/writerfilter/dmapper/NumberingManager.cxx | 33 ++++++- sw/source/writerfilter/dmapper/NumberingManager.hxx | 2 7 files changed, 160 insertions(+), 5 deletions(-)
New commits: commit 8bc8f073e81d125a8e8f1adce966ddb2c7d6bacb Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Thu Jun 27 08:37:19 2024 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Thu Jun 27 10:11:01 2024 +0200 Related: tdf#161652 sw, RTF paste: avoid duplicated numbering styles Open the DOCX bugdoc from <https://bugs.documentfoundation.org/show_bug.cgi?id=161652#c6>, select-all, copy, open a second document, paste as RTF, paste as RTF again, the number of char styles in the document change from 27 to 55, then to 73. That is surprising, paragraph styles work by first creating them then a 2nd paste just refers to them. It seems what happens is that paragraph styles are handled in StyleSheetTable::ApplyStyleSheetsImpl(), and we have an explicit "When pasting, don't update existing styles" code there; on the other hand ListDef::GetStyleName() solves the style name collision by appending enough "a" characters at the end of the style name till there is no collision. Fix the inconsistency by adapting the list style behavior to match what paragraph styles do: if we don't open an RTF file but paste into an existing document, then try to use the existing style on collision. Fixing this on the RTF producing side would be less effective, also one could argue that in case a numbering uses e.g. 3 levels, then it still makes sense to emit the definition for all levels to help the editor once numbering levels are increased in the pasted content. Change-Id: Ia211cb4300c3c41dae8c815ddfaf30cc2f0de8b5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169609 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/CppunitTest_sw_writerfilter_dmapper.mk b/sw/CppunitTest_sw_writerfilter_dmapper.mk index d865706c2b50..a56d02e4b71d 100644 --- a/sw/CppunitTest_sw_writerfilter_dmapper.mk +++ b/sw/CppunitTest_sw_writerfilter_dmapper.mk @@ -23,6 +23,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,sw_writerfilter_dmapper, \ sw/qa/writerfilter/dmapper/DomainMapper \ sw/qa/writerfilter/dmapper/DomainMapper_Impl \ sw/qa/writerfilter/dmapper/GraphicImport \ + sw/qa/writerfilter/dmapper/NumberingManager \ sw/qa/writerfilter/dmapper/TableManager \ sw/qa/writerfilter/dmapper/TextEffectsHandler \ sw/qa/writerfilter/dmapper/PropertyMap \ diff --git a/sw/qa/writerfilter/dmapper/NumberingManager.cxx b/sw/qa/writerfilter/dmapper/NumberingManager.cxx new file mode 100644 index 000000000000..8c60033a74d7 --- /dev/null +++ b/sw/qa/writerfilter/dmapper/NumberingManager.cxx @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <test/unoapi_test.hxx> + +#include <comphelper/propertyvalue.hxx> +#include <unotools/streamwrap.hxx> + +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/document/XImporter.hpp> +#include <com/sun/star/document/XFilter.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/text/XTextDocument.hpp> + +using namespace com::sun::star; + +namespace +{ +/// Tests for sw/source/writerfilter/dmapper/NumberingManager.cxx. +class Test : public UnoApiTest +{ +public: + Test() + : UnoApiTest(u"/sw/qa/writerfilter/dmapper/data/"_ustr) + { + } +}; + +CPPUNIT_TEST_FIXTURE(Test, testPasteBulletListStyleName) +{ + // Given a document with a WWNum1 list style: + mxComponent + = loadFromDesktop(u"private:factory/swriter"_ustr, u"com.sun.star.text.TextDocument"_ustr); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(mxComponent, + uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies + = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameContainer> xStyles; + xStyleFamilies->getByName(u"NumberingStyles"_ustr) >>= xStyles; + uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference<uno::XInterface> xStyle + = xFactory->createInstance("com.sun.star.style.NumberingStyle"); + xStyles->insertByName(u"WWNum1"_ustr, uno::Any(xStyle)); + + // When pasting bullets to that document: + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XTextRange> xText = xTextDocument->getText(); + uno::Reference<text::XTextRange> xBodyEnd = xText->getEnd(); + uno::Reference<document::XFilter> xFilter( + m_xSFactory->createInstance(u"com.sun.star.comp.Writer.RtfFilter"_ustr), uno::UNO_QUERY); + uno::Reference<document::XImporter> xImporter(xFilter, uno::UNO_QUERY); + xImporter->setTargetDocument(mxComponent); + std::unique_ptr<SvStream> pStream( + new SvFileStream(createFileURL(u"clipboard-bullets.rtf"), StreamMode::READ)); + uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(std::move(pStream))); + uno::Sequence aDescriptor{ comphelper::makePropertyValue(u"InputStream"_ustr, xStream), + comphelper::makePropertyValue(u"InsertMode"_ustr, true), + comphelper::makePropertyValue(u"TextInsertModeRange"_ustr, + xBodyEnd) }; + CPPUNIT_ASSERT(xFilter->filter(aDescriptor)); + + // Then make sure we don't create new list styles, but reuse the existing ones: + // Without the accompanying fix in place, this test would have failed, new character styles were + // created again and again on each paste. + CPPUNIT_ASSERT(!xStyles->hasByName(u"WWNum1a"_ustr)); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/writerfilter/dmapper/data/clipboard-bullets.rtf b/sw/qa/writerfilter/dmapper/data/clipboard-bullets.rtf new file mode 100644 index 000000000000..1e9202125ad8 --- /dev/null +++ b/sw/qa/writerfilter/dmapper/data/clipboard-bullets.rtf @@ -0,0 +1,48 @@ +{ tf1nsi\deff4deflang1025 +{onttbl +{0romanprq2charset0 Times New Roman;} +{1romanprq2charset2 Symbol;} +{2swissprq2charset0 Arial;} +{3romanprq2charset0 Liberation Serif +{\*alt Times New Roman} +;} +{4romanprq2charset0 Calibri;} +} +{\stylesheet +{\s0\snext0 tlchfs22lang1025 \ltrch\lang1033\langfe1033\hichf4\loch\sl259\slmult1\ql\widctlpar\sb0\sa160\ltrpar\hyphpar0+{\*+{\*+{\*+{\s25\sbasedon0\snext25\loch\li720\lin720\sb0\sa160+{\s26\sbasedon0\snext26 tlchf8 \ltrch\loch oline Index;} +{\s27\sbasedon0\snext27 tlchf8fs24i \ltrch\loch\sb120\sa120 olines24\i caption;} +{\s28\sbasedon29\snext28 tlchf8 \ltrch List;} +{\s29\sbasedon0\snext29\loch\sl276\slmult1\sb0\sa140 Body Text;} +} +{\*\listtable +{\list\listtemplateid1 +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0 +{\leveltext \'01\u61623 ?;} +{\levelnumbers;} +i-360\li720} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0 +{\leveltext \'01\u111 ?;} +{\levelnumbers;} + tlchf7 \ltrch\lochi-360\li1440} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0 +{\leveltext \'01\u61607 ?;} +{\levelnumbers;} +i-360\li2160} +\listid1} +} +{\listoverridetable +{\listoverride\listid1\listoverridecount0\ls1} +} +\pard\plain before\par +\pard\plain \s25\loch\li720\lin720\sb0\sa160+\ilvl0\ls1 i-360\li1440\lin1440 A +\par \pard\plain \s25\loch\li720\lin720\sb0\sa160+\ilvl1\ls1 i-360\li2160\lin2160 B +\par \pard\plain \s25\loch\li720\lin720\sb0\sa160+\ilvl2\ls1 i-360\li2880\lin2880\sb0\sa160+} diff --git a/sw/source/writerfilter/dmapper/DomainMapper.cxx b/sw/source/writerfilter/dmapper/DomainMapper.cxx index adb29ba57c23..71760a6e3b56 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.cxx @@ -5110,6 +5110,11 @@ OUString DomainMapper::GetUnusedCharacterStyleName() return m_pImpl->GetUnusedCharacterStyleName(); } +bool DomainMapper::IsNewDoc() const +{ + return m_pImpl->IsNewDoc(); +} + } //namespace writerfilter /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/writerfilter/dmapper/DomainMapper.hxx b/sw/source/writerfilter/dmapper/DomainMapper.hxx index ecd5c4459273..fb03a9a9fd8b 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.hxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.hxx @@ -143,6 +143,7 @@ public: css::uno::Reference<css::container::XNameContainer> const & GetCharacterStyles(); OUString GetUnusedCharacterStyleName(); + bool IsNewDoc() const; private: // Stream diff --git a/sw/source/writerfilter/dmapper/NumberingManager.cxx b/sw/source/writerfilter/dmapper/NumberingManager.cxx index 6147af5e0228..3c4c89f156e5 100644 --- a/sw/source/writerfilter/dmapper/NumberingManager.cxx +++ b/sw/source/writerfilter/dmapper/NumberingManager.cxx @@ -421,7 +421,8 @@ ListDef::~ListDef( ) } const OUString & ListDef::GetStyleName(sal_Int32 const nId, - uno::Reference<container::XNameContainer> const& xStyles) + uno::Reference<container::XNameContainer> const& xStyles, + DomainMapper& rDMapper) { if (xStyles.is()) { @@ -429,6 +430,12 @@ const OUString & ListDef::GetStyleName(sal_Int32 const nId, while (xStyles->hasByName(sStyleName)) // unique { + if (!rDMapper.IsNewDoc()) + { + // When pasting, don't rename our style, just use the existing one. + break; + } + sStyleName += "a"; } @@ -538,12 +545,24 @@ void ListDef::CreateNumberingRules( DomainMapper& rDMapper, try { // Create the numbering style + bool bUpdate = true; if (GetId() == nOutline) m_StyleName = "Outline"; //SwNumRule.GetOutlineRuleName() else - xStyles->insertByName( - GetStyleName(GetId(), xStyles), - css::uno::Any(uno::Reference<css::style::XStyle>(xTextDoc->createNumberingStyle()))); + { + OUString aStyleName = GetStyleName(GetId(), xStyles, rDMapper); + if (xStyles->hasByName(aStyleName) && !rDMapper.IsNewDoc()) + { + // When pasting, don't update existing styles. + bUpdate = false; + } + else + { + xStyles->insertByName( + aStyleName, + css::uno::Any(uno::Reference<css::style::XStyle>(xTextDoc->createNumberingStyle()))); + } + } uno::Any oStyle = xStyles->getByName(GetStyleName()); uno::Reference< beans::XPropertySet > xStyle( oStyle, uno::UNO_QUERY_THROW ); @@ -551,6 +570,12 @@ void ListDef::CreateNumberingRules( DomainMapper& rDMapper, // Get the default OOo Numbering style rules uno::Any aRules = xStyle->getPropertyValue( getPropertyName( PROP_NUMBERING_RULES ) ); aRules >>= m_xNumRules; + if (!bUpdate) + { + // If it was requested that we don't update existing styles, we fetched the numbering + // rules, don't modify them. + return; + } uno::Sequence<uno::Sequence<beans::PropertyValue>> aProps = GetMergedPropertyValues(); diff --git a/sw/source/writerfilter/dmapper/NumberingManager.hxx b/sw/source/writerfilter/dmapper/NumberingManager.hxx index 36571bc87ec4..767b6b5d60e9 100644 --- a/sw/source/writerfilter/dmapper/NumberingManager.hxx +++ b/sw/source/writerfilter/dmapper/NumberingManager.hxx @@ -189,7 +189,7 @@ public: // Mapping functions const OUString & GetStyleName() const { return m_StyleName; }; - const OUString & GetStyleName(sal_Int32 nId, css::uno::Reference<css::container::XNameContainer> const& xStyles); + const OUString & GetStyleName(sal_Int32 nId, css::uno::Reference<css::container::XNameContainer> const& xStyles, DomainMapper& rDMapper); css::uno::Sequence< css::uno::Sequence<css::beans::PropertyValue> > GetMergedPropertyValues();