diff options
author | Mike Kaganski <mike.kaganski@collabora.com> | 2022-12-10 08:58:10 +0300 |
---|---|---|
committer | Mike Kaganski <mike.kaganski@collabora.com> | 2023-01-14 07:17:31 +0000 |
commit | 673f59684f8bd2aac29c703f07fdb91165a38775 (patch) | |
tree | 3e6143c093ec0347e09406cc0d1a80464b3187ca | |
parent | acd83239e47f7155611f388f25ab127fb6690ed8 (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.cxx | 8 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlexport16.cxx | 2 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlexport18.cxx | 5 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxattributeoutput.cxx | 16 | ||||
-rw-r--r-- | sw/source/filter/ww8/wrtw8sty.cxx | 142 | ||||
-rw-r--r-- | sw/source/filter/ww8/wrtww8.hxx | 10 |
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; |