Kaydet (Commit) fa430e6b authored tarafından Caolán McNamara's avatar Caolán McNamara

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

Change-Id: I45c76bba47fd430bc3bccb5f919502660d415d9e
üst 79fcf0b4
...@@ -65,19 +65,30 @@ class SwTxtNode; ...@@ -65,19 +65,30 @@ class SwTxtNode;
#define EXPANDFIELDS 0x0001 #define EXPANDFIELDS 0x0001
#define EXPANDFOOTNOTE 0x0002 #define EXPANDFOOTNOTE 0x0002
#define HIDEINVISIBLE 0x0004 #define HIDEINVISIBLE 0x0004
#define HIDEREDLINED 0x0008 #define HIDEDELETIONS 0x0008
/// do not expand to content, but replace with ZWSP /// do not expand to content, but replace with ZWSP
#define REPLACEMODE 0x0010 #define REPLACEMODE 0x0010
class ModelToViewHelper class ModelToViewHelper
{ {
/** For each field in the model string, there is an entry in the conversion /** For each expanded/hidden portion in the model string, there is an entry in
map. The first value of the ConversionMapEntry points to the field the conversion map. The first value of the ConversionMapEntry points to
position in the model string, the second value points to the associated the start position in the model string, the second value points to the
position in the view string. The last entry in the conversion map associated start position in the view string. The last entry in the
denotes the lengths of the model resp. view string. 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< ConversionMapEntry > ConversionMap;
typedef std::vector<sal_Int32> Positions; typedef std::vector<sal_Int32> Positions;
......
...@@ -406,7 +406,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideRedlined() ...@@ -406,7 +406,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideRedlined()
{ {
SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc); SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc);
ModelToViewHelper aModelToViewHelper(*pTxtNode, HIDEREDLINED); ModelToViewHelper aModelToViewHelper(*pTxtNode, HIDEDELETIONS);
OUString sViewText = aModelToViewHelper.getViewText(); OUString sViewText = aModelToViewHelper.getViewText();
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
OUString("AAAABB " + OUString(CH_TXTATR_BREAKWORD) + " CCCCC " + OUString(CH_TXTATR_BREAKWORD) + " DDDDD"), OUString("AAAABB " + OUString(CH_TXTATR_BREAKWORD) + " CCCCC " + OUString(CH_TXTATR_BREAKWORD) + " DDDDD"),
...@@ -444,7 +444,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideHideRedlinedExpandFootnote( ...@@ -444,7 +444,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideHideRedlinedExpandFootnote(
{ {
SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc); SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc);
ModelToViewHelper aModelToViewHelper(*pTxtNode, EXPANDFIELDS | HIDEREDLINED | EXPANDFOOTNOTE); ModelToViewHelper aModelToViewHelper(*pTxtNode, EXPANDFIELDS | HIDEDELETIONS | EXPANDFOOTNOTE);
OUString sViewText = aModelToViewHelper.getViewText(); OUString sViewText = aModelToViewHelper.getViewText();
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
OUString("AAAABB foo CCCCC foo DDDDD"), sViewText); OUString("AAAABB foo CCCCC foo DDDDD"), sViewText);
...@@ -455,7 +455,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideHideRedlinedExpandFootnoteR ...@@ -455,7 +455,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideHideRedlinedExpandFootnoteR
SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc); SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc);
ModelToViewHelper aModelToViewHelper(*pTxtNode, ModelToViewHelper aModelToViewHelper(*pTxtNode,
EXPANDFIELDS | HIDEREDLINED | EXPANDFOOTNOTE | REPLACEMODE); EXPANDFIELDS | HIDEDELETIONS | EXPANDFOOTNOTE | REPLACEMODE);
OUString sViewText = aModelToViewHelper.getViewText(); OUString sViewText = aModelToViewHelper.getViewText();
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
OUString("AAAABB " + OUString(CHAR_ZWSP) + " CCCCC " + OUString(CHAR_ZWSP) + " DDDDD"), OUString("AAAABB " + OUString(CHAR_ZWSP) + " CCCCC " + OUString(CHAR_ZWSP) + " DDDDD"),
...@@ -474,7 +474,7 @@ void SwDocTest::testModelToViewHelperHideInvisibleHideRedlined() ...@@ -474,7 +474,7 @@ void SwDocTest::testModelToViewHelperHideInvisibleHideRedlined()
{ {
SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc); SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc);
ModelToViewHelper aModelToViewHelper(*pTxtNode, HIDEINVISIBLE | HIDEREDLINED); ModelToViewHelper aModelToViewHelper(*pTxtNode, HIDEINVISIBLE | HIDEDELETIONS);
OUString sViewText = aModelToViewHelper.getViewText(); OUString sViewText = aModelToViewHelper.getViewText();
OUStringBuffer aBuffer; OUStringBuffer aBuffer;
aBuffer.append("AAAACCCCC "); aBuffer.append("AAAACCCCC ");
...@@ -487,7 +487,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideInvisibleHideRedlinedExpand ...@@ -487,7 +487,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideInvisibleHideRedlinedExpand
{ {
SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc); SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc);
ModelToViewHelper aModelToViewHelper(*pTxtNode, EXPANDFIELDS | HIDEINVISIBLE | HIDEREDLINED | EXPANDFOOTNOTE); ModelToViewHelper aModelToViewHelper(*pTxtNode, EXPANDFIELDS | HIDEINVISIBLE | HIDEDELETIONS | EXPANDFOOTNOTE);
OUString sViewText = aModelToViewHelper.getViewText(); OUString sViewText = aModelToViewHelper.getViewText();
CPPUNIT_ASSERT_EQUAL(OUString("AAAACCCCC foo DDDDD"), sViewText); CPPUNIT_ASSERT_EQUAL(OUString("AAAACCCCC foo DDDDD"), sViewText);
} }
...@@ -497,7 +497,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideInvisibleHideRedlinedExpand ...@@ -497,7 +497,7 @@ void SwDocTest::testModelToViewHelperExpandFieldsHideInvisibleHideRedlinedExpand
SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc); SwTxtNode* pTxtNode = getModelToViewTestDocument(m_pDoc);
ModelToViewHelper aModelToViewHelper(*pTxtNode, ModelToViewHelper aModelToViewHelper(*pTxtNode,
EXPANDFIELDS | HIDEINVISIBLE | HIDEREDLINED | EXPANDFOOTNOTE | REPLACEMODE); EXPANDFIELDS | HIDEINVISIBLE | HIDEDELETIONS | EXPANDFOOTNOTE | REPLACEMODE);
OUString sViewText = aModelToViewHelper.getViewText(); OUString sViewText = aModelToViewHelper.getViewText();
CPPUNIT_ASSERT_EQUAL(sViewText, CPPUNIT_ASSERT_EQUAL(sViewText,
OUString("AAAACCCCC " + OUString(CHAR_ZWSP) + " DDDDD")); OUString("AAAACCCCC " + OUString(CHAR_ZWSP) + " DDDDD"));
...@@ -768,8 +768,26 @@ void SwDocTest::testSwScanner() ...@@ -768,8 +768,26 @@ void SwDocTest::testSwScanner()
aDocStat.Reset(); 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 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(static_cast<sal_uLong>(0), aDocStat.nWord);
CPPUNIT_ASSERT_EQUAL(aDocStat.nChar, static_cast<sal_uLong>(0)); 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->getIDocumentRedlineAccess().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->getIDocumentContentOperations().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 //See https://bugs.libreoffice.org/show_bug.cgi?id=38983
......
...@@ -2100,9 +2100,12 @@ void SwScriptInfo::selectRedLineDeleted(const SwTxtNode& rNode, MultiSelection & ...@@ -2100,9 +2100,12 @@ void SwScriptInfo::selectRedLineDeleted(const SwTxtNode& rNode, MultiSelection &
{ {
const SwRangeRedline* pRed = rIDRA.GetRedlineTbl()[ nAct ]; const SwRangeRedline* pRed = rIDRA.GetRedlineTbl()[ nAct ];
if ( pRed->Start()->nNode > rNode.GetIndex() ) if (pRed->Start()->nNode > rNode.GetIndex())
break; break;
if (pRed->GetType() != nsRedlineType_t::REDLINE_DELETE)
continue;
sal_Int32 nRedlStart; sal_Int32 nRedlStart;
sal_Int32 nRedlnEnd; sal_Int32 nRedlnEnd;
pRed->CalcStartEnd( rNode.GetIndex(), nRedlStart, nRedlnEnd ); pRed->CalcStartEnd( rNode.GetIndex(), nRedlStart, nRedlnEnd );
......
...@@ -94,7 +94,7 @@ ModelToViewHelper::ModelToViewHelper(const SwTxtNode &rNode, sal_uInt16 eMode) ...@@ -94,7 +94,7 @@ ModelToViewHelper::ModelToViewHelper(const SwTxtNode &rNode, sal_uInt16 eMode)
if (eMode & HIDEINVISIBLE) if (eMode & HIDEINVISIBLE)
SwScriptInfo::selectHiddenTextProperty(rNode, aHiddenMulti); SwScriptInfo::selectHiddenTextProperty(rNode, aHiddenMulti);
if (eMode & HIDEREDLINED) if (eMode & HIDEDELETIONS)
SwScriptInfo::selectRedLineDeleted(rNode, aHiddenMulti); SwScriptInfo::selectRedLineDeleted(rNode, aHiddenMulti);
std::vector<block> aBlocks; std::vector<block> aBlocks;
...@@ -203,43 +203,55 @@ ModelToViewHelper::ModelToViewHelper(const SwTxtNode &rNode, sal_uInt16 eMode) ...@@ -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; sal_Int32 nOffset = 0;
for (std::vector<block>::iterator i = aBlocks.begin(); i != aBlocks.end(); ++i) 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) if (!i->m_bVisible)
{ {
const sal_Int32 nHiddenStart = i->m_nStart; sal_Int32 const modelBlockPos(nBlockEnd);
const sal_Int32 nHiddenLen = i->m_nLen; sal_Int32 const viewBlockPos(nBlockStart + nOffset);
m_aMap.push_back(ConversionMapEntry(modelBlockPos, viewBlockPos, false));
m_aRetText = m_aRetText.replaceAt( nOffset + nHiddenStart, nHiddenLen, OUString() ); m_aRetText = m_aRetText.replaceAt(nOffset + nBlockStart, nBlockLen, OUString());
m_aMap.push_back( ConversionMapEntry( nHiddenStart, nOffset + nHiddenStart ) ); nOffset -= nBlockLen;
nOffset -= nHiddenLen;
} }
else else
{ {
for (FieldResultSet::iterator j = i->m_aAttrs.begin(); j != i->m_aAttrs.end(); ++j) for (FieldResultSet::iterator j = i->m_aAttrs.begin(); j != i->m_aAttrs.end(); ++j)
{ {
sal_Int32 const viewPos(nOffset + j->m_nFieldPos); sal_Int32 const modelFieldPos(j->m_nFieldPos);
m_aRetText = m_aRetText.replaceAt(viewPos, 1, j->m_sExpand); sal_Int32 const viewFieldPos(j->m_nFieldPos + nOffset);
m_aMap.push_back( ConversionMapEntry(j->m_nFieldPos, viewPos) ); 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) switch (j->m_eType)
{ {
case FieldResult::FIELD: case FieldResult::FIELD:
m_FieldPositions.push_back(viewPos); m_FieldPositions.push_back(viewFieldPos);
break; break;
case FieldResult::FOOTNOTE: case FieldResult::FOOTNOTE:
m_FootnotePositions.push_back(viewPos); m_FootnotePositions.push_back(viewFieldPos);
break; break;
case FieldResult::NONE: /*ignore*/ case FieldResult::NONE: /*ignore*/
break; 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 /** Converts a model position into a view position
...@@ -248,15 +260,21 @@ sal_Int32 ModelToViewHelper::ConvertToViewPosition( sal_Int32 nModelPos ) const ...@@ -248,15 +260,21 @@ sal_Int32 ModelToViewHelper::ConvertToViewPosition( sal_Int32 nModelPos ) const
{ {
// Search for entry after nPos: // Search for entry after nPos:
ConversionMap::const_iterator aIter; ConversionMap::const_iterator aIter;
for ( aIter = m_aMap.begin(); aIter != m_aMap.end(); ++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; //if it's an invisible portion, map all contained positions
const sal_Int32 nPosExpand = (*aIter).second; //to the anchor viewpos
if (!aIter->m_bVisible)
const sal_Int32 nDistToNextModel = nPosModel - nModelPos; return aIter->m_nViewPos;
return nPosExpand - nDistToNextModel;
//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_ ...@@ -274,10 +292,10 @@ ModelToViewHelper::ModelPosition ModelToViewHelper::ConvertToModelPosition( sal_
ConversionMap::const_iterator aIter; ConversionMap::const_iterator aIter;
for ( aIter = m_aMap.begin(); aIter != m_aMap.end(); ++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 nPosModel = aIter->m_nModelPos;
const sal_Int32 nPosExpand = (*aIter).second; const sal_Int32 nPosExpand = aIter->m_nViewPos;
// If nViewPos is in front of first field, we are finished. // If nViewPos is in front of first field, we are finished.
if ( aIter == m_aMap.begin() ) if ( aIter == m_aMap.begin() )
...@@ -286,8 +304,8 @@ ModelToViewHelper::ModelPosition ModelToViewHelper::ConvertToModelPosition( sal_ ...@@ -286,8 +304,8 @@ ModelToViewHelper::ModelPosition ModelToViewHelper::ConvertToModelPosition( sal_
--aIter; --aIter;
// nPrevPosModel is the field position // nPrevPosModel is the field position
const sal_Int32 nPrevPosModel = (*aIter).first; const sal_Int32 nPrevPosModel = aIter->m_nModelPos;
const sal_Int32 nPrevPosExpand = (*aIter).second; const sal_Int32 nPrevPosExpand = aIter->m_nViewPos;
const sal_Int32 nLengthModel = nPosModel - nPrevPosModel; const sal_Int32 nLengthModel = nPosModel - nPrevPosModel;
const sal_Int32 nLengthExpand = nPosExpand - nPrevPosExpand; const sal_Int32 nLengthExpand = nPosExpand - nPrevPosExpand;
......
...@@ -1993,7 +1993,7 @@ bool SwTxtNode::CountWords( SwDocStat& rStat, ...@@ -1993,7 +1993,7 @@ bool SwTxtNode::CountWords( SwDocStat& rStat,
} }
// ConversionMap to expand fields, remove invisible and redline deleted text for scanner // 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(); OUString aExpandText = aConversionMap.getViewText();
if (aExpandText.isEmpty() && !bCountNumbering) if (aExpandText.isEmpty() && !bCountNumbering)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment