diff options
author | Mike Kaganski <mike.kaganski@collabora.com> | 2021-04-13 09:06:52 +0300 |
---|---|---|
committer | Mike Kaganski <mike.kaganski@collabora.com> | 2021-04-16 18:41:21 +0200 |
commit | e61caf4d2719ebf5f696df39c41497a452c9d606 (patch) | |
tree | 5b31b950aa374cf127ba3ead4c91ead729e7611c /sw/source/filter | |
parent | ffc5d0fe1ab2453d267c6474c50f3b978df60a8b (diff) |
tdf#122222: add DOCX export of resolved comments as "done"
Since implementation of tdf#119228, Writer comments may have
"Resolved" state, which is the equivalent of Word's internal
"done" flag.
This relies on [MS-DOCX] extensions available since Word 2013.
DOCX import will be implemented in a follow-up commit.
[MS-DOCX]: https://docs.microsoft.com/en-us/openspecs/office_standards/ms-docx
Change-Id: I3be1e8a096bdec41c8268974fe81328480eb0704
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114023
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Diffstat (limited to 'sw/source/filter')
-rw-r--r-- | sw/source/filter/ww8/attributeoutputbase.hxx | 2 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxattributeoutput.cxx | 64 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxattributeoutput.hxx | 17 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxexport.cxx | 30 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxexport.hxx | 2 | ||||
-rw-r--r-- | sw/source/filter/ww8/rtfattributeoutput.cxx | 4 | ||||
-rw-r--r-- | sw/source/filter/ww8/rtfattributeoutput.hxx | 3 | ||||
-rw-r--r-- | sw/source/filter/ww8/wrtw8nds.cxx | 2 | ||||
-rw-r--r-- | sw/source/filter/ww8/ww8attributeoutput.hxx | 2 |
9 files changed, 103 insertions, 23 deletions
diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx b/sw/source/filter/ww8/attributeoutputbase.hxx index a34848e6df7e..34920374537c 100644 --- a/sw/source/filter/ww8/attributeoutputbase.hxx +++ b/sw/source/filter/ww8/attributeoutputbase.hxx @@ -153,7 +153,7 @@ public: virtual void RTLAndCJKState( bool bIsRTL, sal_uInt16 nScript ) = 0; /// Start of the paragraph. - virtual void StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo ) = 0; + virtual sal_Int32 StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, bool bGenerateParaId ) = 0; /// End of the paragraph. virtual void EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner ) = 0; diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index e3ad3f917942..7850fca4a6a2 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -90,6 +90,7 @@ #include <editeng/editobj.hxx> #include <editeng/keepitem.hxx> #include <editeng/borderline.hxx> +#include <sax/tools/converter.hxx> #include <svx/xdef.hxx> #include <svx/xfillit0.hxx> #include <svx/xflclit.hxx> @@ -290,6 +291,14 @@ class FieldMarkParamsHelper } }; +// [ISO/IEC29500-1:2016] 17.18.50 ST_LongHexNumber (Eight Digit Hexadecimal Value) +OUString NumberToHexBinary(sal_Int32 n) +{ + OUStringBuffer aBuf; + sax::Converter::convertNumberToHexBinary(aBuf, n); + return aBuf.makeStringAndClear(); +} + } void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ ) @@ -380,7 +389,8 @@ static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutpu } } -void DocxAttributeOutput::StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo ) +sal_Int32 DocxAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, + bool bGenerateParaId) { // Paragraphs (in headers/footers/comments/frames etc) can start before another finishes. // So a stack is needed to keep track of each paragraph's status separately. @@ -475,7 +485,14 @@ void DocxAttributeOutput::StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pText // We will only know if we have to do that later. m_pSerializer->mark(Tag_StartParagraph_1); - m_pSerializer->startElementNS(XML_w, XML_p); + std::optional<OUString> aParaId; + sal_Int32 nParaId = 0; + if (bGenerateParaId) + { + nParaId = m_nNextParaId++; + aParaId = NumberToHexBinary(nParaId); + } + m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId); // postpone the output of the run (we get it before the paragraph // properties, but must write it after them) @@ -486,6 +503,8 @@ void DocxAttributeOutput::StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pText m_bParagraphOpened = true; m_bIsFirstParagraph = false; + + return nParaId; } static OString convertToOOXMLVertOrient(sal_Int16 nOrient) @@ -6191,7 +6210,7 @@ void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj) sal_Int32 nCurrentPos = 0; sal_Int32 nEnd = aStr.getLength(); - StartParagraph(ww8::WW8TableNodeInfo::Pointer_t()); + StartParagraph(ww8::WW8TableNodeInfo::Pointer_t(), false); // Write paragraph properties. StartParagraphProperties(); @@ -7971,14 +7990,14 @@ void DocxAttributeOutput::PostitField( const SwField* pField ) else // Otherwise get a new one. nId = m_nNextAnnotationMarkId++; - m_postitFields.emplace_back(pPostItField, nId); + m_postitFields.emplace_back(pPostItField, PostItDOCXData{ nId }); } void DocxAttributeOutput::WritePostitFieldReference() { while( m_postitFieldsMaxId < m_postitFields.size()) { - OString idstr = OString::number(m_postitFields[m_postitFieldsMaxId].second); + OString idstr = OString::number(m_postitFields[m_postitFieldsMaxId].second.id); // In case this file is inside annotation marks, we want to write the // comment reference after the annotation mark is closed, not here. @@ -7990,27 +8009,37 @@ void DocxAttributeOutput::WritePostitFieldReference() } } -void DocxAttributeOutput::WritePostitFields() +DocxAttributeOutput::hasResolved DocxAttributeOutput::WritePostitFields() { - for (const auto& rPair : m_postitFields) + hasResolved eResult = hasResolved::no; + for (auto& [f, data] : m_postitFields) { - OString idstr = OString::number( rPair.second); - const SwPostItField* f = rPair.first; + OString idstr = OString::number(data.id); m_pSerializer->startElementNS( XML_w, XML_comment, FSNS( XML_w, XML_id ), idstr, FSNS( XML_w, XML_author ), f->GetPar1(), FSNS( XML_w, XML_date ), DateTimeToOString(f->GetDateTime()), FSNS( XML_w, XML_initials ), f->GetInitials() ); + const bool bNeedParaId = f->GetResolved(); + if (bNeedParaId) + eResult = hasResolved::yes; + if (f->GetTextObject() != nullptr) { // richtext - GetExport().WriteOutliner(*f->GetTextObject(), TXT_ATN); + data.lastParaId = GetExport().WriteOutliner(*f->GetTextObject(), TXT_ATN, bNeedParaId); } else { // just plain text - eg. when the field was created via the // .uno:InsertAnnotation API - m_pSerializer->startElementNS(XML_w, XML_p); + std::optional<OUString> aParaId; + if (bNeedParaId) + { + data.lastParaId = m_nNextParaId++; + aParaId = NumberToHexBinary(data.lastParaId); + } + m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId); m_pSerializer->startElementNS(XML_w, XML_r); RunText(f->GetText()); m_pSerializer->endElementNS(XML_w, XML_r); @@ -8019,6 +8048,19 @@ void DocxAttributeOutput::WritePostitFields() m_pSerializer->endElementNS( XML_w, XML_comment ); } + return eResult; +} + +void DocxAttributeOutput::WritePostItFieldsResolved() +{ + for (auto& [f, data] : m_postitFields) + { + if (!f->GetResolved()) + continue; + OUString idstr = NumberToHexBinary(data.lastParaId); + m_pSerializer->singleElementNS(XML_w15, XML_commentEx, FSNS(XML_w15, XML_paraId), idstr, + FSNS(XML_w15, XML_done), "1"); + } } bool DocxAttributeOutput::DropdownField( const SwField* pField ) diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index 8910c7eabf49..b0688183ab69 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -129,7 +129,8 @@ public: virtual void RTLAndCJKState( bool bIsRTL, sal_uInt16 nScript ) override; /// Start of the paragraph. - virtual void StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo ) override; + virtual sal_Int32 StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, + bool bGenerateParaId) override; /// End of the paragraph. virtual void EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner ) override; @@ -796,6 +797,9 @@ private: sal_Int32 m_nNextBookmarkId; sal_Int32 m_nNextAnnotationMarkId; + /// [MS-DOCX] section 2.6.2.3 + sal_Int32 m_nNextParaId = 1; // MUST be greater than 0 + OUString m_sRawText; /// Bookmarks to output @@ -929,8 +933,13 @@ private: std::vector<const SdrObject*> m_aPostponedFormControls; std::vector<PostponedDrawing> m_aPostponedActiveXControls; const SwField* pendingPlaceholder; + + struct PostItDOCXData{ + sal_Int32 id; + sal_Int32 lastParaId = 0; // [MS-DOCX] 2.5.3.1 CT_CommentEx needs paraId attribute + }; /// Maps postit fields to ID's, used in commentRangeStart/End, commentReference and comment.xml. - std::vector< std::pair<const SwPostItField*, sal_Int32> > m_postitFields; + std::vector<std::pair<const SwPostItField*, PostItDOCXData>> m_postitFields; /// Number of postit fields which already have a commentReference written. unsigned int m_postitFieldsMaxId; int m_anchorId; @@ -1021,7 +1030,9 @@ public: static void WriteFootnoteEndnotePr( ::sax_fastparser::FSHelperPtr const & fs, int tag, const SwEndNoteInfo& info, int listtag ); bool HasPostitFields() const; - void WritePostitFields(); + enum class hasResolved { no, yes }; + hasResolved WritePostitFields(); + void WritePostItFieldsResolved(); /// VMLTextExport virtual void WriteOutliner(const OutlinerParaObject& rParaObj) override; diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx index 23e67cb66536..c3e5adc49331 100644 --- a/sw/source/filter/ww8/docxexport.cxx +++ b/sw/source/filter/ww8/docxexport.cxx @@ -736,9 +736,29 @@ void DocxExport::WritePostitFields() pPostitFS->startElementNS( XML_w, XML_comments, MainXmlNamespaces()); m_pAttrOutput->SetSerializer( pPostitFS ); - m_pAttrOutput->WritePostitFields(); + const auto eHasResolved = m_pAttrOutput->WritePostitFields(); m_pAttrOutput->SetSerializer( m_pDocumentFS ); pPostitFS->endElementNS( XML_w, XML_comments ); + + if (eHasResolved != DocxAttributeOutput::hasResolved::yes) + return; + + m_rFilter.addRelation(m_pDocumentFS->getOutputStream(), + oox::getRelationship(Relationship::COMMENTSEXTENDED), + "commentsExtended.xml"); + + pPostitFS = m_rFilter.openFragmentStreamWithSerializer( + "word/commentsExtended.xml", + "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"); + + pPostitFS->startElementNS(XML_w15, XML_commentsEx, // Add namespaces manually now + FSNS(XML_xmlns, XML_mc), m_rFilter.getNamespaceURL(OOX_NS(mce)), + FSNS(XML_xmlns, XML_w15), m_rFilter.getNamespaceURL(OOX_NS(w15)), + FSNS(XML_mc, XML_Ignorable), "w15"); + m_pAttrOutput->SetSerializer(pPostitFS); + m_pAttrOutput->WritePostItFieldsResolved(); + m_pAttrOutput->SetSerializer(m_pDocumentFS); + pPostitFS->endElementNS(XML_w15, XML_commentsEx); } void DocxExport::WriteNumbering() @@ -1709,18 +1729,21 @@ bool DocxExport::ignoreAttributeForStyleDefaults( sal_uInt16 nWhich ) const return MSWordExportBase::ignoreAttributeForStyleDefaults( nWhich ); } -void DocxExport::WriteOutliner(const OutlinerParaObject& rParaObj, sal_uInt8 nTyp) +sal_Int32 DocxExport::WriteOutliner(const OutlinerParaObject& rParaObj, sal_uInt8 nTyp, + bool bNeedsLastParaId) { const EditTextObject& rEditObj = rParaObj.GetTextObject(); MSWord_SdrAttrIter aAttrIter( *this, rEditObj, nTyp ); sal_Int32 nPara = rEditObj.GetParagraphCount(); + sal_Int32 nParaId = 0; for( sal_Int32 n = 0; n < nPara; ++n ) { if( n ) aAttrIter.NextPara( n ); - AttrOutput().StartParagraph( ww8::WW8TableNodeInfo::Pointer_t()); + nParaId = AttrOutput().StartParagraph(ww8::WW8TableNodeInfo::Pointer_t(), + bNeedsLastParaId && n == nPara - 1); rtl_TextEncoding eChrSet = aAttrIter.GetNodeCharSet(); OUString aStr( rEditObj.GetText( n )); sal_Int32 nCurrentPos = 0; @@ -1755,6 +1778,7 @@ void DocxExport::WriteOutliner(const OutlinerParaObject& rParaObj, sal_uInt8 nTy // aAttrIter.OutParaAttr(false); AttrOutput().EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t()); } + return nParaId; } void DocxExport::SetFS( ::sax_fastparser::FSHelperPtr const & pFS ) diff --git a/sw/source/filter/ww8/docxexport.hxx b/sw/source/filter/ww8/docxexport.hxx index f599764a45e6..08c5372460fd 100644 --- a/sw/source/filter/ww8/docxexport.hxx +++ b/sw/source/filter/ww8/docxexport.hxx @@ -189,7 +189,7 @@ public: /// Writes the shape using drawingML syntax. void OutputDML( css::uno::Reference< css::drawing::XShape > const & xShape ); - void WriteOutliner(const OutlinerParaObject& rOutliner, sal_uInt8 nTyp); + sal_Int32 WriteOutliner(const OutlinerParaObject& rOutliner, sal_uInt8 nTyp, bool bNeedsLastParaId); virtual ExportFormat GetExportFormat() const override { return ExportFormat::DOCX; } diff --git a/sw/source/filter/ww8/rtfattributeoutput.cxx b/sw/source/filter/ww8/rtfattributeoutput.cxx index 35f30f67aff0..1407f3ef0025 100644 --- a/sw/source/filter/ww8/rtfattributeoutput.cxx +++ b/sw/source/filter/ww8/rtfattributeoutput.cxx @@ -211,7 +211,8 @@ void RtfAttributeOutput::RTLAndCJKState(bool bIsRTL, sal_uInt16 nScript) m_bControlLtrRtl = true; } -void RtfAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo) +sal_Int32 RtfAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, + bool /*bGenerateParaId*/) { if (m_bIsBeforeFirstParagraph && m_rExport.m_nTextTyp != TXT_HDFT) m_bIsBeforeFirstParagraph = false; @@ -267,6 +268,7 @@ void RtfAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNo } OSL_ENSURE(m_aRun.getLength() == 0, "m_aRun is not empty"); + return 0; } void RtfAttributeOutput::EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner) diff --git a/sw/source/filter/ww8/rtfattributeoutput.hxx b/sw/source/filter/ww8/rtfattributeoutput.hxx index ae44869ea2c8..c3e20ddca49b 100644 --- a/sw/source/filter/ww8/rtfattributeoutput.hxx +++ b/sw/source/filter/ww8/rtfattributeoutput.hxx @@ -48,7 +48,8 @@ public: void RTLAndCJKState(bool bIsRTL, sal_uInt16 nScript) override; /// Start of the paragraph. - void StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo) override; + sal_Int32 StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, + bool bGenerateParaId) override; /// End of the paragraph. void EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner) override; diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx index 9dcc1fdacd0e..c278d57588ec 100644 --- a/sw/source/filter/ww8/wrtw8nds.cxx +++ b/sw/source/filter/ww8/wrtw8nds.cxx @@ -2313,7 +2313,7 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode ) ++aBreakIt; } - AttrOutput().StartParagraph( pTextNodeInfo ); + AttrOutput().StartParagraph(pTextNodeInfo, false); const SwSection* pTOXSect = nullptr; if( m_bInWriteTOX ) diff --git a/sw/source/filter/ww8/ww8attributeoutput.hxx b/sw/source/filter/ww8/ww8attributeoutput.hxx index b748abb6e1da..f601cd361303 100644 --- a/sw/source/filter/ww8/ww8attributeoutput.hxx +++ b/sw/source/filter/ww8/ww8attributeoutput.hxx @@ -32,7 +32,7 @@ public: virtual void RTLAndCJKState( bool bIsRTL, sal_uInt16 nScript ) override; /// Start of the paragraph. - virtual void StartParagraph( ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/ ) override {} + virtual sal_Int32 StartParagraph( ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, bool /*bGenerateParaId*/ ) override { return 0; } /// End of the paragraph. virtual void EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner ) override; |