schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng | 12 sw/inc/unoprnms.hxx | 1 sw/source/core/unocore/unoredline.cxx | 168 ++++++++++++ xmloff/qa/unit/data/redline-format-char-props.docx |binary xmloff/qa/unit/text.cxx | 21 + xmloff/source/text/XMLRedlineExport.cxx | 27 + 6 files changed, 228 insertions(+), 1 deletion(-)
New commits: commit b78bdc9eb15fedd22ece76aeb1b43df40caf3b82 Author: Miklos Vajna <[email protected]> AuthorDate: Fri Aug 15 08:25:46 2025 +0200 Commit: Miklos Vajna <[email protected]> CommitDate: Fri Aug 15 15:29:00 2025 +0200 tdf#167761 sw format redline: implement ODF export Load the bugdoc, revert the format redline, the font size should go from 36pt (new direct format) to 24pt (old direct format), but it goes to 12pt (doc default). What happens is that we have working DOCX import to store this in SwRedlineExtraData_FormatColl's item set, but the ODF import/export is missing. Add the ODT export by: 1) Adding a new SwXRedlineAutoStyle that exposes the item set, assuming it contains character properties, and create this in SwXRedlinePortion::GetPropertyValue() if the RedlineAutoFormat property is requested. 2) In XMLRedlineExport::ExportChangeAutoStyle(), write this item set as an autostyle. 3) In XMLRedlineExport::ExportChangedRegion(), refer to this autostyle. Note that similar to delete redlines, the format redline contains the old formatting, the new formatting is directly in the document. Change-Id: Iedd7416e5eefc4814199d60c4a6b68dbb76136e2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189672 Reviewed-by: Miklos Vajna <[email protected]> Tested-by: Jenkins diff --git a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng index e5b2e312030b..a9c923703e0f 100644 --- a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng +++ b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng @@ -4170,4 +4170,16 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1. </rng:optional> </rng:element> </rng:define> + + <!-- TODO(vmiklos) no proposal for style on format changes --> + <rng:define name="text-changed-region-content" combine="choice"> + <rng:element name="text:format-change"> + <rng:optional> + <rng:attribute name="loext:style-name"> + <rng:ref name="styleNameRef"/> + </rng:attribute> + </rng:optional> + <rng:ref name="office-change-info"/> + </rng:element> + </rng:define> </rng:grammar> diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index f95bc877305d..8ffe778f437a 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -607,6 +607,7 @@ inline constexpr OUString UNO_NAME_REDLINE_DESCRIPTION = u"RedlineDescription"_u inline constexpr OUString UNO_NAME_REDLINE_TYPE = u"RedlineType"_ustr; inline constexpr OUString UNO_NAME_REDLINE_SUCCESSOR_DATA = u"RedlineSuccessorData"_ustr; inline constexpr OUString UNO_NAME_REDLINE_IDENTIFIER = u"RedlineIdentifier"_ustr; +inline constexpr OUString UNO_NAME_REDLINE_AUTO_FORMAT = u"RedlineAutoFormat"_ustr; inline constexpr OUString UNO_NAME_IS_IN_HEADER_FOOTER = u"IsInHeaderFooter"_ustr; inline constexpr OUString UNO_NAME_START_REDLINE = u"StartRedline"_ustr; inline constexpr OUString UNO_NAME_END_REDLINE = u"EndRedline"_ustr; diff --git a/sw/source/core/unocore/unoredline.cxx b/sw/source/core/unocore/unoredline.cxx index 0c7cb87f47ec..ca2b4759cf3b 100644 --- a/sw/source/core/unocore/unoredline.cxx +++ b/sw/source/core/unocore/unoredline.cxx @@ -86,6 +86,170 @@ uno::Sequence<beans::PropertyValue> GetSuccessorProperties(const SwRangeRedline& return uno::Sequence<beans::PropertyValue>(5); } +/// Presents character properties in an item set as a beans::XPropertySet. +class SwXRedlineAutoStyle final + : public cppu::WeakImplHelper<beans::XPropertySet, beans::XPropertyState> +{ + std::shared_ptr<SfxItemSet> m_pItemSet; + +public: + SwXRedlineAutoStyle(const std::shared_ptr<SfxItemSet>& pItemSet); + ~SwXRedlineAutoStyle() override; + + // XPropertySet + uno::Reference<beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override; + void SAL_CALL setPropertyValue(const OUString& rPropertyName, + const uno::Any& rValue) override; + uno::Any SAL_CALL getPropertyValue(const OUString& rPropertyName) override; + void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const uno::Reference<beans::XPropertyChangeListener>& xListener) override; + void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const uno::Reference<beans::XPropertyChangeListener>& xListener) override; + void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const uno::Reference<beans::XVetoableChangeListener>& xListener) override; + void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const uno::Reference<beans::XVetoableChangeListener>& xListener) override; + + // XPropertyState + beans::PropertyState SAL_CALL getPropertyState(const OUString& PropertyName) override; + uno::Sequence<beans::PropertyState> + SAL_CALL getPropertyStates(const uno::Sequence<OUString>& aPropertyName) override; + void SAL_CALL setPropertyToDefault(const OUString& PropertyName) override; + uno::Any SAL_CALL getPropertyDefault(const OUString& aPropertyName) override; +}; + +SwXRedlineAutoStyle::SwXRedlineAutoStyle(const std::shared_ptr<SfxItemSet>& pItemSet) + : m_pItemSet(pItemSet) +{ +} + +SwXRedlineAutoStyle::~SwXRedlineAutoStyle() = default; + +uno::Reference<beans::XPropertySetInfo> SAL_CALL SwXRedlineAutoStyle::getPropertySetInfo() +{ + SolarMutexGuard aGuard; + + static uno::Reference<beans::XPropertySetInfo> xRet + = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE)->getPropertySetInfo(); + return xRet; +} + +void SAL_CALL SwXRedlineAutoStyle::setPropertyValue(const OUString& rPropertyName, + const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + + const SfxItemPropertySet* pPropertySet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE); + pPropertySet->setPropertyValue(rPropertyName, rValue, *m_pItemSet); +} + +uno::Any SAL_CALL SwXRedlineAutoStyle::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + const SfxItemPropertySet* pPropertySet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE); + return pPropertySet->getPropertyValue(rPropertyName, *m_pItemSet); +} + +void SAL_CALL SwXRedlineAutoStyle::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference<beans::XPropertyChangeListener>& /*xListener*/) +{ + SAL_WARN("sw.uno", "SwXRedlineAutoStyle::addPropertyChangeListener: not implemented"); +} + +void SAL_CALL SwXRedlineAutoStyle::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference<beans::XPropertyChangeListener>& /*xListener*/) +{ + SAL_WARN("sw.uno", "SwXRedlineAutoStyle::removePropertyChangeListener: not implemented"); +} + +void SAL_CALL SwXRedlineAutoStyle::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference<beans::XVetoableChangeListener>& /*xListener*/) +{ + SAL_WARN("sw.uno", "SwXRedlineAutoStyle::addVetoableChangeListener: not implemented"); +} + +void SAL_CALL SwXRedlineAutoStyle::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference<beans::XVetoableChangeListener>& /*xListener*/) +{ + SAL_WARN("sw.uno", "SwXRedlineAutoStyle::removeVetoableChangeListener: not implemented"); +} + +beans::PropertyState SwXRedlineAutoStyle::getPropertyState(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + const SfxItemPropertySet* pPropertySet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE); + return pPropertySet->getPropertyState(rPropertyName, *m_pItemSet); +} + +uno::Sequence< beans::PropertyState > SwXRedlineAutoStyle::getPropertyStates( + const uno::Sequence<OUString>& rPropertyNames) +{ + SolarMutexGuard aGuard; + + const SfxItemPropertySet* pPropertySet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE); + const OUString* pNames = rPropertyNames.getConstArray(); + uno::Sequence<beans::PropertyState> aRet(rPropertyNames.getLength()); + beans::PropertyState* pStates = aRet.getArray(); + const SfxItemPropertyMap& rMap = pPropertySet->getPropertyMap(); + for (sal_Int32 i = 0, nEnd = rPropertyNames.getLength(); i < nEnd; i++) + { + const SfxItemPropertyMapEntry* pEntry = rMap.getByName(pNames[i]); + if (!pEntry) + { + throw beans::UnknownPropertyException("Unknown property: " + pNames[i]); + } + + pStates[i] = SfxItemPropertySet::getPropertyState(*pEntry, *m_pItemSet); + } + return aRet; +} + +void SwXRedlineAutoStyle::setPropertyToDefault(const OUString& /*rPropertyName*/) +{ + SAL_WARN("sw.uno", "SwXRedlineAutoStyle::setPropertyToDefault: not implemented"); +} + +uno::Any SwXRedlineAutoStyle::getPropertyDefault(const OUString& /*rPropertyName*/) +{ + SAL_WARN("sw.uno", "SwXRedlineAutoStyle::getPropertyDefault: not implemented"); + return uno::Any(); +} + +/// If this format redline has old direct formatting, return it as an autostyle. +uno::Reference<beans::XPropertySet> GetRedlineAutoFormat(const SwRangeRedline& rRedline) +{ + if (rRedline.GetType() != RedlineType::Format) + { + return {}; + } + + const SwRedlineExtraData* pExtraData = rRedline.GetRedlineData().GetExtraData(); + auto pFormattingExtraData = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData); + if (!pFormattingExtraData) + { + return {}; + } + + std::shared_ptr<SfxItemSet> pItemSet = pFormattingExtraData->GetItemSet(); + if (!pItemSet) + { + return {}; + } + + uno::Reference<beans::XPropertySet> xAutoStyle(new SwXRedlineAutoStyle(pItemSet)); + return xAutoStyle; +} + std::optional<uno::Any> GetRedlinePortionPropertyValue(std::u16string_view rPropertyName, const SwRangeRedline& rRedline) { @@ -131,6 +295,10 @@ std::optional<uno::Any> GetRedlinePortionPropertyValue(std::u16string_view rProp { aRet <<= !rRedline.IsDelLastPara(); } + else if (rPropertyName == UNO_NAME_REDLINE_AUTO_FORMAT) + { + aRet <<= GetRedlineAutoFormat(rRedline); + } else { return {}; // Property name unknown; the caller decides when to throw diff --git a/xmloff/qa/unit/data/redline-format-char-props.docx b/xmloff/qa/unit/data/redline-format-char-props.docx new file mode 100644 index 000000000000..dfcf74ebc04e Binary files /dev/null and b/xmloff/qa/unit/data/redline-format-char-props.docx differ diff --git a/xmloff/qa/unit/text.cxx b/xmloff/qa/unit/text.cxx index ce11001b12cd..798e311be507 100644 --- a/xmloff/qa/unit/text.cxx +++ b/xmloff/qa/unit/text.cxx @@ -1291,7 +1291,7 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDeleteThenFormatOdtExport) // Given a document that has a delete redline, and then a format redline on top of it: loadFromFile(u"del-then-format.docx"); - // When exporting that to DOCX: + // When exporting that to ODT: save(u"writer8"_ustr); // Then make sure <text:changed-region> not only has the <text:format-change> child, but also an @@ -1307,6 +1307,25 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDeleteThenFormatOdtExport) assertXPath(pXmlDoc, "//text:tracked-changes/text:changed-region[2]/text:deletion/text:p"); } +CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testRedlineFormatCharProps) +{ + // Given a document with a format redline, the redline contains the old char props: + loadFromFile(u"redline-format-char-props.docx"); + + // When exporting that to ODT: + save(u"writer8"_ustr); + + // Then make sure <text:format-change> refers to an autostyle that describes those old char + // props: + xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr); + assertXPath(pXmlDoc, "//text:tracked-changes/text:changed-region/text:format-change"); + // Without the accompanying fix in place, this test would have failed with: + // - XPath '//text:tracked-changes/text:changed-region/text:format-change' no attribute 'style-name' exist + OUString aStyleName = getXPath( + pXmlDoc, "//text:tracked-changes/text:changed-region/text:format-change", "style-name"); + CPPUNIT_ASSERT(!aStyleName.isEmpty()); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmloff/source/text/XMLRedlineExport.cxx b/xmloff/source/text/XMLRedlineExport.cxx index 34e3ae5c5264..4357e5c11ffb 100644 --- a/xmloff/source/text/XMLRedlineExport.cxx +++ b/xmloff/source/text/XMLRedlineExport.cxx @@ -261,6 +261,16 @@ void XMLRedlineExport::ExportChangeAutoStyle( // export the auto styles rExport.GetTextParagraphExport()->collectTextAutoStyles(xText); } + + // See if the format redline has an autostyle for old direct formatting: if so, export that as + // an autostyle. + aAny = rPropSet->getPropertyValue(u"RedlineAutoFormat"_ustr); + uno::Reference<beans::XPropertySet> xAutoFormat; + aAny >>= xAutoFormat; + if (xAutoFormat.is()) + { + rExport.GetTextParagraphExport()->Add(XmlStyleFamily::TEXT_TEXT, xAutoFormat); + } } void XMLRedlineExport::ExportChangesListAutoStyles() @@ -353,6 +363,23 @@ void XMLRedlineExport::ExportChangedRegion( aAny = rPropSet->getPropertyValue(u"RedlineType"_ustr); OUString sType; aAny >>= sType; + + // See if the format redline has an autostyle for old direct formatting: if so, refer to the + // already exported autostyle. + aAny = rPropSet->getPropertyValue(u"RedlineAutoFormat"_ustr); + uno::Reference<beans::XPropertySet> xAutoStyle; + aAny >>= xAutoStyle; + if (xAutoStyle.is()) + { + bool bIsUICharStyle; + bool bHasAutoStyle; + OUString sStyle = rExport.GetTextParagraphExport()->FindTextStyle(xAutoStyle, bIsUICharStyle, bHasAutoStyle); + if (!sStyle.isEmpty()) + { + rExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_STYLE_NAME, sStyle); + } + } + SvXMLElementExport aChange(rExport, XML_NAMESPACE_TEXT, ConvertTypeName(sType), true, true);
