sw/qa/writerfilter/dmapper/DomainMapper.cxx | 40 +++++++++++++++++++ sw/qa/writerfilter/dmapper/data/clipboard.rtf | 7 +++ sw/source/writerfilter/dmapper/DomainMapper.cxx | 32 +++++++++++++++ sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx | 4 + sw/source/writerfilter/dmapper/StyleSheetTable.cxx | 30 ++++++++++++++ sw/source/writerfilter/dmapper/StyleSheetTable.hxx | 4 + 6 files changed, 117 insertions(+)
New commits: commit f58e3e3402c87755a2dd3cb83f29d00c40b94f1a Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Mon Jun 24 15:46:03 2024 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Mon Jun 24 19:09:09 2024 +0200 Related: tdf#161652 sw, RTF paste: only keep used paragraph styles When pasting from old enough Impress that doesn't have commit afb4ea67463d9f0200dc6216cfd932aec0984c82 (tdf#161652 editeng, RTF copy: only write used paragraph styles, 2024-06-20), it still happened that we got many styles from an Impress slide's paragraph in Writer than just the style of that paragraph itself. The problem is that if we want to avoid problems with bad user input, that has to be handled on the RTF paste / import side, not on the producing side. Fix the problem by filtering out unused paragraph styles also on the RTF import (paste) side, in the IsNewDoc() case, which is the clipboard case (not RTF file open). With this, we attempt to filter out not needed paragraph styles both on the import and export side. Change-Id: Ic2c63e5f45245bb4296ec0d1a95558c459667e29 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169445 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/qa/writerfilter/dmapper/DomainMapper.cxx b/sw/qa/writerfilter/dmapper/DomainMapper.cxx index 3e7acdd3b495..05e6029c8c21 100644 --- a/sw/qa/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/qa/writerfilter/dmapper/DomainMapper.cxx @@ -14,8 +14,13 @@ #include <com/sun/star/beans/PropertyValues.hpp> #include <com/sun/star/drawing/XDrawPageSupplier.hpp> #include <com/sun/star/text/XTextTable.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/document/XFilter.hpp> +#include <com/sun/star/document/XImporter.hpp> #include <tools/UnitConversion.hxx> +#include <unotools/streamwrap.hxx> +#include <comphelper/propertyvalue.hxx> using namespace ::com::sun::star; @@ -185,6 +190,41 @@ CPPUNIT_TEST_FIXTURE(Test, testTableStyleParaBorder) // i.e. the 0 para border distance was applied on the cell instead. CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(203), nLeftBorderDistance); } + +CPPUNIT_TEST_FIXTURE(Test, testRTFStylePaste) +{ + // Given an empty Writer document: + mxComponent + = loadFromDesktop(u"private:factory/swriter"_ustr, u"com.sun.star.text.TextDocument"_ustr); + + // When pasting RTF that has unreferenced paragraph styles: + 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.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 those paragraph styles don't show up in the past result: + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(mxComponent, + uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies + = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily( + xStyleFamilies->getByName(u"ParagraphStyles"_ustr), uno::UNO_QUERY); + // Without the accompanying fix in place, this test would have failed, 'Default Drawing Style' + // was imported, even if no pasted content referenced it. + CPPUNIT_ASSERT(!xStyleFamily->hasByName(u"Default Drawing Style"_ustr)); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/writerfilter/dmapper/data/clipboard.rtf b/sw/qa/writerfilter/dmapper/data/clipboard.rtf new file mode 100644 index 000000000000..0ebaa3aee0e9 --- /dev/null +++ b/sw/qa/writerfilter/dmapper/data/clipboard.rtf @@ -0,0 +1,7 @@ +{ tfnsi +{\stylesheet +{\s1 Default Drawing Style;} +{\s37 Beehive~LT~Titel;} +} +\s37 beehive\par +} diff --git a/sw/source/writerfilter/dmapper/DomainMapper.cxx b/sw/source/writerfilter/dmapper/DomainMapper.cxx index 8e32f2e34569..adb29ba57c23 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.cxx @@ -2700,7 +2700,39 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) 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 )); + + if (!m_pImpl->IsNewDoc()) + { + // Mark the paragraph style & its parent / follow as used. + pStyleTable->MarkParagraphStyleAsUsed(sConvertedStyleName); + + auto pStyle = pStyleTable->FindStyleSheetByConvertedStyleName(sConvertedStyleName); + if (pStyle) + { + if (!pStyle->m_sBaseStyleIdentifier.isEmpty()) + { + StyleSheetEntryPtr pParent = pStyleTable->FindStyleSheetByISTD(pStyle->m_sBaseStyleIdentifier); + if (pParent) + { + OUString sParent = StyleSheetTable::ConvertStyleName(pParent->m_sStyleName).first; + pStyleTable->MarkParagraphStyleAsUsed(sParent); + } + } + + if (!pStyle->m_sNextStyleIdentifier.isEmpty()) + { + StyleSheetEntryPtr pFollow = pStyleTable->FindStyleSheetByISTD(pStyle->m_sNextStyleIdentifier); + if (pFollow) + { + OUString sFollow = StyleSheetTable::ConvertStyleName(pFollow->m_sStyleName).first; + pStyleTable->MarkParagraphStyleAsUsed(sFollow); + } + } + } + } + } } break; case NS_ooxml::LN_EG_RPrBase_rStyle: diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx index 47ef07610ca7..8bb8da5b1db1 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -435,6 +435,10 @@ DomainMapper_Impl::~DomainMapper_Impl() RemoveLastParagraph(); suppress_fun_call_w_exception(GetStyleSheetTable()->ApplyClonedTOCStyles()); } + else if (m_pStyleSheetTable) + { + m_pStyleSheetTable->RemoveUnusedParagraphStyles(); + } if (hasTableManager()) { getTableManager().endLevel(); diff --git a/sw/source/writerfilter/dmapper/StyleSheetTable.cxx b/sw/source/writerfilter/dmapper/StyleSheetTable.cxx index 29f50ecbcf66..5c259de5ff38 100644 --- a/sw/source/writerfilter/dmapper/StyleSheetTable.cxx +++ b/sw/source/writerfilter/dmapper/StyleSheetTable.cxx @@ -285,6 +285,8 @@ struct StyleSheetTable_Impl std::vector< ListCharStylePropertyMap_t > m_aListCharStylePropertyVector; bool m_bHasImportedDefaultParaProps; bool m_bIsNewDoc; + std::set<OUString> m_aInsertedParagraphStyles; + std::set<OUString> m_aUsedParagraphStyles; StyleSheetTable_Impl(DomainMapper& rDMapper, rtl::Reference<SwXTextDocument> xTextDocument, bool bIsNewDoc); @@ -1422,6 +1424,13 @@ void StyleSheetTable::ApplyStyleSheetsImpl(const FontTablePtr& rFontTable, std:: aMissingParent.emplace_back( sParentStyle, xStyle ); xStyles->insertByName( sConvertedStyleName, uno::Any( xStyle) ); + + if (!m_pImpl->m_bIsNewDoc && bParaStyle) + { + // Remember the inserted style, which may or may not be referred during + // pasting content. + m_pImpl->m_aInsertedParagraphStyles.insert(sConvertedStyleName); + } } beans::PropertyValues aGrabBag = pEntry->GetInteropGrabBagSeq(); @@ -2165,6 +2174,27 @@ OUString StyleSheetTable::getOrCreateCharStyle( PropertyValueVector_t& rCharProp return sListLabel; } +void StyleSheetTable::MarkParagraphStyleAsUsed(const OUString& rName) +{ + m_pImpl->m_aUsedParagraphStyles.insert(rName); +} + +void StyleSheetTable::RemoveUnusedParagraphStyles() +{ + uno::Reference< container::XNameAccess > xStyleFamilies = m_pImpl->m_xTextDocument->getStyleFamilies(); + uno::Reference<container::XNameContainer> xParaStyles; + xStyleFamilies->getByName(getPropertyName(PROP_PARAGRAPH_STYLES)) >>= xParaStyles; + for (const auto& rName : m_pImpl->m_aInsertedParagraphStyles) + { + if (m_pImpl->m_aUsedParagraphStyles.contains(rName)) + { + continue; + } + + xParaStyles->removeByName(rName); + } +} + }//namespace writerfilter /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/writerfilter/dmapper/StyleSheetTable.hxx b/sw/source/writerfilter/dmapper/StyleSheetTable.hxx index d388032cdb46..fc2d2a0ba8fa 100644 --- a/sw/source/writerfilter/dmapper/StyleSheetTable.hxx +++ b/sw/source/writerfilter/dmapper/StyleSheetTable.hxx @@ -114,6 +114,10 @@ public: const StyleSheetEntryPtr & GetCurrentEntry() const; + void MarkParagraphStyleAsUsed(const OUString& rName); + /// In case of pasting, removes unused paragraph styles, inserted during the paste. + void RemoveUnusedParagraphStyles(); + private: // Properties virtual void lcl_attribute(Id Name, Value & val) override;