summaryrefslogtreecommitdiff
path: root/sw
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2021-04-13 09:06:52 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2021-04-16 18:41:21 +0200
commite61caf4d2719ebf5f696df39c41497a452c9d606 (patch)
tree5b31b950aa374cf127ba3ead4c91ead729e7611c /sw
parentffc5d0fe1ab2453d267c6474c50f3b978df60a8b (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')
-rw-r--r--sw/qa/extras/ooxmlexport/data/CommentDone.docxbin0 -> 20946 bytes
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport16.cxx24
-rw-r--r--sw/qa/unit/swmodeltestbase.cxx2
-rw-r--r--sw/source/filter/ww8/attributeoutputbase.hxx2
-rw-r--r--sw/source/filter/ww8/docxattributeoutput.cxx64
-rw-r--r--sw/source/filter/ww8/docxattributeoutput.hxx17
-rw-r--r--sw/source/filter/ww8/docxexport.cxx30
-rw-r--r--sw/source/filter/ww8/docxexport.hxx2
-rw-r--r--sw/source/filter/ww8/rtfattributeoutput.cxx4
-rw-r--r--sw/source/filter/ww8/rtfattributeoutput.hxx3
-rw-r--r--sw/source/filter/ww8/wrtw8nds.cxx2
-rw-r--r--sw/source/filter/ww8/ww8attributeoutput.hxx2
12 files changed, 129 insertions, 23 deletions
diff --git a/sw/qa/extras/ooxmlexport/data/CommentDone.docx b/sw/qa/extras/ooxmlexport/data/CommentDone.docx
new file mode 100644
index 000000000000..1ce5993d440b
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/CommentDone.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx
index ee982194ff19..1c4bb106a0ec 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx
@@ -23,6 +23,7 @@
#include <com/sun/star/text/XTextTablesSupplier.hpp>
#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
#include <comphelper/configuration.hxx>
+#include <comphelper/propertysequence.hxx>
#include <editeng/escapementitem.hxx>
#include <unotools/fltrcfg.hxx>
@@ -311,6 +312,29 @@ DECLARE_OOXMLEXPORT_TEST(testTdf140137, "tdf140137.docx")
// Don't throw exception during load
}
+DECLARE_OOXMLEXPORT_TEST(testCommentDone, "CommentDone.docx")
+{
+ if (!mbExported)
+ {
+ // This manually toggles (enables) the resolved state of the first comment now, while
+ // import is not yet implemented (TODO)
+ uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
+ { { "Id", uno::makeAny(OUString("1")) } });
+ dispatchCommand(mxComponent, ".uno:ResolveComment", aPropertyValues);
+ return;
+ }
+
+ xmlDocUniquePtr pXmlComm = parseExport("word/comments.xml");
+ assertXPath(pXmlComm, "/w:comments/w:comment[1]/w:p", 2);
+ OUString idLastPara = getXPath(pXmlComm, "/w:comments/w:comment[1]/w:p[2]", "paraId");
+ xmlDocUniquePtr pXmlCommExt = parseExport("word/commentsExtended.xml");
+ assertXPath(pXmlCommExt, "/w15:commentsEx", "Ignorable", "w15");
+ assertXPath(pXmlCommExt, "/w15:commentsEx/w15:commentEx", 1);
+ OUString idLastParaEx = getXPath(pXmlCommExt, "/w15:commentsEx/w15:commentEx", "paraId");
+ CPPUNIT_ASSERT_EQUAL(idLastPara, idLastParaEx);
+ assertXPath(pXmlCommExt, "/w15:commentsEx/w15:commentEx", "done", "1");
+}
+
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/qa/unit/swmodeltestbase.cxx b/sw/qa/unit/swmodeltestbase.cxx
index 60cd329fbbc9..632441524eb3 100644
--- a/sw/qa/unit/swmodeltestbase.cxx
+++ b/sw/qa/unit/swmodeltestbase.cxx
@@ -722,6 +722,8 @@ void SwModelTestBase::registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx)
BAD_CAST("http://schemas.openxmlformats.org/package/2006/relationships"));
xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("w14"),
BAD_CAST("http://schemas.microsoft.com/office/word/2010/wordml"));
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("w15"),
+ BAD_CAST("http://schemas.microsoft.com/office/word/2012/wordml"));
xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("m"),
BAD_CAST("http://schemas.openxmlformats.org/officeDocument/2006/math"));
xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("ContentType"),
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;