You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
15 KiB
335 lines
15 KiB
10 years ago
|
From 5ae6072774ab544fdfdfebf2364e97ca2fa2326a Mon Sep 17 00:00:00 2001
|
||
|
From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= <caolanm@redhat.com>
|
||
|
Date: Mon, 3 Nov 2014 09:49:37 +0000
|
||
|
Subject: [PATCH] Resolves: fdo#68347 fix word count with recorded changes
|
||
|
|
||
|
also see fdo#46757
|
||
|
|
||
|
a) We need to ignore redline-deleted text, but count redline-added text
|
||
|
b) each block of text is denoted by its end position in the model
|
||
|
and where that maps to in the view so a hidden portion
|
||
|
should record its end point not its starting point, and a non-hidden
|
||
|
deleted portion should always record its end point
|
||
|
c) when mapping a model position to the view we take the offset of
|
||
|
the model pos arg from the block end and use that to offset the
|
||
|
mapped block-end view pos to get the final view pos. But for
|
||
|
hidden portions that won't make a whole lot of sense, and
|
||
|
end up offsetting into prior portions, so map all positions within a
|
||
|
hidden portion to the same block-end view pos
|
||
|
|
||
|
add regression tests for these cases
|
||
|
|
||
|
(cherry picked from commit fa430e6b4e6f5d096bdf59db26e5d7393ca2297b)
|
||
|
|
||
|
Conflicts:
|
||
|
sw/qa/core/uwriter.cxx
|
||
|
|
||
|
Change-Id: I45c76bba47fd430bc3bccb5f919502660d415d9e
|
||
|
Reviewed-on: https://gerrit.libreoffice.org/12219
|
||
|
Reviewed-by: Michael Stahl <mstahl@redhat.com>
|
||
|
Tested-by: Michael Stahl <mstahl@redhat.com>
|
||
|
---
|
||
|
sw/inc/modeltoviewhelper.hxx | 25 +++++++---
|
||
|
sw/qa/core/uwriter.cxx | 34 ++++++++++----
|
||
|
sw/source/core/text/porlay.cxx | 5 +-
|
||
|
sw/source/core/txtnode/modeltoviewhelper.cxx | 70 +++++++++++++++++-----------
|
||
|
sw/source/core/txtnode/txtedt.cxx | 2 +-
|
||
|
5 files changed, 93 insertions(+), 43 deletions(-)
|
||
|
|
||
|
diff --git a/sw/inc/modeltoviewhelper.hxx b/sw/inc/modeltoviewhelper.hxx
|
||
|
index 018b67c..d99c9db 100644
|
||
|
--- a/sw/inc/modeltoviewhelper.hxx
|
||
|
+++ b/sw/inc/modeltoviewhelper.hxx
|
||
|
@@ -65,19 +65,30 @@ class SwTxtNode;
|
||
|
#define EXPANDFIELDS 0x0001
|
||
|
#define EXPANDFOOTNOTE 0x0002
|
||
|
#define HIDEINVISIBLE 0x0004
|
||
|
-#define HIDEREDLINED 0x0008
|
||
|
+#define HIDEDELETIONS 0x0008
|
||
|
/// do not expand to content, but replace with ZWSP
|
||
|
#define REPLACEMODE 0x0010
|
||
|
|
||
|
class ModelToViewHelper
|
||
|
{
|
||
|
- /** For each field in the model string, there is an entry in the conversion
|
||
|
- map. The first value of the ConversionMapEntry points to the field
|
||
|
- position in the model string, the second value points to the associated
|
||
|
- position in the view string. The last entry in the conversion map
|
||
|
- denotes the lengths of the model resp. view string.
|
||
|
+ /** For each expanded/hidden portion in the model string, there is an entry in
|
||
|
+ the conversion map. The first value of the ConversionMapEntry points to
|
||
|
+ the start position in the model string, the second value points to the
|
||
|
+ associated start position in the view string. The last entry in the
|
||
|
+ conversion map denotes the lengths of the model resp. view string.
|
||
|
*/
|
||
|
- typedef std::pair< sal_Int32 , sal_Int32 > ConversionMapEntry;
|
||
|
+ struct ConversionMapEntry
|
||
|
+ {
|
||
|
+ ConversionMapEntry(sal_Int32 nModelPos, sal_Int32 nViewPos, bool bVisible)
|
||
|
+ : m_nModelPos(nModelPos)
|
||
|
+ , m_nViewPos(nViewPos)
|
||
|
+ , m_bVisible(bVisible)
|
||
|
+ {
|
||
|
+ }
|
||
|
+ sal_Int32 m_nModelPos;
|
||
|
+ sal_Int32 m_nViewPos;
|
||
|
+ bool m_bVisible;
|
||
|
+ };
|
||
|
typedef std::vector< ConversionMapEntry > ConversionMap;
|
||
|
typedef std::vector<sal_Int32> Positions;
|
||
|
|
||
|
diff --git a/sw/qa/core/uwriter.cxx b/sw/qa/core/uwriter.cxx
|
||
|
index bd6e000..1297c65 100644
|
||
|
--- a/sw/qa/core/uwriter.cxx
|
||
|
+++ b/sw/qa/core/uwriter.cxx
|
||
|
@@ -321,7 +321,7 @@ void SwDocTest::testModelToViewHelper()
|
||
|
}
|
||
|
|
||
|
{
|
||
|
- ModelToViewHelper aModelToViewHelper(*pTxtNode, HIDEREDLINED);
|
||
|
+ ModelToViewHelper aModelToViewHelper(*pTxtNode, HIDEDELETIONS);
|
||
|
OUString sViewText = aModelToViewHelper.getViewText();
|
||
|
CPPUNIT_ASSERT_EQUAL(
|
||
|
OUString("AAAABB " + OUString(CH_TXTATR_BREAKWORD) + " CCCCC " + OUString(CH_TXTATR_BREAKWORD) + " DDDDD"),
|
||
|
@@ -349,14 +349,14 @@ void SwDocTest::testModelToViewHelper()
|
||
|
}
|
||
|
|
||
|
{
|
||
|
- ModelToViewHelper aModelToViewHelper(*pTxtNode, EXPANDFIELDS | HIDEREDLINED | EXPANDFOOTNOTE);
|
||
|
+ ModelToViewHelper aModelToViewHelper(*pTxtNode, EXPANDFIELDS | HIDEDELETIONS | EXPANDFOOTNOTE);
|
||
|
OUString sViewText = aModelToViewHelper.getViewText();
|
||
|
CPPUNIT_ASSERT_EQUAL(
|
||
|
OUString("AAAABB foo CCCCC foo DDDDD"), sViewText);
|
||
|
}
|
||
|
{
|
||
|
ModelToViewHelper aModelToViewHelper(*pTxtNode,
|
||
|
- EXPANDFIELDS | HIDEREDLINED | EXPANDFOOTNOTE | REPLACEMODE);
|
||
|
+ EXPANDFIELDS | HIDEDELETIONS | EXPANDFOOTNOTE | REPLACEMODE);
|
||
|
OUString sViewText = aModelToViewHelper.getViewText();
|
||
|
CPPUNIT_ASSERT_EQUAL(
|
||
|
OUString("AAAABB " + OUString(CHAR_ZWSP) + " CCCCC " + OUString(CHAR_ZWSP) + " DDDDD"),
|
||
|
@@ -372,7 +372,7 @@ void SwDocTest::testModelToViewHelper()
|
||
|
}
|
||
|
|
||
|
{
|
||
|
- ModelToViewHelper aModelToViewHelper(*pTxtNode, HIDEINVISIBLE | HIDEREDLINED);
|
||
|
+ ModelToViewHelper aModelToViewHelper(*pTxtNode, HIDEINVISIBLE | HIDEDELETIONS);
|
||
|
OUString sViewText = aModelToViewHelper.getViewText();
|
||
|
OUStringBuffer aBuffer;
|
||
|
aBuffer.append("AAAACCCCC ");
|
||
|
@@ -382,13 +382,13 @@ void SwDocTest::testModelToViewHelper()
|
||
|
}
|
||
|
|
||
|
{
|
||
|
- ModelToViewHelper aModelToViewHelper(*pTxtNode, EXPANDFIELDS | HIDEINVISIBLE | HIDEREDLINED | EXPANDFOOTNOTE);
|
||
|
+ ModelToViewHelper aModelToViewHelper(*pTxtNode, EXPANDFIELDS | HIDEINVISIBLE | HIDEDELETIONS | EXPANDFOOTNOTE);
|
||
|
OUString sViewText = aModelToViewHelper.getViewText();
|
||
|
CPPUNIT_ASSERT_EQUAL(OUString("AAAACCCCC foo DDDDD"), sViewText);
|
||
|
}
|
||
|
{
|
||
|
ModelToViewHelper aModelToViewHelper(*pTxtNode,
|
||
|
- EXPANDFIELDS | HIDEINVISIBLE | HIDEREDLINED | EXPANDFOOTNOTE | REPLACEMODE);
|
||
|
+ EXPANDFIELDS | HIDEINVISIBLE | HIDEDELETIONS | EXPANDFOOTNOTE | REPLACEMODE);
|
||
|
OUString sViewText = aModelToViewHelper.getViewText();
|
||
|
CPPUNIT_ASSERT_EQUAL(sViewText,
|
||
|
OUString("AAAACCCCC " + OUString(CHAR_ZWSP) + " DDDDD"));
|
||
|
@@ -669,8 +669,26 @@ void SwDocTest::testSwScanner()
|
||
|
|
||
|
aDocStat.Reset();
|
||
|
pTxtNode->CountWords(aDocStat, 0, pTxtNode->Len()); //word-counting the text should only count the non-deleted text, and this whole chunk should be ignored
|
||
|
- CPPUNIT_ASSERT_EQUAL(aDocStat.nWord, static_cast<sal_uLong>(0));
|
||
|
- CPPUNIT_ASSERT_EQUAL(aDocStat.nChar, static_cast<sal_uLong>(0));
|
||
|
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uLong>(0), aDocStat.nWord);
|
||
|
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uLong>(0), aDocStat.nChar);
|
||
|
+
|
||
|
+ // https://bugs.libreoffice.org/show_bug.cgi?id=68347 we do want to count
|
||
|
+ // redline *added* text though
|
||
|
+ m_pDoc->SetRedlineMode(nsRedlineMode_t::REDLINE_ON | nsRedlineMode_t::REDLINE_SHOW_DELETE|nsRedlineMode_t::REDLINE_SHOW_INSERT);
|
||
|
+ aPaM.DeleteMark();
|
||
|
+ aPaM.GetPoint()->nContent.Assign(aPaM.GetCntntNode(), 0);
|
||
|
+ m_pDoc->InsertString(aPaM, "redline-new-text ");
|
||
|
+ aDocStat.Reset();
|
||
|
+ pTxtNode = aPaM.GetNode()->GetTxtNode();
|
||
|
+ pTxtNode->SetWordCountDirty(true);
|
||
|
+ pTxtNode->CountWords(aDocStat, 0, pTxtNode->Len());
|
||
|
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uLong>(2), aDocStat.nWord);
|
||
|
+ //redline-new-text Lorem ipsum
|
||
|
+ //+++++++++++++++++ ------
|
||
|
+ //select start of original text and part of deleted text
|
||
|
+ aDocStat.Reset();
|
||
|
+ pTxtNode->CountWords(aDocStat, 17, 25);
|
||
|
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uLong>(5), aDocStat.nChar);
|
||
|
}
|
||
|
|
||
|
//See https://bugs.libreoffice.org/show_bug.cgi?id=38983
|
||
|
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
|
||
|
index 40e3a58..214bbe5 100644
|
||
|
--- a/sw/source/core/text/porlay.cxx
|
||
|
+++ b/sw/source/core/text/porlay.cxx
|
||
|
@@ -2107,9 +2107,12 @@ void SwScriptInfo::selectRedLineDeleted(const SwTxtNode& rNode, MultiSelection &
|
||
|
{
|
||
|
const SwRangeRedline* pRed = rIDRA.GetRedlineTbl()[ nAct ];
|
||
|
|
||
|
- if ( pRed->Start()->nNode > rNode.GetIndex() )
|
||
|
+ if (pRed->Start()->nNode > rNode.GetIndex())
|
||
|
break;
|
||
|
|
||
|
+ if (pRed->GetType() != nsRedlineType_t::REDLINE_DELETE)
|
||
|
+ continue;
|
||
|
+
|
||
|
sal_Int32 nRedlStart;
|
||
|
sal_Int32 nRedlnEnd;
|
||
|
pRed->CalcStartEnd( rNode.GetIndex(), nRedlStart, nRedlnEnd );
|
||
|
diff --git a/sw/source/core/txtnode/modeltoviewhelper.cxx b/sw/source/core/txtnode/modeltoviewhelper.cxx
|
||
|
index bd1a3ae..875ea1e 100644
|
||
|
--- a/sw/source/core/txtnode/modeltoviewhelper.cxx
|
||
|
+++ b/sw/source/core/txtnode/modeltoviewhelper.cxx
|
||
|
@@ -94,7 +94,7 @@ ModelToViewHelper::ModelToViewHelper(const SwTxtNode &rNode, sal_uInt16 eMode)
|
||
|
if (eMode & HIDEINVISIBLE)
|
||
|
SwScriptInfo::selectHiddenTextProperty(rNode, aHiddenMulti);
|
||
|
|
||
|
- if (eMode & HIDEREDLINED)
|
||
|
+ if (eMode & HIDEDELETIONS)
|
||
|
SwScriptInfo::selectRedLineDeleted(rNode, aHiddenMulti);
|
||
|
|
||
|
std::vector<block> aBlocks;
|
||
|
@@ -203,43 +203,55 @@ ModelToViewHelper::ModelToViewHelper(const SwTxtNode &rNode, sal_uInt16 eMode)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
+ //store the end of each range in the model and where that end of range
|
||
|
+ //maps to in the view
|
||
|
sal_Int32 nOffset = 0;
|
||
|
for (std::vector<block>::iterator i = aBlocks.begin(); i != aBlocks.end(); ++i)
|
||
|
{
|
||
|
+ const sal_Int32 nBlockLen = i->m_nLen;
|
||
|
+ if (!nBlockLen)
|
||
|
+ continue;
|
||
|
+ const sal_Int32 nBlockStart = i->m_nStart;
|
||
|
+ const sal_Int32 nBlockEnd = nBlockStart + nBlockLen;
|
||
|
+
|
||
|
if (!i->m_bVisible)
|
||
|
{
|
||
|
- const sal_Int32 nHiddenStart = i->m_nStart;
|
||
|
- const sal_Int32 nHiddenLen = i->m_nLen;
|
||
|
+ sal_Int32 const modelBlockPos(nBlockEnd);
|
||
|
+ sal_Int32 const viewBlockPos(nBlockStart + nOffset);
|
||
|
+ m_aMap.push_back(ConversionMapEntry(modelBlockPos, viewBlockPos, false));
|
||
|
|
||
|
- m_aRetText = m_aRetText.replaceAt( nOffset + nHiddenStart, nHiddenLen, OUString() );
|
||
|
- m_aMap.push_back( ConversionMapEntry( nHiddenStart, nOffset + nHiddenStart ) );
|
||
|
- nOffset -= nHiddenLen;
|
||
|
+ m_aRetText = m_aRetText.replaceAt(nOffset + nBlockStart, nBlockLen, OUString());
|
||
|
+ nOffset -= nBlockLen;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (FieldResultSet::iterator j = i->m_aAttrs.begin(); j != i->m_aAttrs.end(); ++j)
|
||
|
{
|
||
|
- sal_Int32 const viewPos(nOffset + j->m_nFieldPos);
|
||
|
- m_aRetText = m_aRetText.replaceAt(viewPos, 1, j->m_sExpand);
|
||
|
- m_aMap.push_back( ConversionMapEntry(j->m_nFieldPos, viewPos) );
|
||
|
+ sal_Int32 const modelFieldPos(j->m_nFieldPos);
|
||
|
+ sal_Int32 const viewFieldPos(j->m_nFieldPos + nOffset);
|
||
|
+ m_aMap.push_back( ConversionMapEntry(modelFieldPos, viewFieldPos, true) );
|
||
|
+
|
||
|
+ m_aRetText = m_aRetText.replaceAt(viewFieldPos, 1, j->m_sExpand);
|
||
|
+ nOffset += j->m_sExpand.getLength() - 1;
|
||
|
+
|
||
|
switch (j->m_eType)
|
||
|
{
|
||
|
case FieldResult::FIELD:
|
||
|
- m_FieldPositions.push_back(viewPos);
|
||
|
+ m_FieldPositions.push_back(viewFieldPos);
|
||
|
break;
|
||
|
case FieldResult::FOOTNOTE:
|
||
|
- m_FootnotePositions.push_back(viewPos);
|
||
|
+ m_FootnotePositions.push_back(viewFieldPos);
|
||
|
break;
|
||
|
case FieldResult::NONE: /*ignore*/
|
||
|
break;
|
||
|
}
|
||
|
- nOffset += j->m_sExpand.getLength() - 1;
|
||
|
}
|
||
|
+
|
||
|
+ sal_Int32 const modelEndBlock(nBlockEnd);
|
||
|
+ sal_Int32 const viewFieldPos(nBlockEnd + nOffset);
|
||
|
+ m_aMap.push_back(ConversionMapEntry(modelEndBlock, viewFieldPos, true));
|
||
|
}
|
||
|
}
|
||
|
-
|
||
|
- if ( !m_aMap.empty() )
|
||
|
- m_aMap.push_back( ConversionMapEntry( rNodeText.getLength()+1, m_aRetText.getLength()+1 ) );
|
||
|
}
|
||
|
|
||
|
/** Converts a model position into a view position
|
||
|
@@ -248,15 +260,21 @@ sal_Int32 ModelToViewHelper::ConvertToViewPosition( sal_Int32 nModelPos ) const
|
||
|
{
|
||
|
// Search for entry after nPos:
|
||
|
ConversionMap::const_iterator aIter;
|
||
|
+
|
||
|
for ( aIter = m_aMap.begin(); aIter != m_aMap.end(); ++aIter )
|
||
|
{
|
||
|
- if ( (*aIter).first >= nModelPos )
|
||
|
+ if (aIter->m_nModelPos >= nModelPos)
|
||
|
{
|
||
|
- const sal_Int32 nPosModel = (*aIter).first;
|
||
|
- const sal_Int32 nPosExpand = (*aIter).second;
|
||
|
-
|
||
|
- const sal_Int32 nDistToNextModel = nPosModel - nModelPos;
|
||
|
- return nPosExpand - nDistToNextModel;
|
||
|
+ //if it's an invisible portion, map all contained positions
|
||
|
+ //to the anchor viewpos
|
||
|
+ if (!aIter->m_bVisible)
|
||
|
+ return aIter->m_nViewPos;
|
||
|
+
|
||
|
+ //if it's a visible portion, then the view position is the anchor
|
||
|
+ //viewpos - the offset of the input modelpos from the anchor
|
||
|
+ //modelpos
|
||
|
+ const sal_Int32 nOffsetFromEnd = aIter->m_nModelPos - nModelPos;
|
||
|
+ return aIter->m_nViewPos - nOffsetFromEnd;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@@ -274,10 +292,10 @@ ModelToViewHelper::ModelPosition ModelToViewHelper::ConvertToModelPosition( sal_
|
||
|
ConversionMap::const_iterator aIter;
|
||
|
for ( aIter = m_aMap.begin(); aIter != m_aMap.end(); ++aIter )
|
||
|
{
|
||
|
- if ( (*aIter).second > nViewPos )
|
||
|
+ if (aIter->m_nViewPos > nViewPos)
|
||
|
{
|
||
|
- const sal_Int32 nPosModel = (*aIter).first;
|
||
|
- const sal_Int32 nPosExpand = (*aIter).second;
|
||
|
+ const sal_Int32 nPosModel = aIter->m_nModelPos;
|
||
|
+ const sal_Int32 nPosExpand = aIter->m_nViewPos;
|
||
|
|
||
|
// If nViewPos is in front of first field, we are finished.
|
||
|
if ( aIter == m_aMap.begin() )
|
||
|
@@ -286,8 +304,8 @@ ModelToViewHelper::ModelPosition ModelToViewHelper::ConvertToModelPosition( sal_
|
||
|
--aIter;
|
||
|
|
||
|
// nPrevPosModel is the field position
|
||
|
- const sal_Int32 nPrevPosModel = (*aIter).first;
|
||
|
- const sal_Int32 nPrevPosExpand = (*aIter).second;
|
||
|
+ const sal_Int32 nPrevPosModel = aIter->m_nModelPos;
|
||
|
+ const sal_Int32 nPrevPosExpand = aIter->m_nViewPos;
|
||
|
|
||
|
const sal_Int32 nLengthModel = nPosModel - nPrevPosModel;
|
||
|
const sal_Int32 nLengthExpand = nPosExpand - nPrevPosExpand;
|
||
|
diff --git a/sw/source/core/txtnode/txtedt.cxx b/sw/source/core/txtnode/txtedt.cxx
|
||
|
index aa6f7d6..f9f19e2 100644
|
||
|
--- a/sw/source/core/txtnode/txtedt.cxx
|
||
|
+++ b/sw/source/core/txtnode/txtedt.cxx
|
||
|
@@ -1986,7 +1986,7 @@ bool SwTxtNode::CountWords( SwDocStat& rStat,
|
||
|
}
|
||
|
|
||
|
// ConversionMap to expand fields, remove invisible and redline deleted text for scanner
|
||
|
- const ModelToViewHelper aConversionMap(*this, EXPANDFIELDS | EXPANDFOOTNOTE | HIDEINVISIBLE | HIDEREDLINED);
|
||
|
+ const ModelToViewHelper aConversionMap(*this, EXPANDFIELDS | EXPANDFOOTNOTE | HIDEINVISIBLE | HIDEDELETIONS);
|
||
|
OUString aExpandText = aConversionMap.getViewText();
|
||
|
|
||
|
if (aExpandText.isEmpty() && !bCountNumbering)
|
||
|
--
|
||
|
1.9.3
|
||
|
|