Kaydet (Commit) 1be0a3fa authored tarafından Michael Stahl's avatar Michael Stahl

n#825305: writerfilter RTF import: override style properties like Word

It would certainly be immediately obvious to any reader of the RTF spec
that \sN will apply the style with index N to the current paragraph.

But actually, that is not what Word does when it reads \sN...
what it really does is to apply the style with index N, and then for
every attribute in that style, apply the same attribute with a default
value to the paragraph, effectively overriding what's in the style.

If that doesn't make any sense to you, well, have you heard the joke
about how many Microsoft engineers it takes to change a light bulb?

Also, \pard apparently implies \s0.

To implement that, change RTFSprms::deduplicate() to recursively
look for style SPRMs that are missing in the properties, and put
in default ones, currently just for 2 keywords \sa and \sb.

This requires changing deduplicate() to be const and return a new value,
since it is no longer idempotent, as the erased SPRMs would get
defaulted on the next run.

While at it, fix RTFValue::equals() which did not compare m_sValue.

This fixes the testParaBottomMargin test that was broken by the fix
for fdo#70578.

Change-Id: I4ced38628d76f6c41b488d608a804883493ff00b
üst 6c0e1270
......@@ -1173,8 +1173,18 @@ DECLARE_RTFIMPORT_TEST(testN825305, "n825305.rtf")
DECLARE_RTFIMPORT_TEST(testParaBottomMargin, "para-bottom-margin.rtf")
uno::Reference<beans::XPropertySet> xPropertySet(
getStyles("ParagraphStyles")->getByName("Standard"), uno::UNO_QUERY);
getProperty<sal_Int32>(xPropertySet, "ParaBottomMargin"));
// This was 353, i.e. bottom margin of the paragraph was 0.35cm instead of 0.
// The reason why this is 0 despite the default style containing \sa200
// is that Word will actually interpret \sN (or \pard which apparently
// implies \s0) as "set style N and for every attribute of that style,
// set an attribute with default value on the paragraph"
CPPUNIT_ASSERT_EQUAL(sal_Int32(0), getProperty<sal_Int32>(getParagraph(1), "ParaBottomMargin"));
CPPUNIT_ASSERT_EQUAL(sal_Int32(2), getProperty<sal_Int32>(getParagraph(1), "ParaTopMargin"));
DECLARE_RTFIMPORT_TEST(testN823655, "n823655.rtf")
......@@ -443,8 +443,12 @@ writerfilter::Reference<Properties>::Pointer_t RTFDocumentImpl::getProperties(RT
RTFReferenceProperties& rProps = *(RTFReferenceProperties*)it->second.get();
// Get rid of direct formatting what is already in the style.
RTFSprms const sprms(
RTFSprms const attributes(
return writerfilter::Reference<Properties>::Pointer_t(
new RTFReferenceProperties(attributes, sprms));
writerfilter::Reference<Properties>::Pointer_t pRet(new RTFReferenceProperties(rAttributes, rSprms));
return pRet;
......@@ -2759,6 +2763,7 @@ int RTFDocumentImpl::dispatchFlag(RTFKeyword nKeyword)
// \pard is allowed between \cell and \row, but in that case it should not reset the fact that we're inside a table.
m_aStates.top().aParagraphSprms = m_aDefaultState.aParagraphSprms;
m_aStates.top().aParagraphAttributes = m_aDefaultState.aParagraphAttributes;
if (m_nTopLevelCells == 0 && m_nNestedCells == 0)
// Reset that we're in a table.
......@@ -2772,7 +2777,20 @@ int RTFDocumentImpl::dispatchFlag(RTFKeyword nKeyword)
// Reset currently selected paragraph style as well.
m_aStates.top().nCurrentStyleIndex = -1;
// By default the style with index 0 is applied.
OUString const aName = getStyleName(0);
if (!aName.isEmpty())
RTFValue::Pointer_t(new RTFValue(aName)));
m_aStates.top().nCurrentStyleIndex = 0;
m_aStates.top().nCurrentStyleIndex = -1;
......@@ -8,8 +8,10 @@
#include <rtfsprm.hxx>
#include <rtl/strbuf.hxx>
#include <ooxml/resourceids.hxx>
#include <resourcemodel/QNameToString.hxx>
......@@ -138,22 +140,68 @@ bool RTFSprms::erase(Id nKeyword)
return false;
void RTFSprms::deduplicate(RTFSprms& rReference)
static RTFValue::Pointer_t getDefaultSPRM(Id const id)
switch (id)
case NS_ooxml::LN_CT_Spacing_before:
case NS_ooxml::LN_CT_Spacing_after:
return RTFValue::Pointer_t(new RTFValue(0));
RTFSprms::Iterator_t i = m_pSprms->begin();
while (i != m_pSprms->end())
return 0;
RTFSprms RTFSprms::cloneAndDeduplicate(RTFSprms& rReference) const
RTFSprms ret(*this);
// Note: apparently some attributes are set with OVERWRITE_NO_APPEND;
// it is probably a bad idea to mess with those in any way here?
for (RTFSprms::Iterator_t i = rReference.begin(); i != rReference.end(); ++i)
bool bIgnore = false;
RTFValue::Pointer_t pValue(rReference.find(i->first));
if (pValue.get() && i->second->equals(*pValue))
bIgnore = true;
if (bIgnore)
i = m_pSprms->erase(i);
RTFValue::Pointer_t const pValue(ret.find(i->first));
if (pValue)
if (i->second->equals(*pValue))
ret.erase(i->first); // duplicate to style
else if (!i->second->getSprms().empty() || !i->second->getAttributes().empty())
RTFSprms const sprms(
RTFSprms const attributes(
ret.set(i->first, RTFValue::Pointer_t(
pValue->CloneWithSprms(attributes, sprms)));
// not found - try to override style with default
RTFValue::Pointer_t const pDefault(getDefaultSPRM(i->first));
if (pDefault)
ret.set(i->first, pDefault);
else if (!i->second->getSprms().empty() || !i->second->getAttributes().empty())
RTFSprms const sprms(
RTFSprms const attributes(
if (!sprms.empty() || !attributes.empty())
RTFValue::Pointer_t(new RTFValue(attributes, sprms)));
return ret;
bool RTFSprms::equals(RTFValue& rOther)
......@@ -67,8 +67,10 @@ public:
/// Does the same as ->push_back(), except that it can overwrite or ignore existing entries.
void set(Id nKeyword, RTFValue::Pointer_t pValue, RTFOverwrite eOverwrite = OVERWRITE_YES);
bool erase(Id nKeyword);
/// Removes elements, which are already in the reference set.
void deduplicate(RTFSprms& rReference);
/// Removes elements which are already in the reference set.
/// Also insert default values to override attributes of style
/// (yes, really; that's what Word does).
RTFSprms cloneAndDeduplicate(RTFSprms& rReference) const;
size_t size() const
return m_pSprms->size();
......@@ -223,10 +223,18 @@ RTFValue* RTFValue::Clone()
return new RTFValue(m_nValue, m_sValue, *m_pAttributes, *m_pSprms, m_xShape, m_xStream, m_xObject, m_bForceString, *m_pShape);
RTFValue* RTFValue::CloneWithSprms(
RTFSprms const& rAttributes, RTFSprms const& rSprms)
return new RTFValue(m_nValue, m_sValue, rAttributes, rSprms, m_xShape, m_xStream, m_xObject, m_bForceString, *m_pShape);
bool RTFValue::equals(RTFValue& rOther)
if (m_nValue != rOther.m_nValue)
return false;
if (m_sValue != rOther.m_sValue)
return false;
if (m_pAttributes->size() != rOther.m_pAttributes->size())
return false;
else if (!m_pAttributes->equals(rOther))
......@@ -48,6 +48,7 @@ public:
virtual writerfilter::Reference<BinaryObj>::Pointer_t getBinary() SAL_OVERRIDE;
virtual std::string toString() const SAL_OVERRIDE;
virtual RTFValue* Clone();
virtual RTFValue* CloneWithSprms(RTFSprms const& rAttributes, RTFSprms const& rSprms);
RTFSprms& getAttributes();
RTFSprms& getSprms();
RTFShape& getShape() const;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment