Rebased ref, commits from common ancestor: commit faa71d493a8c9ceab3a67ab7d5197d3b8ec020e2 Author: Serge Krot <serge.k...@cib.de> Date: Fri Sep 29 18:01:54 2017 +0200
tdf#66398 Import/export docx document protection properties This includes: - original fix, import/export of all doc protection properties - unit test - remove double initialization of the form protection - do not output document protection in docx twice - remove useless breaks - fix cid#1418980: Resource leak - parse and output permissions for DOCX using bookmarks - enhance unit test: check permissions at content level - fix copy-paste: call start() bookmark instead of end() Conflicts: sw/source/filter/ww8/docxexport.cxx writerfilter/source/dmapper/DomainMapper.cxx sw/source/filter/ww8/docxexport.cxx Change-Id: I63bde881507dc3c5d123bbcf96b63027522ea569 diff --git a/sw/qa/extras/ooxmlexport/data/tdf66398_permissions.docx b/sw/qa/extras/ooxmlexport/data/tdf66398_permissions.docx new file mode 100644 index 000000000000..d5c855994811 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf66398_permissions.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx index d4c99766b730..45f1aea29b82 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx @@ -963,6 +963,33 @@ DECLARE_OOXMLEXPORT_TEST(testSectionProtection, "sectionprot.odt") } } +DECLARE_OOXMLEXPORT_TEST(tdf66398_permissions, "tdf66398_permissions.docx") +{ + // check document permission settings for the whole document + if (xmlDocPtr pXmlSettings = parseExport("word/settings.xml")) + { + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "edit", "readOnly"); + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "enforcement", "1"); + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "cryptProviderType", "rsaAES"); + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "cryptAlgorithmClass","hash"); + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "cryptAlgorithmType", "typeAny"); + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "cryptAlgorithmSid", "14"); + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "cryptSpinCount", "100000"); + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "hash", "A0/Xy6KcXljJlZjP0TwJMPJuW2rc46UwXqn2ctxckc2nCECE5i89M85z2Noh3ZEA5NBQ9RJ5ycxiUH6nzmJaKw=="); + assertXPath(pXmlSettings, "/w:settings/w:documentProtection", "salt", "B8k6wb1pkjUs4Nv/8QBk/w=="); + } + + // get bookmark interface + uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XIndexAccess> xBookmarksByIdx(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xBookmarksByName(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY); + + // check: we have 2 bookmarks + CPPUNIT_ASSERT_EQUAL(xBookmarksByIdx->getCount(), static_cast<sal_Int32>(2)); + CPPUNIT_ASSERT(xBookmarksByName->hasByName("_GoBack")); + CPPUNIT_ASSERT(xBookmarksByName->hasByName("permission-for-group:267014232:everyone")); +} + DECLARE_OOXMLEXPORT_TEST(testSectionHeader, "sectionprot.odt") { if (xmlDocPtr pXmlDoc = parseExport("word/document.xml")) diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index d37e9989a0e0..2b245545583c 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -1224,8 +1224,13 @@ void DocxAttributeOutput::EndRun() // if there is some redlining in the document, output it StartRedline( m_pRedlineData ); - DoWriteBookmarks( ); - DoWriteAnnotationMarks( ); + // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks. + // The same is applied for permission ranges. + // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end. + DoWriteBookmarksStart(); + DoWriteBookmarksEnd(); + DoWritePermissionsStart(); + DoWriteAnnotationMarks(); if( m_closeHyperlinkInThisRun && m_startedHyperlink && !m_hyperLinkAnchor.isEmpty() && m_hyperLinkAnchor.startsWith("_Toc")) { @@ -1301,6 +1306,9 @@ void DocxAttributeOutput::EndRun() m_pSerializer->mergeTopMarks(Tag_StartRun_1); + // XML_r node should be surrounded with permission-begin and permission-end nodes if it has permission. + DoWritePermissionsEnd(); + for (std::vector<const SwOLENode*>::iterator it = m_aPostponedMaths.begin(); it != m_aPostponedMaths.end(); ++it) WritePostponedMath(*it); m_aPostponedMaths.clear(); @@ -1358,41 +1366,159 @@ void DocxAttributeOutput::EndRun() } } -void DocxAttributeOutput::DoWriteBookmarks() +void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString & bookmarkName) +{ + const OString rId = OString::number(m_nNextBookmarkId); + const OString rName = OUStringToOString(BookmarkToWord(bookmarkName), RTL_TEXTENCODING_UTF8).getStr(); + + m_pSerializer->singleElementNS(XML_w, XML_bookmarkStart, + FSNS(XML_w, XML_id), rId.getStr(), + FSNS(XML_w, XML_name), rName.getStr(), + FSEND); +} + +void DocxAttributeOutput::DoWriteBookmarkTagEnd(const OUString & bookmarkName) { - // Write the start bookmarks - for ( const auto & it : m_rBookmarksStart ) + const auto nameToIdIter = m_rOpenedBookmarksIds.find(bookmarkName); + if (nameToIdIter != m_rOpenedBookmarksIds.end()) { - OString rName = OUStringToOString( BookmarkToWord( it ), RTL_TEXTENCODING_UTF8 ).getStr(); + const sal_Int32 nId = nameToIdIter->second; + const OString rId = OString::number(nId); + + m_pSerializer->singleElementNS(XML_w, XML_bookmarkEnd, + FSNS(XML_w, XML_id), rId.getStr(), + FSEND); + } +} +/// Write the start bookmarks +void DocxAttributeOutput::DoWriteBookmarksStart() +{ + for (const OUString & bookmarkName : m_rBookmarksStart) + { // Output the bookmark - const sal_Int32 nId = m_nNextBookmarkId++; - m_rOpenedBookmarksIds[it] = nId; - m_pSerializer->singleElementNS( XML_w, XML_bookmarkStart, - FSNS( XML_w, XML_id ), OString::number( nId ).getStr( ), - FSNS( XML_w, XML_name ), rName.getStr(), - FSEND ); - m_sLastOpenedBookmark = rName; + DoWriteBookmarkTagStart(bookmarkName); + + m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId; + m_sLastOpenedBookmark = OUStringToOString(BookmarkToWord(bookmarkName), RTL_TEXTENCODING_UTF8).getStr(); + m_nNextBookmarkId++; } m_rBookmarksStart.clear(); +} - // export the end bookmarks - for ( const auto & it : m_rBookmarksEnd ) +/// export the end bookmarks +void DocxAttributeOutput::DoWriteBookmarksEnd() +{ + for (const OUString & bookmarkName : m_rBookmarksEnd) { // Get the id of the bookmark - auto pPos = m_rOpenedBookmarksIds.find(it); - if ( pPos != m_rOpenedBookmarksIds.end() ) + auto pPos = m_rOpenedBookmarksIds.find(bookmarkName); + if (pPos != m_rOpenedBookmarksIds.end()) { - const sal_Int32 nId = ( *pPos ).second; - m_pSerializer->singleElementNS( XML_w, XML_bookmarkEnd, - FSNS( XML_w, XML_id ), OString::number( nId ).getStr(), - FSEND ); - m_rOpenedBookmarksIds.erase( it ); + // Output the bookmark + DoWriteBookmarkTagEnd(bookmarkName); + + m_rOpenedBookmarksIds.erase(bookmarkName); } } m_rBookmarksEnd.clear(); } +// For construction of the special bookmark name template for permissions: +// see, PermInsertPosition::createBookmarkName() +// +// Syntax: +// - "permission-for-user:<permission-id>:<permission-user-name>" +// - "permission-for-group:<permission-id>:<permission-group-name>" +// +void DocxAttributeOutput::DoWritePermissionTagStart(const OUString & permission) +{ + OUString permissionIdAndName; + + if (permission.startsWith("permission-for-group:", &permissionIdAndName)) + { + const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':'); + const OUString permissionId = permissionIdAndName.copy(0, sparatorIndex); + const OUString permissionName = permissionIdAndName.copy(sparatorIndex + 1); + + const OString rId = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr(); + const OString rName = OUStringToOString(BookmarkToWord(permissionName), RTL_TEXTENCODING_UTF8).getStr(); + + m_pSerializer->singleElementNS(XML_w, XML_permStart, + FSNS(XML_w, XML_id), rId.getStr(), + FSNS(XML_w, XML_edGrp), rName.getStr(), + FSEND); + } + else // if (permission.startsWith("permission-for-user:", &permissionIdAndName)) + { + const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':'); + const OUString permissionId = permissionIdAndName.copy(0, sparatorIndex); + const OUString permissionName = permissionIdAndName.copy(sparatorIndex + 1); + + const OString rId = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr(); + const OString rName = OUStringToOString(BookmarkToWord(permissionName), RTL_TEXTENCODING_UTF8).getStr(); + + m_pSerializer->singleElementNS(XML_w, XML_permStart, + FSNS(XML_w, XML_id), rId.getStr(), + FSNS(XML_w, XML_ed), rName.getStr(), + FSEND); + } +} + + +// For construction of the special bookmark name template for permissions: +// see, PermInsertPosition::createBookmarkName() +// +// Syntax: +// - "permission-for-user:<permission-id>:<permission-user-name>" +// - "permission-for-group:<permission-id>:<permission-group-name>" +// +void DocxAttributeOutput::DoWritePermissionTagEnd(const OUString & permission) +{ + OUString permissionIdAndName; + + if (permission.startsWith("permission-for-group:", &permissionIdAndName)) + { + const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':'); + const OUString permissionId = permissionIdAndName.copy(0, sparatorIndex); + const OString rId = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr(); + + m_pSerializer->singleElementNS(XML_w, XML_permEnd, + FSNS(XML_w, XML_id), rId.getStr(), + FSEND); + } + else // if (permission.startsWith("permission-for-user:", &permissionIdAndName)) + { + const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':'); + const OUString permissionId = permissionIdAndName.copy(0, sparatorIndex); + const OString rId = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr(); + + m_pSerializer->singleElementNS(XML_w, XML_permEnd, + FSNS(XML_w, XML_id), rId.getStr(), + FSEND); + } +} + +/// Write the start permissions +void DocxAttributeOutput::DoWritePermissionsStart() +{ + for (const OUString & permission : m_rPermissionsStart) + { + DoWritePermissionTagStart(permission); + } + m_rPermissionsStart.clear(); +} + +/// export the end permissions +void DocxAttributeOutput::DoWritePermissionsEnd() +{ + for (const OUString & permission : m_rPermissionsEnd) + { + DoWritePermissionTagEnd(permission); + } + m_rPermissionsEnd.clear(); +} + void DocxAttributeOutput::DoWriteAnnotationMarks() { // Write the start annotation marks @@ -1619,13 +1745,9 @@ void DocxAttributeOutput::EndField_Impl( FieldInfos& rInfos ) } // Write the bookmark start if any - OUString aBkmName( m_sFieldBkm ); - if ( !aBkmName.isEmpty() ) + if ( !m_sFieldBkm.isEmpty() ) { - m_pSerializer->singleElementNS( XML_w, XML_bookmarkStart, - FSNS( XML_w, XML_id ), OString::number( m_nNextBookmarkId ).getStr( ), - FSNS( XML_w, XML_name ), OUStringToOString( aBkmName, RTL_TEXTENCODING_UTF8 ).getStr( ), - FSEND ); + DoWriteBookmarkTagStart(m_sFieldBkm); } if (rInfos.pField ) // For hyperlinks and TOX @@ -1649,11 +1771,9 @@ void DocxAttributeOutput::EndField_Impl( FieldInfos& rInfos ) } // Write the bookmark end if any - if ( !aBkmName.isEmpty() ) + if ( !m_sFieldBkm.isEmpty() ) { - m_pSerializer->singleElementNS( XML_w, XML_bookmarkEnd, - FSNS( XML_w, XML_id ), OString::number( m_nNextBookmarkId ).getStr( ), - FSEND ); + DoWriteBookmarkTagEnd(m_sFieldBkm); m_nNextBookmarkId++; } @@ -6772,18 +6892,33 @@ void DocxAttributeOutput::WriteFormData_Impl( const ::sw::mark::IFieldmark& rFie m_Fields.begin()->pFieldmark = &rFieldmark; } -void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts, - std::vector< OUString >& rEnds ) +void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds ) { - for ( const auto & it : rStarts ) + for ( const OUString & name : rStarts ) { - m_rBookmarksStart.push_back( it ); + if (name.startsWith("permission-for-group:") || + name.startsWith("permission-for-user:")) + { + m_rPermissionsStart.push_back(name); + } + else + { + m_rBookmarksStart.push_back(name); + } } rStarts.clear(); - for ( const auto & it : rEnds ) + for ( const OUString & name : rEnds ) { - m_rBookmarksEnd.push_back( it ); + if (name.startsWith("permission-for-group:") || + name.startsWith("permission-for-user:")) + { + m_rPermissionsEnd.push_back(name); + } + else + { + m_rBookmarksEnd.push_back(name); + } } rEnds.clear(); } diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index fd4143081c0f..8413b487d0b4 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -692,7 +692,16 @@ protected: private: - void DoWriteBookmarks( ); + void DoWriteBookmarkTagStart(const OUString & bookmarkName); + void DoWriteBookmarkTagEnd(const OUString & bookmarkName); + void DoWriteBookmarksStart(); + void DoWriteBookmarksEnd(); + + void DoWritePermissionTagStart(const OUString & permission); + void DoWritePermissionTagEnd(const OUString & permission); + void DoWritePermissionsStart(); + void DoWritePermissionsEnd(); + void DoWriteAnnotationMarks( ); void WritePostponedGraphic(); void WritePostponedMath(const SwOLENode* pObject); @@ -773,6 +782,10 @@ private: std::vector<OUString> m_rBookmarksStart; std::vector<OUString> m_rBookmarksEnd; + /// Permissions to output + std::vector<OUString> m_rPermissionsStart; + std::vector<OUString> m_rPermissionsEnd; + /// Annotation marks to output std::vector<OString> m_rAnnotationMarksStart; std::vector<OString> m_rAnnotationMarksEnd; diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx index 42900986c57c..e8e738377f25 100644 --- a/sw/source/filter/ww8/docxexport.cxx +++ b/sw/source/filter/ww8/docxexport.cxx @@ -21,6 +21,7 @@ #include "docxexportfilter.hxx" #include "docxattributeoutput.hxx" #include "docxsdrexport.hxx" +#include "docxhelper.hxx" #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> #include <com/sun/star/document/XDocumentProperties.hpp> @@ -855,12 +856,6 @@ void DocxExport::WriteSettings() pFS->singleElementNS( XML_w, XML_defaultTabStop, FSNS( XML_w, XML_val ), OString::number( m_aSettings.defaultTabStop).getStr(), FSEND ); - // Protect form - if( m_pDoc->getIDocumentSettingAccess().get( DocumentSettingId::PROTECT_FORM )) - { - pFS->singleElementNS( XML_w, XML_documentProtection, FSNS(XML_w, XML_edit), "forms", FSNS(XML_w, XML_enforcement), "1", FSEND ); - } - // Automatic hyphenation: it's a global setting in Word, it's a paragraph setting in Writer. // Use the setting from the default style. SwTextFormatColl* pColl = m_pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD, /*bRegardLanguage=*/false); @@ -887,12 +882,14 @@ void DocxExport::WriteSettings() // Has themeFontLang information uno::Reference< beans::XPropertySet > xPropSet( m_pDoc->GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW ); + bool hasProtectionProperties = false; uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); - OUString aGrabBagName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG; + const OUString aGrabBagName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG; if ( xPropSetInfo->hasPropertyByName( aGrabBagName ) ) { uno::Sequence< beans::PropertyValue > propList; xPropSet->getPropertyValue( aGrabBagName ) >>= propList; + for( sal_Int32 i=0; i < propList.getLength(); ++i ) { if ( propList[i].Name == "ThemeFontLangProps" ) @@ -921,6 +918,7 @@ void DocxExport::WriteSettings() uno::Sequence< beans::PropertyValue > aCompatSettingsSequence; propList[i].Value >>= aCompatSettingsSequence; + for(sal_Int32 j=0; j < aCompatSettingsSequence.getLength(); ++j) { uno::Sequence< beans::PropertyValue > aCompatSetting; @@ -947,18 +945,64 @@ void DocxExport::WriteSettings() pFS->endElementNS( XML_w, XML_compat ); } + else if (propList[i].Name == "DocumentProtection") + { + + uno::Sequence< beans::PropertyValue > rAttributeList; + propList[i].Value >>= rAttributeList; + + if (rAttributeList.getLength()) + { + sax_fastparser::FastAttributeList* pAttributeList = sax_fastparser::FastSerializerHelper::createAttrList(); + for (sal_Int32 j = 0; j < rAttributeList.getLength(); ++j) + { + 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, rAttributeList[j].Name)) + pAttributeList->add(FSNS(XML_w, nToken), rAttributeList[j].Value.get<OUString>().toUtf8()); + } + + // we have document protection from from input DOCX file + + sax_fastparser::XFastAttributeListRef xAttributeList(pAttributeList); + pFS->singleElementNS(XML_w, XML_documentProtection, xAttributeList); + + hasProtectionProperties = true; + } + } } } + // Protect form // Section-specific write protection - if ( m_pSections->DocumentIsProtected() ) + if (! hasProtectionProperties) { - pFS->singleElementNS( XML_w, XML_documentProtection, - FSNS( XML_w, XML_enforcement ), "true", - FSNS( XML_w, XML_edit ), "forms", - FSEND ); + if (m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_FORM) || + m_pSections->DocumentIsProtected()) + { + // 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", + FSEND); + } } + // finish settings.xml pFS->endElementNS( XML_w, XML_settings ); } diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx index 789aebea2cf8..138fca3f7863 100644 --- a/writerfilter/source/dmapper/DomainMapper.cxx +++ b/writerfilter/source/dmapper/DomainMapper.cxx @@ -190,10 +190,14 @@ DomainMapper::~DomainMapper() // Grab-bag handling comphelper::SequenceAsHashMap aProperties; + // Add the saved w:themeFontLang setting aProperties["ThemeFontLangProps"] = uno::makeAny(GetThemeFontLangProperties()); // Add the saved compat settings aProperties["CompatSettings"] = uno::makeAny(GetCompatSettings()); + // Add the saved DocumentProtection settings + aProperties["DocumentProtection"] <<= m_pImpl->GetSettingsTable()->GetDocumentProtectionSettings(); + uno::Reference<beans::XPropertySet> xDocProps(m_pImpl->GetTextDocument(), uno::UNO_QUERY); if (xDocProps.is()) { @@ -1077,6 +1081,26 @@ void DomainMapper::lcl_attribute(Id nName, Value & val) case NS_ooxml::LN_CT_Cnf_val: m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "val", sStringValue); break; + case NS_ooxml::LN_CT_PermStart_ed: + { + m_pImpl->setPermissionRangeEd(sStringValue); + break; + } + case NS_ooxml::LN_CT_PermStart_edGrp: + { + m_pImpl->setPermissionRangeEdGrp(sStringValue); + break; + } + case NS_ooxml::LN_CT_PermStart_id: + { + m_pImpl->startOrEndPermissionRange(nIntValue); + break; + } + case NS_ooxml::LN_CT_PermEnd_id: + { + m_pImpl->startOrEndPermissionRange(nIntValue); + break; + } default: SAL_WARN("writerfilter", "DomainMapper::lcl_attribute: unhandled token: " << nName); } diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx index bd532b87cc19..19d6f1a82985 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -199,6 +199,7 @@ DomainMapper_Impl::DomainMapper_Impl( m_bTOCPageRef(false), m_bStartGenericField(false), m_bTextInserted(false), + m_sCurrentPermId(0), m_pLastSectionContext( ), m_pLastCharacterContext(), m_nCurrentTabStopIndex( 0 ), @@ -4586,6 +4587,7 @@ void DomainMapper_Impl::SetBookmarkName( const OUString& rBookmarkName ) m_sCurrentBkmkName = rBookmarkName; } +// This method was used as-is for DomainMapper_Impl::startOrEndPermissionRange() implementation. void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId ) { /* @@ -4665,6 +4667,121 @@ void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId ) } } +void DomainMapper_Impl::setPermissionRangeEd(const OUString& user) +{ + PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId); + if (aPremIter != m_aPermMap.end()) + aPremIter->second.m_Ed = user; + else + m_sCurrentPermEd = user; +} + +void DomainMapper_Impl::setPermissionRangeEdGrp(const OUString& group) +{ + PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId); + if (aPremIter != m_aPermMap.end()) + aPremIter->second.m_EdGrp = group; + else + m_sCurrentPermEdGrp = group; +} + +// This method is based on implementation from DomainMapper_Impl::StartOrEndBookmark() +void DomainMapper_Impl::startOrEndPermissionRange(sal_Int32 permissinId) +{ + /* + * Add the dummy paragraph to handle section properties + * if the first element in the section is a table. If the dummy para is not added yet, then add it; + * So permission is not attached to the wrong paragraph. + */ + if (getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection() + && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted()) + { + AddDummyParaForTableInSection(); + } + + if (m_aTextAppendStack.empty()) + return; + + const bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection(); + + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + PermMap_t::iterator aPermIter = m_aPermMap.find(permissinId); + + //is the bookmark name already registered? + try + { + if (aPermIter == m_aPermMap.end()) + { + //otherwise insert a text range as marker + bool bIsStart = true; + uno::Reference< text::XTextRange > xCurrent; + if (xTextAppend.is()) + { + uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd()); + + if (!bIsAfterDummyPara) + bIsStart = !xCursor->goLeft(1, false); + xCurrent = xCursor->getStart(); + } + + // register the start of the new permission + m_sCurrentPermId = permissinId; + m_aPermMap.emplace(permissinId, PermInsertPosition(bIsStart, permissinId, m_sCurrentPermEd, m_sCurrentPermEdGrp, xCurrent)); + + // clean up + m_sCurrentPermEd.clear(); + m_sCurrentPermEdGrp.clear(); + } + else + { + if (m_xTextFactory.is()) + { + uno::Reference< text::XTextCursor > xCursor; + uno::Reference< text::XText > xText = aPermIter->second.m_xTextRange->getText(); + if (aPermIter->second.m_bIsStartOfText && !bIsAfterDummyPara) + { + xCursor = xText->createTextCursorByRange(xText->getStart()); + } + else + { + xCursor = xText->createTextCursorByRange(aPermIter->second.m_xTextRange); + xCursor->goRight(1, false); + } + + xCursor->gotoRange(xTextAppend->getEnd(), true); + // A Paragraph was recently finished, and a new Paragraph has not been started as yet + // then move the bookmark-End to the earlier paragraph + if (IsOutsideAParagraph()) + { + xCursor->goLeft(1, false); + } + + // create a new bookmark using specific bookmark name pattern for permissions + uno::Reference< text::XTextContent > xPerm(m_xTextFactory->createInstance("com.sun.star.text.Bookmark"), uno::UNO_QUERY_THROW); + uno::Reference< container::XNamed > xPermNamed(xPerm, uno::UNO_QUERY_THROW); + xPermNamed->setName(aPermIter->second.createBookmarkName()); + + // add new bookmark + const bool bAbsorb = !xCursor->isCollapsed(); + uno::Reference< text::XTextRange > xCurrent = uno::Reference< text::XTextRange >(xCursor, uno::UNO_QUERY_THROW); + xTextAppend->insertTextContent(xCurrent, xPerm, bAbsorb); + } + + // remove proccessed permission + m_aPermMap.erase(aPermIter); + + // clean up + m_sCurrentPermId = 0; + m_sCurrentPermEd.clear(); + m_sCurrentPermEdGrp.clear(); + } + } + catch (const uno::Exception&) + { + //TODO: What happens to bookmarks where start and end are at different XText objects? + } +} + void DomainMapper_Impl::AddAnnotationPosition( const bool bStart, const sal_Int32 nAnnotationId) diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx index 37bea0a84605..ad96d4c77ef9 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx @@ -261,6 +261,49 @@ struct BookmarkInsertPosition {} }; +struct PermInsertPosition +{ + bool m_bIsStartOfText; + sal_Int32 m_Id; + OUString m_Ed; + OUString m_EdGrp; + + css::uno::Reference<css::text::XTextRange> m_xTextRange; + + PermInsertPosition(bool bIsStartOfText, sal_Int32 id, const OUString& ed, const OUString& edGrp, css::uno::Reference<css::text::XTextRange> const& xTextRange) + : m_bIsStartOfText(bIsStartOfText) + , m_Id(id) + , m_Ed(ed) + , m_EdGrp(edGrp) + , m_xTextRange(xTextRange) + {} + + OUString createBookmarkName() const + { + OUString bookmarkName; + + assert((!m_Ed.isEmpty()) || (!m_EdGrp.isEmpty())); + + if (m_Ed.isEmpty()) + { + bookmarkName += "permission-for-group:"; + bookmarkName += OUString::number(m_Id); + bookmarkName += ":"; + bookmarkName += m_EdGrp; + } + else + { + bookmarkName += "permission-for-user:"; + bookmarkName += OUString::number(m_Id); + bookmarkName += ":"; + bookmarkName += m_Ed; + } + + //todo: make sure the name is not used already! + return bookmarkName; + } +}; + /// Stores the start/end positions of an annotation before its insertion. struct AnnotationPosition { @@ -339,6 +382,7 @@ class DomainMapper_Impl { public: typedef std::map < OUString, BookmarkInsertPosition > BookmarkMap_t; + typedef std::map < sal_Int32, PermInsertPosition > PermMap_t; private: SourceDocumentType m_eDocumentType; @@ -380,6 +424,11 @@ private: OUString m_sCurrentBkmkId; OUString m_sCurrentBkmkName; + PermMap_t m_aPermMap; + sal_Int32 m_sCurrentPermId; + OUString m_sCurrentPermEd; + OUString m_sCurrentPermEdGrp; + PageMar m_aPageMargins; SymbolData m_aSymbolData; @@ -696,6 +745,10 @@ public: void SetBookmarkName( const OUString& rBookmarkName ); void StartOrEndBookmark( const OUString& rId ); + void setPermissionRangeEd(const OUString& user); + void setPermissionRangeEdGrp(const OUString& group); + void startOrEndPermissionRange(sal_Int32 permissinId); + void AddAnnotationPosition( const bool bStart, const sal_Int32 nAnnotationId ); diff --git a/writerfilter/source/dmapper/SettingsTable.cxx b/writerfilter/source/dmapper/SettingsTable.cxx index 10791f065f5e..56c1c247e7df 100644 --- a/writerfilter/source/dmapper/SettingsTable.cxx +++ b/writerfilter/source/dmapper/SettingsTable.cxx @@ -38,6 +38,176 @@ namespace writerfilter { namespace dmapper { + /** Document protection restrictions + * + * This element specifies the set of document protection restrictions which have been applied to the contents of a + * WordprocessingML document.These restrictions should be enforced by applications editing this document + * when the enforcement attribute is turned on, and ignored(but persisted) otherwise.Document protection is a + * set of restrictions used to prevent unintentional changes to all or part of a WordprocessingML document. + */ + struct DocumentProtection_Impl + { + /** Document Editing Restrictions + * + * Possible values: + * - NS_ooxml::LN_Value_doc_ST_DocProtect_none + * - NS_ooxml::LN_Value_doc_ST_DocProtect_readOnly + * - NS_ooxml::LN_Value_doc_ST_DocProtect_comments + * - NS_ooxml::LN_Value_doc_ST_DocProtect_trackedChanges + * - NS_ooxml::LN_Value_doc_ST_DocProtect_forms + */ + sal_Int32 m_nEdit; + bool m_bEnforcement; + bool m_bFormatting; + + /** Provider type + * + * Possible values: + * "rsaAES" - NS_ooxml::LN_Value_doc_ST_CryptProv_rsaAES + * "rsaFull" - NS_ooxml::LN_Value_doc_ST_CryptProv_rsaFull + */ + sal_Int32 m_nCryptProviderType; + OUString m_sCryptAlgorithmClass; + OUString m_sCryptAlgorithmType; + OUString m_sCryptAlgorithmSid; + sal_Int32 m_CryptSpinCount; + OUString m_sHash; + OUString m_sSalt; + + DocumentProtection_Impl() + : m_nEdit(NS_ooxml::LN_Value_doc_ST_DocProtect_none) // Specifies that no editing restrictions have been applied to the document + , m_bEnforcement(false) + , m_bFormatting(false) + , m_nCryptProviderType(NS_ooxml::LN_Value_doc_ST_CryptProv_rsaAES) + , m_sCryptAlgorithmClass("hash") + , m_sCryptAlgorithmType("typeAny") + , m_CryptSpinCount(0) + { + } + + css::uno::Sequence<css::beans::PropertyValue> toSequence() const; + + bool enabled() const + { + return ! isNone(); + } + + bool isNone() const { return m_nEdit == NS_ooxml::LN_Value_doc_ST_DocProtect_none; }; + // bool isReadOnly() const { return m_nEdit == NS_ooxml::LN_Value_doc_ST_DocProtect_readOnly; }; + // bool isComments() const { return m_nEdit == NS_ooxml::LN_Value_doc_ST_DocProtect_comments; }; + // bool isTrackChanges() const { return m_nEdit == NS_ooxml::LN_Value_doc_ST_DocProtect_trackedChanges; }; + bool isForms() const { return m_nEdit == NS_ooxml::LN_Value_doc_ST_DocProtect_forms; }; + }; + + css::uno::Sequence<css::beans::PropertyValue> DocumentProtection_Impl::toSequence() const + { + std::vector<beans::PropertyValue> documentProtection; + + if (enabled()) + { + // w:edit + { + beans::PropertyValue aValue; + aValue.Name = "edit"; + + switch (m_nEdit) + { + case NS_ooxml::LN_Value_doc_ST_DocProtect_none: aValue.Value <<= OUString("none"); break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_readOnly: aValue.Value <<= OUString("readOnly"); break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_comments: aValue.Value <<= OUString("comments"); break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_trackedChanges: aValue.Value <<= OUString("trackedChanges"); break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_forms: aValue.Value <<= OUString("forms"); break; + default: + { +#ifdef DEBUG_WRITERFILTER + TagLogger::getInstance().element("unhandled"); +#endif + } + } + + documentProtection.push_back(aValue); + } + + // w:enforcement + if (m_bEnforcement) + { + beans::PropertyValue aValue; + aValue.Name = "enforcement"; + aValue.Value <<= OUString("1"); + documentProtection.push_back(aValue); + } + + // w:formatting + if (m_bFormatting) + { + beans::PropertyValue aValue; + aValue.Name = "formatting"; + aValue.Value <<= OUString("1"); + documentProtection.push_back(aValue); + } + + // w:cryptProviderType + { + beans::PropertyValue aValue; + aValue.Name = "cryptProviderType"; + if (m_nCryptProviderType == NS_ooxml::LN_Value_doc_ST_CryptProv_rsaAES) + aValue.Value <<= OUString("rsaAES"); + else if (m_nCryptProviderType == NS_ooxml::LN_Value_doc_ST_CryptProv_rsaFull) + aValue.Value <<= OUString("rsaFull"); + documentProtection.push_back(aValue); + } + + // w:cryptAlgorithmClass + { + beans::PropertyValue aValue; + aValue.Name = "cryptAlgorithmClass"; + aValue.Value <<= m_sCryptAlgorithmClass; + documentProtection.push_back(aValue); + } + + // w:cryptAlgorithmType + { + beans::PropertyValue aValue; + aValue.Name = "cryptAlgorithmType"; + aValue.Value <<= m_sCryptAlgorithmType; + documentProtection.push_back(aValue); + } + + // w:cryptAlgorithmSid + { + beans::PropertyValue aValue; + aValue.Name = "cryptAlgorithmSid"; + aValue.Value <<= m_sCryptAlgorithmSid; + documentProtection.push_back(aValue); + } + + // w:cryptSpinCount + { + beans::PropertyValue aValue; + aValue.Name = "cryptSpinCount"; + aValue.Value <<= OUString::number(m_CryptSpinCount); + documentProtection.push_back(aValue); + } + + // w:hash + { + beans::PropertyValue aValue; + aValue.Name = "hash"; + aValue.Value <<= m_sHash; + documentProtection.push_back(aValue); + } + + // w:salt + { + beans::PropertyValue aValue; + aValue.Name = "salt"; + aValue.Value <<= m_sSalt; + documentProtection.push_back(aValue); + } + } + + return comphelper::containerToSequence(documentProtection); + } struct SettingsTable_Impl { @@ -71,6 +241,8 @@ struct SettingsTable_Impl std::vector<beans::PropertyValue> m_aCompatSettings; uno::Sequence<beans::PropertyValue> m_pCurrentCompatSetting; + DocumentProtection_Impl m_DocumentProtection; + SettingsTable_Impl() : m_nDefaultTabStop( 720 ) //default is 1/2 in , m_nHyphenationZone(0) @@ -146,12 +318,40 @@ void SettingsTable::lcl_attribute(Id nName, Value & val) m_pImpl->m_pCurrentCompatSetting[2].Name = "val"; m_pImpl->m_pCurrentCompatSetting[2].Value <<= sStringValue; break; - case NS_ooxml::LN_CT_DocProtect_edit: - m_pImpl->m_bProtectForm = (nIntValue == NS_ooxml::LN_Value_doc_ST_DocProtect_forms); + case NS_ooxml::LN_CT_DocProtect_edit: // 92037 + m_pImpl->m_DocumentProtection.m_nEdit = nIntValue; + m_pImpl->m_bProtectForm = m_pImpl->m_DocumentProtection.isForms(); break; - case NS_ooxml::LN_CT_DocProtect_enforcement: + case NS_ooxml::LN_CT_DocProtect_enforcement: // 92039 + m_pImpl->m_DocumentProtection.m_bEnforcement = (nIntValue != 0); m_pImpl->m_bProtectForm &= (bool)nIntValue; break; + case NS_ooxml::LN_CT_DocProtect_formatting: // 92038 + m_pImpl->m_DocumentProtection.m_bFormatting = (nIntValue != 0); + break; + case NS_ooxml::LN_AG_Password_cryptProviderType: // 92025 + m_pImpl->m_DocumentProtection.m_nCryptProviderType = nIntValue; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmClass: // 92026 + if (nIntValue == NS_ooxml::LN_Value_doc_ST_AlgClass_hash) // 92023 + m_pImpl->m_DocumentProtection.m_sCryptAlgorithmClass = "hash"; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmType: // 92027 + if (nIntValue == NS_ooxml::LN_Value_doc_ST_AlgType_typeAny) // 92024 + m_pImpl->m_DocumentProtection.m_sCryptAlgorithmType = "typeAny"; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmSid: // 92028 + m_pImpl->m_DocumentProtection.m_sCryptAlgorithmSid = sStringValue; + break; + case NS_ooxml::LN_AG_Password_cryptSpinCount: // 92029 + m_pImpl->m_DocumentProtection.m_CryptSpinCount = nIntValue; + break; + case NS_ooxml::LN_AG_Password_hash: // 92035 + m_pImpl->m_DocumentProtection.m_sHash = sStringValue; + break; + case NS_ooxml::LN_AG_Password_salt: // 92036 + m_pImpl->m_DocumentProtection.m_sSalt = sStringValue; + break; default: { #ifdef DEBUG_WRITERFILTER @@ -184,9 +384,7 @@ void SettingsTable::lcl_sprm(Sprm& rSprm) case NS_ooxml::LN_CT_Settings_view: //PropertySetValues - need to be resolved { - writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); - if( pProperties.get()) - pProperties->resolve(*this); + resolveSprmProps(*this, rSprm); } break; case NS_ooxml::LN_CT_Settings_stylePaneFormatFilter: // 92493; @@ -229,9 +427,7 @@ void SettingsTable::lcl_sprm(Sprm& rSprm) } break; case NS_ooxml::LN_CT_Settings_documentProtection: - { - resolveSprmProps(*this, rSprm); - } + resolveSprmProps(*this, rSprm); break; case NS_ooxml::LN_CT_Compat_usePrinterMetrics: m_pImpl->m_bUsePrinterMetrics = nIntValue; @@ -363,6 +559,11 @@ uno::Sequence<beans::PropertyValue> SettingsTable::GetCompatSettings() const return comphelper::containerToSequence(m_pImpl->m_aCompatSettings); } +css::uno::Sequence<css::beans::PropertyValue> SettingsTable::GetDocumentProtectionSettings() const +{ + return m_pImpl->m_DocumentProtection.toSequence(); +} + static bool lcl_isDefault(const uno::Reference<beans::XPropertyState>& xPropertyState, const OUString& rPropertyName) { return xPropertyState->getPropertyState(rPropertyName) == beans::PropertyState_DEFAULT_VALUE; diff --git a/writerfilter/source/dmapper/SettingsTable.hxx b/writerfilter/source/dmapper/SettingsTable.hxx index 648bf6d41b93..347de39c2cdf 100644 --- a/writerfilter/source/dmapper/SettingsTable.hxx +++ b/writerfilter/source/dmapper/SettingsTable.hxx @@ -77,6 +77,8 @@ class SettingsTable : public LoggedProperties, public LoggedTable css::uno::Sequence<css::beans::PropertyValue> GetCompatSettings() const; + css::uno::Sequence<css::beans::PropertyValue> GetDocumentProtectionSettings() const; + void ApplyProperties(css::uno::Reference<css::text::XTextDocument> const& xDoc); private: diff --git a/writerfilter/source/ooxml/factoryimpl.py b/writerfilter/source/ooxml/factoryimpl.py index 8584e196cb38..519936b7b42e 100644 --- a/writerfilter/source/ooxml/factoryimpl.py +++ b/writerfilter/source/ooxml/factoryimpl.py @@ -25,7 +25,7 @@ def createFastChildContextFromFactory(model): (OOXMLFastContextHandler* pHandler, OOXMLFactory_ns::Pointer_t pFactory, Token_t Element) { uno::Reference <xml::sax::XFastContextHandler> aResult; - Id nDefine = pHandler->getDefine(); + const Id nDefine = pHandler->getDefine(); if (pFactory.get() != NULL) { @@ -33,7 +33,7 @@ def createFastChildContextFromFactory(model): Id nElementId; if (pFactory->getElementId(nDefine, Element, nResource, nElementId)) { - Id nId = pFactory->getResourceId(nDefine, Element); + const Id nId = pFactory->getResourceId(nDefine, Element); switch (nResource) {""") @@ -118,7 +118,6 @@ public: std::string fastTokenToId(sal_uInt32 nToken) { - std::string sResult; #ifdef DEBUG_WRITERFILTER diff --git a/writerfilter/source/ooxml/factoryimpl_ns.py b/writerfilter/source/ooxml/factoryimpl_ns.py index d9baaa1c3613..a222af8e8fb0 100644 --- a/writerfilter/source/ooxml/factoryimpl_ns.py +++ b/writerfilter/source/ooxml/factoryimpl_ns.py @@ -235,7 +235,6 @@ def printValueData(values): output_else = "else " print(" else { return false; }") print(" return true;") - print(" break;") print(" }") @@ -258,7 +257,6 @@ def factoryGetListValue(nsNode): appendValueData(values, valueData, idToLabel(valueNode.getAttribute("tokenid"))) printValueData(values) print(" return false;") - print(" break;") print(""" default: break; @@ -376,7 +374,6 @@ def factoryCreateElementMap(files, nsNode): print(" default: return false;") print(" }") print(" return true;") - print(" break;") print(" default:") print(" switch (nId)") print(" {") @@ -384,10 +381,7 @@ def factoryCreateElementMap(files, nsNode): print(""" default: return false; } return true; - break; } - - return false; } """) diff --git a/writerfilter/source/ooxml/model.xml b/writerfilter/source/ooxml/model.xml index 1f5aa8285ecc..a96acdc05627 100644 --- a/writerfilter/source/ooxml/model.xml +++ b/writerfilter/source/ooxml/model.xml @@ -12117,6 +12117,9 @@ </attribute> <ref name="CT_MarkupRange"/> </define> + <define name="CT_MarkupRangePerm"> + <ref name="CT_MarkupRange"/> + </define> <define name="CT_MarkupRangeCommentStart"> <ref name="CT_Markup"/> </define> @@ -12157,6 +12160,24 @@ <ref name="ST_String"/> </attribute> </define> + <define name="CT_PermStart"> + <ref name="CT_MarkupRangePerm"/> + <attribute name="ed"> + <data type="string"/> + </attribute> + <attribute name="edGrp"> + <data type="string"/> + </attribute> + <attribute name="colFirst"> + <data type="ST_DecimalNumber"/> + </attribute> + <attribute name="colLast"> + <data type="ST_DecimalNumber"/> + </attribute> + </define> + <define name="CT_PermEnd"> + <ref name="CT_MarkupRangePerm"/> + </define> <define name="CT_TrackChangeNumbering"> <ref name="CT_TrackChange"/> <attribute name="original"> @@ -12245,6 +12266,12 @@ <element name="bookmarkEnd"> <ref name="CT_MarkupRangeBookmark"/> </element> + <element name="permStart"> + <ref name="CT_PermStart"/> + </element> + <element name="permEnd"> + <ref name="CT_PermEnd"/> + </element> <element name="moveFromRangeStart"> <ref name="CT_MoveBookmark"/> </element> @@ -13271,29 +13298,6 @@ <data type="string"/> </attribute> </define> - <define name="CT_Perm"> - <attribute name="id"> - <data type="string"/> - </attribute> - <attribute name="displacedByCustomXml"> - <data type="string"/> - </attribute> - </define> - <define name="CT_PermStart"> - <ref name="CT_Perm"/> - <attribute name="edGrp"> - <data type="string"/> - </attribute> - <attribute name="ed"> - <data type="string"/> - </attribute> - <attribute name="colFirst"> - <data type="string"/> - </attribute> - <attribute name="colLast"> - <data type="string"/> - </attribute> - </define> <define name="CT_Text"> <ref name="ST_String"/> <attribute name="xml:space"> @@ -16255,12 +16259,6 @@ <element name="proofErr"> <ref name="CT_ProofErr"/> </element> - <element name="permStart"> - <ref name="CT_PermStart"/> - </element> - <element name="permEnd"> - <ref name="CT_Perm"/> - </element> <ref name="EG_RangeMarkupElements"/> <element name="ins"> <ref name="CT_RunTrackChange"/> @@ -17326,6 +17324,18 @@ <resource name="CT_MarkupRangeBookmark" resource="Properties"> <attribute name="id" tokenid="ooxml:CT_MarkupRangeBookmark_id"/> </resource> + <resource name="CT_PermStart" resource="Properties"> + <attribute name="id" tokenid="ooxml:CT_PermStart_id"/> + <attribute name="colFirst" tokenid="ooxml:CT_PermStart_colFirst"/> + <attribute name="colLast" tokenid="ooxml:CT_PermStart_colLast"/> + <attribute name="ed" tokenid="ooxml:CT_PermStart_ed"/> + <attribute name="edGrp" tokenid="ooxml:CT_PermStart_edGrp"/> + <attribute name="displacedByCustomXml" tokenid="ooxml:CT_PermStart_displacedByCustomXml"/> + </resource> + <resource name="CT_PermEnd" resource="Properties"> + <attribute name="id" tokenid="ooxml:CT_PermEnd_id"/> + <attribute name="displacedByCustomXml" tokenid="ooxml:CT_PermEnd_displacedByCustomXml"/> + </resource> <resource name="CT_MarkupRangeCommentStart" resource="Properties"> <attribute name="id" tokenid="ooxml:EG_RangeMarkupElements_commentRangeStart"/> </resource> @@ -17388,6 +17398,8 @@ <resource name="EG_RangeMarkupElements" resource="Properties"> <element name="bookmarkStart" tokenid="ooxml:EG_RangeMarkupElements_bookmarkStart"/> <element name="bookmarkEnd" tokenid="ooxml:EG_RangeMarkupElements_bookmarkEnd"/> + <element name="permStart" tokenid="ooxml:EG_RangeMarkupElements_PermStart"/> + <element name="permEnd" tokenid="ooxml:EG_RangeMarkupElements_PermEnd"/> <element name="moveFromRangeStart" tokenid="ooxml:EG_RangeMarkupElements_moveFromRangeStart"/> <element name="moveFromRangeEnd" tokenid="ooxml:EG_RangeMarkupElements_moveFromRangeEnd"/> <element name="moveToRangeStart" tokenid="ooxml:EG_RangeMarkupElements_moveToRangeStart"/> _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits