sw/source/filter/ww8/docxexport.cxx | 422 ++++++++++++++++++------------------ 1 file changed, 213 insertions(+), 209 deletions(-)
New commits: commit 56c20a70e5a7c1edeb73e3c932ca6ab9cc51afb3 Author: Noel Grandin <[email protected]> AuthorDate: Tue Oct 28 11:50:00 2025 +0200 Commit: Noel Grandin <[email protected]> CommitDate: Fri Oct 31 07:57:47 2025 +0100 mso-test: emit w:settings elements in correct places When loading and then saving the document from ooo#120405 , we end up with incorrect ordering of some of the settings sub-elements. Re-structure the code here to match the schema ordering. Pull code related to each sub-element closer together, to make it easier to see when we need to emit an element. Change-Id: I4ef7c2f275911f1226b3c64fe12605de84cb418d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193083 Reviewed-by: Michael Stahl <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193223 Tested-by: Jenkins Reviewed-by: Noel Grandin <[email protected]> diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx index 5ed61dba6249..f70ade9e3b48 100644 --- a/sw/source/filter/ww8/docxexport.cxx +++ b/sw/source/filter/ww8/docxexport.cxx @@ -97,6 +97,7 @@ #include <comphelper/processfactory.hxx> #include <comphelper/sequence.hxx> #include <comphelper/storagehelper.hxx> +#include <comphelper/sequenceashashmap.hxx> #include <o3tl/any.hxx> #include <sal/log.hxx> #include <unotools/ucbstreamhelper.hxx> @@ -1213,14 +1214,13 @@ void DocxExport::WriteSettings() pFS->singleElementNS(XML_w, XML_displayBackgroundShape); } - // Track Changes - if ( !m_aSettings.revisionView ) - pFS->singleElementNS( XML_w, XML_revisionView, - FSNS( XML_w, XML_insDel ), "0", - FSNS( XML_w, XML_formatting ), "0" ); + // Embed Fonts + if( m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_FONTS )) + pFS->singleElementNS(XML_w, XML_embedTrueTypeFonts); - if ( m_aSettings.trackRevisions ) - pFS->singleElementNS(XML_w, XML_trackRevisions); + // Embed System Fonts + if( m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_SYSTEM_FONTS )) + pFS->singleElementNS(XML_w, XML_embedSystemFonts); // Mirror Margins if(isMirroredMargin()) @@ -1231,19 +1231,6 @@ void DocxExport::WriteSettings() pFS->singleElementNS(XML_w, XML_gutterAtTop); } - // Embed Fonts - if( m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_FONTS )) - pFS->singleElementNS(XML_w, XML_embedTrueTypeFonts); - - // Embed System Fonts - if( m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_SYSTEM_FONTS )) - pFS->singleElementNS(XML_w, XML_embedSystemFonts); - - // Default Tab Stop - if( m_aSettings.defaultTabStop != 0 ) - pFS->singleElementNS( XML_w, XML_defaultTabStop, FSNS( XML_w, XML_val ), - OString::number(m_aSettings.defaultTabStop) ); - // export current mail merge database and table names SwDBData aData = m_rDoc.GetDBData(); if ( !aData.sDataSource.isEmpty() && aData.nCommandType == css::sdb::CommandType::TABLE && !aData.sCommand.isEmpty() ) @@ -1264,61 +1251,22 @@ void DocxExport::WriteSettings() pFS->endElementNS( XML_w, XML_mailMerge ); } - // Automatic hyphenation: it's a global setting in Word, it's a paragraph setting in Writer. - // Set it's value to "auto" and disable on paragraph level, if no hyphenation is used there. - pFS->singleElementNS(XML_w, XML_autoHyphenation, FSNS(XML_w, XML_val), "true"); - - // Hyphenation details set depending on default style, otherwise on body style - SwTextFormatColl* pColl = m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD, /*bRegardLanguage=*/false); - if (!pColl || !pColl->GetItemIfSet(RES_PARATR_HYPHENZONE, false)) - pColl = m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT, /*bRegardLanguage=*/false); - const SvxHyphenZoneItem* pZoneItem; - bool bHyphenationKeep = false; - bool bHyphenationZone = false; - if (pColl && (pZoneItem = pColl->GetItemIfSet(RES_PARATR_HYPHENZONE, false))) - { - if ( sal_Int16 nHyphenZone = pZoneItem->GetTextHyphenZone() ) - { - pFS->singleElementNS(XML_w, XML_hyphenationZone, FSNS(XML_w, XML_val), - OString::number(nHyphenZone)); - bHyphenationZone = true; - } - - if (pZoneItem->IsNoCapsHyphenation()) - pFS->singleElementNS(XML_w, XML_doNotHyphenateCaps); - - if ( sal_Int16 nMaxHyphens = pZoneItem->GetMaxHyphens() ) - pFS->singleElementNS(XML_w, XML_consecutiveHyphenLimit, FSNS(XML_w, XML_val), - OString::number(nMaxHyphens)); - - if ( pZoneItem->IsKeep() && pZoneItem->GetKeepType() ) - bHyphenationKeep = true; - } - - // export 0, if hyphenation zone is not defined (otherwise it would be the default 360 twips) - if ( !bHyphenationZone ) - pFS->singleElementNS(XML_w, XML_hyphenationZone, FSNS(XML_w, XML_val), "0"); - - // Even and Odd Headers - if( m_aSettings.evenAndOddHeaders ) - pFS->singleElementNS(XML_w, XML_evenAndOddHeaders); - - // Has Footnotes - if( m_pAttrOutput->HasFootnotes()) - m_pAttrOutput->WriteFootnoteEndnotePr( pFS, XML_footnotePr, m_rDoc.GetFootnoteInfo(), XML_footnote ); + // Track Changes + if ( !m_aSettings.revisionView ) + pFS->singleElementNS( XML_w, XML_revisionView, + FSNS( XML_w, XML_insDel ), "0", + FSNS( XML_w, XML_formatting ), "0" ); - // Has Endnotes - if( m_pAttrOutput->HasEndnotes()) - m_pAttrOutput->WriteFootnoteEndnotePr( pFS, XML_endnotePr, m_rDoc.GetEndNoteInfo(), XML_endnote ); + if ( m_aSettings.trackRevisions ) + pFS->singleElementNS(XML_w, XML_trackRevisions); - // Has themeFontLang information + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = m_xTextDoc->getPropertySetInfo(); + bool bReadOnlyStatusUnchanged = true; bool bUseGrabBagProtection = false; bool bWriterWantsToProtect = false; bool bWriterWantsToProtectForm = false; bool bWriterWantsToProtectRedline = false; bool bHasDummyRedlineProtectionKey = false; - bool bReadOnlyStatusUnchanged = true; - uno::Reference< beans::XPropertySetInfo > xPropSetInfo = m_xTextDoc->getPropertySetInfo(); if ( m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_FORM) || m_pSections->DocumentIsProtected() ) { @@ -1334,157 +1282,215 @@ void DocxExport::WriteSettings() bWriterWantsToProtect = bWriterWantsToProtectRedline = true; } - bool bHasCompatibilityMode = false; + comphelper::SequenceAsHashMap aGrabBagPropMap; const OUString aGrabBagName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG; if ( xPropSetInfo->hasPropertyByName( aGrabBagName ) ) { uno::Sequence< beans::PropertyValue > propList; m_xTextDoc->getPropertyValue( aGrabBagName ) >>= propList; + aGrabBagPropMap = comphelper::SequenceAsHashMap(propList); + } - for (const auto& rProp : propList) + // write w:documentProtection + if (uno::Sequence< beans::PropertyValue > rAttributeList; + aGrabBagPropMap.getValue("DocumentProtection") >>= rAttributeList) + { + if (rAttributeList.hasElements()) { - if ( rProp.Name == "ThemeFontLangProps" ) + rtl::Reference<sax_fastparser::FastAttributeList> xAttributeList = sax_fastparser::FastSerializerHelper::createAttrList(); + bool bIsProtectionTrackChanges = false; + // if grabbag protection is not enforced, allow Writer protection to override + bool bEnforced = false; + for (const auto& rAttribute : rAttributeList) { - uno::Sequence< beans::PropertyValue > themeFontLangProps; - rProp.Value >>= themeFontLangProps; - OUString aValues[3]; - for (const auto& rThemeFontLangProp : themeFontLangProps) + static DocxStringTokenMap const aTokens[] = + { + { "edit", XML_edit }, + { "enforcement", XML_enforcement }, + { "formatting", XML_formatting }, + { "cryptProviderType", XML_cryptProviderType }, + { "cryptAlgorithmClass", XML_cryptAlgorithmClass }, + { "cryptAlgorithmType", XML_cryptAlgorithmType }, + { "cryptAlgorithmSid", XML_cryptAlgorithmSid }, + { "cryptSpinCount", XML_cryptSpinCount }, + { "hash", XML_hash }, + { "salt", XML_salt }, + { nullptr, 0 } + }; + + if (sal_Int32 nToken = DocxStringGetToken(aTokens, rAttribute.Name)) { - if( rThemeFontLangProp.Name == "val" ) - rThemeFontLangProp.Value >>= aValues[0]; - else if( rThemeFontLangProp.Name == "eastAsia" ) - rThemeFontLangProp.Value >>= aValues[1]; - else if( rThemeFontLangProp.Name == "bidi" ) - rThemeFontLangProp.Value >>= aValues[2]; + OUString sValue = rAttribute.Value.get<OUString>(); + xAttributeList->add(FSNS(XML_w, nToken), sValue.toUtf8()); + if ( nToken == XML_edit && sValue == "trackedChanges" ) + bIsProtectionTrackChanges = true; + else if ( nToken == XML_edit && sValue == "readOnly" ) + { + // Ignore the case where read-only was not enforced, but now is. That is handled by _MarkAsFinal + bReadOnlyStatusUnchanged = pDocShell->IsSecurityOptOpenReadOnly(); + } + else if ( nToken == XML_enforcement ) + bEnforced = sValue.toBoolean(); } - pFS->singleElementNS( XML_w, XML_themeFontLang, - FSNS( XML_w, XML_val ), aValues[0], - FSNS( XML_w, XML_eastAsia ), aValues[1], - FSNS( XML_w, XML_bidi ), aValues[2] ); } - else if ( rProp.Name == "CompatSettings" ) + + // we have document protection from input DOCX file + if ( !bEnforced ) { - pFS->startElementNS(XML_w, XML_compat); + // Leave as an un-enforced suggestion if Writer doesn't want to set any enforcement + bUseGrabBagProtection = !bWriterWantsToProtect; + } + else + { + // Check if the grabbag protection is still valid + // In the case of change tracking protection, we didn't modify it + // and in the case of read-only, we didn't modify it. + bUseGrabBagProtection = (!bIsProtectionTrackChanges || bHasDummyRedlineProtectionKey) + && bReadOnlyStatusUnchanged; + } - WriteCompat(m_rDoc, pFS); + if ( bUseGrabBagProtection ) + { + pFS->singleElementNS(XML_w, XML_documentProtection, xAttributeList); + } - uno::Sequence< beans::PropertyValue > aCompatSettingsSequence; - rProp.Value >>= aCompatSettingsSequence; + } + } + if ( !bUseGrabBagProtection ) + { + // Protect form - highest priority + // Section-specific write protection + if ( bWriterWantsToProtectForm ) + { + // we have form protection from Writer or from input ODT file - for (const auto& rCompatSetting : aCompatSettingsSequence) - { - uno::Sequence< beans::PropertyValue > aCompatSetting; - rCompatSetting.Value >>= aCompatSetting; - OUString aName; - OUString aUri; - OUString aValue; + pFS->singleElementNS(XML_w, XML_documentProtection, + FSNS(XML_w, XML_edit), "forms", + FSNS(XML_w, XML_enforcement), "true"); + } + // Protect Change Tracking - next priority + else if ( bWriterWantsToProtectRedline ) + { + // we have change tracking protection from Writer or from input ODT file - for (const auto& rPropVal : aCompatSetting) - { - if( rPropVal.Name == "name" ) - rPropVal.Value >>= aName; - else if( rPropVal.Name == "uri" ) - rPropVal.Value >>= aUri; - else if( rPropVal.Name == "val" ) - rPropVal.Value >>= aValue; - } - if ( aName == "compatibilityMode" ) - { - bHasCompatibilityMode = true; - aValue = OUString::number(getWordCompatibilityMode()); - } + pFS->singleElementNS(XML_w, XML_documentProtection, + FSNS(XML_w, XML_edit), "trackedChanges", + FSNS(XML_w, XML_enforcement), "1"); + } + } - pFS->singleElementNS( XML_w, XML_compatSetting, - FSNS( XML_w, XML_name ), aName, - FSNS( XML_w, XML_uri ), aUri, - FSNS( XML_w, XML_val ), aValue); - } + // Default Tab Stop + if( m_aSettings.defaultTabStop != 0 ) + pFS->singleElementNS( XML_w, XML_defaultTabStop, FSNS( XML_w, XML_val ), + OString::number(m_aSettings.defaultTabStop) ); - if ( !bHasCompatibilityMode ) - { - pFS->singleElementNS( XML_w, XML_compatSetting, - FSNS( XML_w, XML_name ), "compatibilityMode", - FSNS( XML_w, XML_uri ), "http://schemas.microsoft.com/office/word", - FSNS( XML_w, XML_val ), OString::number(getWordCompatibilityMode())); - bHasCompatibilityMode = true; - } + // Automatic hyphenation: it's a global setting in Word, it's a paragraph setting in Writer. + // Set it's value to "auto" and disable on paragraph level, if no hyphenation is used there. + pFS->singleElementNS(XML_w, XML_autoHyphenation, FSNS(XML_w, XML_val), "true"); - pFS->endElementNS( XML_w, XML_compat ); - } - else if (rProp.Name == "DocumentProtection") + // Hyphenation details set depending on default style, otherwise on body style + SwTextFormatColl* pColl = m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD, /*bRegardLanguage=*/false); + if (!pColl || !pColl->GetItemIfSet(RES_PARATR_HYPHENZONE, false)) + pColl = m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT, /*bRegardLanguage=*/false); + const SvxHyphenZoneItem* pZoneItem = nullptr; + bool bHyphenationKeep = false; + bool bHyphenationZone = false; + if (pColl) + pZoneItem = pColl->GetItemIfSet(RES_PARATR_HYPHENZONE, false); + if (pZoneItem) + { + if ( sal_Int16 nMaxHyphens = pZoneItem->GetMaxHyphens() ) + pFS->singleElementNS(XML_w, XML_consecutiveHyphenLimit, FSNS(XML_w, XML_val), + OString::number(nMaxHyphens)); + if ( sal_Int16 nHyphenZone = pZoneItem->GetTextHyphenZone() ) + { + pFS->singleElementNS(XML_w, XML_hyphenationZone, FSNS(XML_w, XML_val), + OString::number(nHyphenZone)); + bHyphenationZone = true; + } + } + if (!bHyphenationZone) + { + sal_Int16 nHyphenationZone; + if (aGrabBagPropMap.getValue("HyphenationZone") >>= nHyphenationZone) + { + if (nHyphenationZone > 0) { - uno::Sequence< beans::PropertyValue > rAttributeList; - rProp.Value >>= rAttributeList; + pFS->singleElementNS(XML_w, XML_hyphenationZone, FSNS(XML_w, XML_val), + OString::number(nHyphenationZone)); + bHyphenationZone = true; + } + } + } + // export 0, if hyphenation zone is not defined (otherwise it would be the default 360 twips) + if ( !bHyphenationZone ) + pFS->singleElementNS(XML_w, XML_hyphenationZone, FSNS(XML_w, XML_val), "0"); + if (pZoneItem && pZoneItem->IsNoCapsHyphenation()) + pFS->singleElementNS(XML_w, XML_doNotHyphenateCaps); + if (pZoneItem && pZoneItem->IsKeep() && pZoneItem->GetKeepType() ) + bHyphenationKeep = true; - if (rAttributeList.hasElements()) - { - rtl::Reference<sax_fastparser::FastAttributeList> xAttributeList = sax_fastparser::FastSerializerHelper::createAttrList(); - bool bIsProtectionTrackChanges = false; - // if grabbag protection is not enforced, allow Writer protection to override - bool bEnforced = false; - for (const auto& rAttribute : rAttributeList) - { - static DocxStringTokenMap const aTokens[] = - { - { "edit", XML_edit }, - { "enforcement", XML_enforcement }, - { "formatting", XML_formatting }, - { "cryptProviderType", XML_cryptProviderType }, - { "cryptAlgorithmClass", XML_cryptAlgorithmClass }, - { "cryptAlgorithmType", XML_cryptAlgorithmType }, - { "cryptAlgorithmSid", XML_cryptAlgorithmSid }, - { "cryptSpinCount", XML_cryptSpinCount }, - { "hash", XML_hash }, - { "salt", XML_salt }, - { nullptr, 0 } - }; - - if (sal_Int32 nToken = DocxStringGetToken(aTokens, rAttribute.Name)) - { - OUString sValue = rAttribute.Value.get<OUString>(); - xAttributeList->add(FSNS(XML_w, nToken), sValue.toUtf8()); - if ( nToken == XML_edit && sValue == "trackedChanges" ) - bIsProtectionTrackChanges = true; - else if ( nToken == XML_edit && sValue == "readOnly" ) - { - // Ignore the case where read-only was not enforced, but now is. That is handled by _MarkAsFinal - bReadOnlyStatusUnchanged = pDocShell->IsSecurityOptOpenReadOnly(); - } - else if ( nToken == XML_enforcement ) - bEnforced = sValue.toBoolean(); - } - } + // Even and Odd Headers + if( m_aSettings.evenAndOddHeaders ) + pFS->singleElementNS(XML_w, XML_evenAndOddHeaders); - // we have document protection from input DOCX file - if ( !bEnforced ) - { - // Leave as an un-enforced suggestion if Writer doesn't want to set any enforcement - bUseGrabBagProtection = !bWriterWantsToProtect; - } - else - { - // Check if the grabbag protection is still valid - // In the case of change tracking protection, we didn't modify it - // and in the case of read-only, we didn't modify it. - bUseGrabBagProtection = (!bIsProtectionTrackChanges || bHasDummyRedlineProtectionKey) - && bReadOnlyStatusUnchanged; - } + // Has Footnotes + if( m_pAttrOutput->HasFootnotes()) + m_pAttrOutput->WriteFootnoteEndnotePr( pFS, XML_footnotePr, m_rDoc.GetFootnoteInfo(), XML_footnote ); - if ( bUseGrabBagProtection ) - { - pFS->singleElementNS(XML_w, XML_documentProtection, xAttributeList); - } + // Has Endnotes + if( m_pAttrOutput->HasEndnotes()) + m_pAttrOutput->WriteFootnoteEndnotePr( pFS, XML_endnotePr, m_rDoc.GetEndNoteInfo(), XML_endnote ); - } + bool bHasCompatibilityMode = false; + + // write w:compat + if (uno::Sequence< beans::PropertyValue > aCompatSettingsSequence; + aGrabBagPropMap.getValue("CompatSettings") >>= aCompatSettingsSequence) + { + pFS->startElementNS(XML_w, XML_compat); + + WriteCompat(m_rDoc, pFS); + + for (const auto& rCompatSetting : aCompatSettingsSequence) + { + uno::Sequence< beans::PropertyValue > aCompatSetting; + rCompatSetting.Value >>= aCompatSetting; + OUString aName; + OUString aUri; + OUString aValue; + + for (const auto& rPropVal : aCompatSetting) + { + if( rPropVal.Name == "name" ) + rPropVal.Value >>= aName; + else if( rPropVal.Name == "uri" ) + rPropVal.Value >>= aUri; + else if( rPropVal.Name == "val" ) + rPropVal.Value >>= aValue; } - else if (rProp.Name == "HyphenationZone") + if ( aName == "compatibilityMode" ) { - sal_Int16 nHyphenationZone = *o3tl::doAccess<sal_Int16>(rProp.Value); - if (nHyphenationZone > 0) - pFS->singleElementNS(XML_w, XML_hyphenationZone, FSNS(XML_w, XML_val), - OString::number(nHyphenationZone)); + bHasCompatibilityMode = true; + aValue = OUString::number(getWordCompatibilityMode()); } + + pFS->singleElementNS( XML_w, XML_compatSetting, + FSNS( XML_w, XML_name ), aName, + FSNS( XML_w, XML_uri ), aUri, + FSNS( XML_w, XML_val ), aValue); } + + if ( !bHasCompatibilityMode ) + { + pFS->singleElementNS( XML_w, XML_compatSetting, + FSNS( XML_w, XML_name ), "compatibilityMode", + FSNS( XML_w, XML_uri ), "http://schemas.microsoft.com/office/word", + FSNS( XML_w, XML_val ), OString::number(getWordCompatibilityMode())); + bHasCompatibilityMode = true; + } + + pFS->endElementNS( XML_w, XML_compat ); } if ( !bHasCompatibilityMode ) { @@ -1530,29 +1536,27 @@ void DocxExport::WriteSettings() pFS->endElementNS( XML_w, XML_compat ); } + // write w:docVars WriteDocVars(pFS); - if ( !bUseGrabBagProtection ) + // write w:themeFontLang + if (uno::Sequence< beans::PropertyValue > themeFontLangProps; + aGrabBagPropMap.getValue("ThemeFontLangProps") >>= themeFontLangProps) { - // Protect form - highest priority - // Section-specific write protection - if ( bWriterWantsToProtectForm ) - { - // we have form protection from Writer or from input ODT file - - pFS->singleElementNS(XML_w, XML_documentProtection, - FSNS(XML_w, XML_edit), "forms", - FSNS(XML_w, XML_enforcement), "true"); - } - // Protect Change Tracking - next priority - else if ( bWriterWantsToProtectRedline ) + OUString aValues[3]; + for (const auto& rThemeFontLangProp : themeFontLangProps) { - // we have change tracking protection from Writer or from input ODT file - - pFS->singleElementNS(XML_w, XML_documentProtection, - FSNS(XML_w, XML_edit), "trackedChanges", - FSNS(XML_w, XML_enforcement), "1"); + if( rThemeFontLangProp.Name == "val" ) + rThemeFontLangProp.Value >>= aValues[0]; + else if( rThemeFontLangProp.Name == "eastAsia" ) + rThemeFontLangProp.Value >>= aValues[1]; + else if( rThemeFontLangProp.Name == "bidi" ) + rThemeFontLangProp.Value >>= aValues[2]; } + pFS->singleElementNS( XML_w, XML_themeFontLang, + FSNS( XML_w, XML_val ), aValues[0], + FSNS( XML_w, XML_eastAsia ), aValues[1], + FSNS( XML_w, XML_bidi ), aValues[2] ); } // finish settings.xml
