summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2022-12-10 08:58:10 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2023-01-14 07:17:31 +0000
commit673f59684f8bd2aac29c703f07fdb91165a38775 (patch)
tree3e6143c093ec0347e09406cc0d1a80464b3187ca
parentacd83239e47f7155611f388f25ab127fb6690ed8 (diff)
tdf#152425 Make Word names unique, and use them for style ids generation
Before, a style id was generated from LibreOffice name, and then the name was replaced with Word name. Given that Writer's List N maps to Word's List Bullet N, and Word's List N doesn't map to anything in Writer, this led to styles.xml having after roundtrip: <w:style w:type="paragraph" w:styleId="List5"> <w:name w:val="List Bullet 5"/> ... <w:style w:type="paragraph" w:styleId="ListBullet5"> <w:name w:val="List Bullet 5"/> So the idea is to do the following steps: 1. Collect all the exported styles (unchanged); 2. Build unique Word names for collected styles (new): a. Process all the styles that map to special Word styles first, so that their Word names don't get changed when made unique; b. Process the rest of the styles, making sure to append a sequential number after the Writer name, if a clash happens. 3. Build Style Ids from the Word names (previously Writer name could be used), also making sure they are unique. Change-Id: I9f8f254aa6ae713671234f0109b94cc72a588150 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143905 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145376 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport13.cxx8
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport16.cxx2
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport18.cxx5
-rw-r--r--sw/source/filter/ww8/docxattributeoutput.cxx16
-rw-r--r--sw/source/filter/ww8/wrtw8sty.cxx142
-rw-r--r--sw/source/filter/ww8/wrtww8.hxx10
6 files changed, 101 insertions, 82 deletions
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
index 671d9c9958db..cd194b5c68f1 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
@@ -1238,8 +1238,8 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf123628)
xmlDocUniquePtr pXmlStyles = parseExport("word/styles.xml");
- assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:hyperlink/w:r/w:rPr/w:rStyle", "val", "InternetLink");
- assertXPath(pXmlStyles, "/w:styles/w:style[@w:styleId='InternetLink']/w:name", "val", "Hyperlink");
+ assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:hyperlink/w:r/w:rPr/w:rStyle", "val", "Hyperlink");
+ assertXPath(pXmlStyles, "/w:styles/w:style[@w:styleId='Hyperlink']/w:name", "val", "Hyperlink");
}
DECLARE_OOXMLEXPORT_TEST(testTdf127741, "tdf127741.docx")
@@ -1265,7 +1265,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf127925)
loadAndSave("tdf127925.odt");
CPPUNIT_ASSERT_EQUAL(1, getPages());
xmlDocUniquePtr pXmlStyles = parseExport("word/styles.xml");
- assertXPath(pXmlStyles, "/w:styles/w:style[@w:styleId='VisitedInternetLink']/w:name", "val", "FollowedHyperlink");
+ assertXPath(pXmlStyles, "/w:styles/w:style[@w:styleId='FollowedHyperlink']/w:name", "val", "FollowedHyperlink");
}
CPPUNIT_TEST_FIXTURE(Test, testTdf127579)
@@ -1273,7 +1273,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf127579)
loadAndSave("tdf127579.odt");
CPPUNIT_ASSERT_EQUAL(1, getPages());
xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
- assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:rStyle", "val", "InternetLink");
+ assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:rStyle", "val", "Hyperlink");
}
DECLARE_OOXMLEXPORT_TEST(testTdf128304, "tdf128304.odt")
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx
index ae8b96c99f66..22c3b75c5e87 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx
@@ -984,7 +984,7 @@ DECLARE_OOXMLEXPORT_TEST(testTdf143726, "Simple-TOC.odt")
CPPUNIT_ASSERT(pXmlStyles);
// Without the fix this was "TOA Heading" which belongs to the "Table of Authorities" index in Word
// TOC's heading style should be exported as "TOC Heading" as that's the default Word style name
- assertXPath(pXmlStyles, "/w:styles/w:style[@w:styleId='ContentsHeading']/w:name", "val", "TOC Heading");
+ assertXPath(pXmlStyles, "/w:styles/w:style[@w:styleId='TOCHeading']/w:name", "val", "TOC Heading");
}
CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
index 37b1bad5d10e..97d1d117e3ea 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
@@ -153,6 +153,11 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf152425)
// CPPUNIT_ASSERT_EQUAL(OUString("List 5"), Para4Style);
// But for now, just make sure that the style names differ
CPPUNIT_ASSERT(Para4Style != Para3Style);
+ const OUString Para5Style = getProperty<OUString>(getParagraph(5), "ParaStyleName");
+ // Eventually, we need to check this:
+ // CPPUNIT_ASSERT_EQUAL(OUString("List Bullet 5"), Para5Style);
+ // But for now, just make sure that the style names differ
+ CPPUNIT_ASSERT(Para5Style != Para4Style);
}
CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index b6334c48af83..e3a7828e5961 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -3861,7 +3861,12 @@ void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData)
m_pSerializer->startElementNS(XML_w, XML_pPr);
- OString sStyleName = MSWordStyles::CreateStyleId( sParaStyleName );
+ OString sStyleName;
+ if (auto format = m_rExport.m_rDoc.FindTextFormatCollByName(sParaStyleName))
+ if (auto slot = m_rExport.m_pStyles->GetSlot(format); slot != 0xfff)
+ sStyleName = m_rExport.m_pStyles->GetStyleId(slot);
+ if (sStyleName.isEmpty())
+ sStyleName = MSWordStyles::CreateStyleId(sParaStyleName);
if ( !sStyleName.isEmpty() )
m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), sStyleName);
@@ -7204,20 +7209,14 @@ void DocxAttributeOutput::StartStyle( const OUString& rName, StyleType eType,
SAL_WARN("sw.ww8", "Unhandled style property: " << rProp.Name);
}
- // MSO exports English names and writerfilter only recognize them.
- const char *pEnglishName = nullptr;
const char* pType = nullptr;
switch (eType)
{
case STYLE_TYPE_PARA:
pType = "paragraph";
- if ( nWwId < ww::stiMax)
- pEnglishName = ww::GetEnglishNameFromSti( static_cast<ww::sti>(nWwId ) );
break;
case STYLE_TYPE_CHAR:
pType = "character";
- if (nWwId < ww::stiMax)
- pEnglishName = ww::GetEnglishNameFromSti(static_cast<ww::sti>(nWwId));
break;
case STYLE_TYPE_LIST: pType = "numbering"; break;
}
@@ -7228,8 +7227,7 @@ void DocxAttributeOutput::StartStyle( const OUString& rName, StyleType eType,
if (bCustomStyle)
pStyleAttributeList->add(FSNS(XML_w, XML_customStyle), "1");
m_pSerializer->startElementNS( XML_w, XML_style, pStyleAttributeList);
- m_pSerializer->singleElementNS( XML_w, XML_name,
- FSNS( XML_w, XML_val ), pEnglishName ? pEnglishName : rName.toUtf8() );
+ m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName);
if ( nBase != 0x0FFF && eType != STYLE_TYPE_LIST)
{
diff --git a/sw/source/filter/ww8/wrtw8sty.cxx b/sw/source/filter/ww8/wrtw8sty.cxx
index 98730c514718..b76907229f0d 100644
--- a/sw/source/filter/ww8/wrtw8sty.cxx
+++ b/sw/source/filter/ww8/wrtw8sty.cxx
@@ -156,6 +156,7 @@ MSWordStyles::MSWordStyles( MSWordExportBase& rExport, bool bListStyles )
memset( m_aHeadingParagraphStyles, -1 , MAXLEVEL * sizeof( sal_uInt16));
BuildStylesTable();
+ BuildWwNames();
BuildStyleIds();
}
@@ -286,7 +287,7 @@ void MSWordStyles::BuildStylesTable()
sal_uInt16 nSlot = BuildGetSlot(*pFormat);
if (nSlot != 0xfff)
{
- m_aStyles[nSlot].format = pFormat;
+ m_aStyles[nSlot] = { pFormat };
}
else
{
@@ -316,7 +317,60 @@ void MSWordStyles::BuildStylesTable()
}
}
-OString MSWordStyles::CreateStyleId(const OUString &rName)
+void MSWordStyles::BuildWwNames()
+{
+ std::unordered_set<OUString> aUsed;
+
+ auto makeUniqueName = [&aUsed](OUString& name) {
+ // toAsciiLowerCase rules out e.g. user's "normal"; no problem if there are non-ASCII chars
+ OUString lower(name.toAsciiLowerCase());
+ if (!aUsed.insert(lower).second)
+ {
+ int nFree = 1;
+ while (!aUsed.insert(lower + OUString::number(nFree)).second)
+ ++nFree;
+
+ name += OUString::number(nFree);
+ }
+ };
+
+ // We want to map LO's default style to Word's "Normal" style.
+ // Word looks for this specific style name when reading docx files.
+ // (It must be the English word regardless of language settings)
+ assert(!m_aStyles.empty());
+ assert(!m_aStyles[0].format || m_aStyles[0].ww_id == ww::stiNormal);
+ m_aStyles[0].ww_name = "Normal";
+ aUsed.insert("normal");
+
+ // 1. Handle styles having special wwIds, and thus pre-defined names
+ for (auto& entry : m_aStyles)
+ {
+ if (!entry.ww_name.isEmpty())
+ continue; // "Normal" is already added
+ if (entry.ww_id >= ww::stiMax)
+ continue; // Not a format with special name
+ assert(entry.format);
+
+ entry.ww_name = OUString::createFromAscii(ww::GetEnglishNameFromSti(static_cast<ww::sti>(entry.ww_id)));
+ makeUniqueName(entry.ww_name);
+ }
+
+ // 2. Now handle other styles
+ for (auto& entry : m_aStyles)
+ {
+ if (!entry.ww_name.isEmpty())
+ continue;
+ if (entry.format)
+ entry.ww_name = entry.format->GetName();
+ else if (entry.num_rule)
+ entry.ww_name = entry.num_rule->GetName();
+ else
+ continue;
+ makeUniqueName(entry.ww_name);
+ }
+}
+
+OString MSWordStyles::CreateStyleId(const OUString& rName)
{
OStringBuffer aStyleIdBuf(rName.getLength());
for (int i = 0; i < rName.getLength(); ++i)
@@ -340,23 +394,9 @@ void MSWordStyles::BuildStyleIds()
{
std::unordered_set<OString> aUsed;
- assert(!m_aStyles.empty());
- m_aStyles[0].style_id = "Normal";
- aUsed.insert("normal");
-
for (auto& entry : m_aStyles)
{
- if (!entry.style_id.isEmpty())
- continue; // "Normal" is already added
-
- assert(entry.style_id.isEmpty());
- OUString name;
- if (entry.format)
- name = entry.format->GetName();
- else if (entry.num_rule)
- name = entry.num_rule->GetName();
-
- OString aStyleId = CreateStyleId(name);
+ OString aStyleId = CreateStyleId(entry.ww_name);
if (aStyleId.isEmpty())
aStyleId = "Style";
@@ -589,68 +629,45 @@ void WW8AttributeOutput::DefaultStyle()
m_rWW8Export.pTableStrm->WriteUInt16(0); // empty Style
}
-void MSWordStyles::OutputStyle(const SwNumRule* pNumRule, sal_uInt16 nSlot)
+void MSWordStyles::OutputStyle(sal_uInt16 nSlot)
{
- m_rExport.AttrOutput().StartStyle( pNumRule->GetName(), STYLE_TYPE_LIST,
+ const auto& entry = m_aStyles[nSlot];
+
+ if (entry.num_rule)
+ {
+ m_rExport.AttrOutput().StartStyle( entry.ww_name, STYLE_TYPE_LIST,
/*nBase =*/ 0, /*nWwNext =*/ 0, /*nWwLink =*/ 0, /*nWWId =*/ 0, nSlot,
/*bAutoUpdateFormat =*/ false );
- m_rExport.AttrOutput().EndStyle();
-}
-
-// OutputStyle applies for TextFormatColls and CharFormats
-void MSWordStyles::OutputStyle( const SwFormat* pFormat, sal_uInt16 nSlot)
-{
- if ( !pFormat )
+ m_rExport.AttrOutput().EndStyle();
+ }
+ else if (!entry.format)
+ {
m_rExport.AttrOutput().DefaultStyle();
+ }
else
{
bool bFormatColl;
sal_uInt16 nBase, nWwNext;
sal_uInt16 nWwLink = 0x0FFF;
- GetStyleData(pFormat, bFormatColl, nBase, nWwNext, nWwLink);
+ GetStyleData(entry.format, bFormatColl, nBase, nWwNext, nWwLink);
- OUString aName = pFormat->GetName();
- // We want to map LO's default style to Word's "Normal" style.
- // Word looks for this specific style name when reading docx files.
- // (It must be the English word regardless of language settings)
- if (nSlot == 0)
- {
- assert( pFormat->GetPoolFormatId() == RES_POOLCOLL_STANDARD );
- aName = "Normal";
- }
- else if (aName.equalsIgnoreAsciiCase("Normal"))
- {
- // If LO has a style named "Normal"(!) rename it to something unique
- const OUString aBaseName = "LO-" + aName;
- aName = aBaseName;
- // Check if we still have a clash, in which case we add a suffix
- for ( int nSuffix = 0; ; ++nSuffix ) {
- if (std::none_of(m_aStyles.begin() + 1, m_aStyles.end(),
- [&aName](const auto& entry) {
- return entry.format
- && entry.format->GetName().equalsIgnoreAsciiCase(aName);
- }))
- break; // Found a unique name
- aName = aBaseName + OUString::number(nSuffix);
- }
- }
- else if (!bFormatColl && m_rExport.GetExportFormat() == MSWordExportBase::DOCX &&
- m_rExport.m_pStyles->GetStyleId(nSlot).startsWith("ListLabel"))
+ if (!bFormatColl && m_rExport.GetExportFormat() == MSWordExportBase::DOCX &&
+ entry.style_id.startsWith("ListLabel"))
{
// tdf#92335 don't export redundant DOCX import style "ListLabel"
return;
}
- m_rExport.AttrOutput().StartStyle( aName, (bFormatColl ? STYLE_TYPE_PARA : STYLE_TYPE_CHAR),
- nBase, nWwNext, nWwLink, GetWWId( *pFormat ), nSlot,
- pFormat->IsAutoUpdateFormat() );
+ m_rExport.AttrOutput().StartStyle(entry.ww_name, (bFormatColl ? STYLE_TYPE_PARA : STYLE_TYPE_CHAR),
+ nBase, nWwNext, nWwLink, m_aStyles[nSlot].ww_id, nSlot,
+ entry.format->IsAutoUpdateFormat() );
if ( bFormatColl )
- WriteProperties( pFormat, true, nSlot, nBase==0xfff ); // UPX.papx
+ WriteProperties( entry.format, true, nSlot, nBase==0xfff ); // UPX.papx
- WriteProperties( pFormat, false, nSlot, bFormatColl && nBase==0xfff ); // UPX.chpx
+ WriteProperties( entry.format, false, nSlot, bFormatColl && nBase==0xfff ); // UPX.chpx
m_rExport.AttrOutput().EndStyle();
}
@@ -699,12 +716,7 @@ void MSWordStyles::OutputStylesTable()
// Implementing check for all exports DOCX, DOC, RTF
assert(m_aStyles.size() <= MSWORD_MAX_STYLES_LIMIT);
for (size_t slot = 0; slot < m_aStyles.size(); ++slot)
- {
- if (m_aStyles[slot].num_rule)
- OutputStyle(m_aStyles[slot].num_rule, slot);
- else
- OutputStyle(m_aStyles[slot].format, slot);
- }
+ OutputStyle(slot);
m_rExport.AttrOutput().EndStyles(m_aStyles.size());
diff --git a/sw/source/filter/ww8/wrtww8.hxx b/sw/source/filter/ww8/wrtww8.hxx
index 66866fc9085b..2c79ae8e70c8 100644
--- a/sw/source/filter/ww8/wrtww8.hxx
+++ b/sw/source/filter/ww8/wrtww8.hxx
@@ -1577,10 +1577,12 @@ class MSWordStyles
const SwFormat* format = nullptr;
const SwNumRule* num_rule = nullptr;
/// We need to build style id's for DOCX export; ideally we should roundtrip that, but this is good enough.
+ sal_uInt16 ww_id = ww::stiUser;
+ OUString ww_name;
OString style_id;
MapEntry() = default;
- MapEntry(const SwFormat* f) : format(f) {}
+ MapEntry(const SwFormat* f) : format(f) { if (f) ww_id = GetWWId(*f); }
MapEntry(const SwNumRule* r) : num_rule(r) {}
};
std::vector<MapEntry> m_aStyles; ///< Slot <-> Character/paragraph/list style array.
@@ -1589,6 +1591,9 @@ class MSWordStyles
/// Create the style table, called from the constructor.
void BuildStylesTable();
+ /// Generate proper Word names, taking mapping between special types into account
+ void BuildWwNames();
+
/// Based on style names, fill in unique, MS-like names.
void BuildStyleIds();
@@ -1603,8 +1608,7 @@ class MSWordStyles
void SetStyleDefaults( const SwFormat& rFormat, bool bPap );
/// Outputs one style - called (in a loop) from OutputStylesTable().
- void OutputStyle( const SwFormat* pFormat, sal_uInt16 nSlot );
- void OutputStyle( const SwNumRule* pNumRule, sal_uInt16 nSlot);
+ void OutputStyle( sal_uInt16 nSlot );
MSWordStyles( const MSWordStyles& ) = delete;
MSWordStyles& operator=( const MSWordStyles& ) = delete;