From 8220b70fe2dc270188751950ac6d872320db1aa2 Mon Sep 17 00:00:00 2001
From: Michael Meeks <michael.meeks@collabora.com>
Date: Sun, 27 Jul 2014 00:21:50 -0400
Subject: [PATCH 068/137] bnc#467459 - fix editeng text search with expanded
fields.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
(cherry picked from commit 274b628a2b523eb45e297352a85f0177c6e747f0)
Signed-off-by: Matúš Kukan <matus.kukan@collabora.com>
Conflicts:
editeng/source/editeng/editdoc.cxx
editeng/source/editeng/editdoc.hxx
Change-Id: If59d0e2f886e94148b81cb6cfcad067733fcb918
---
editeng/CppunitTest_editeng_core.mk | 1 +
editeng/qa/unit/core-test.cxx | 100 +++++++++++++++++++--
editeng/source/editeng/editdoc.cxx | 173 ++++++++++++++++++++++++------------
editeng/source/editeng/editdoc.hxx | 12 ++-
editeng/source/editeng/impedit4.cxx | 6 +-
5 files changed, 226 insertions(+), 66 deletions(-)
diff --git a/editeng/CppunitTest_editeng_core.mk b/editeng/CppunitTest_editeng_core.mk
index 301c760..962fd8f 100644
--- a/editeng/CppunitTest_editeng_core.mk
+++ b/editeng/CppunitTest_editeng_core.mk
@@ -61,6 +61,7 @@ $(eval $(call gb_CppunitTest_use_components,editeng_core,\
configmgr/source/configmgr \
framework/util/fwk \
i18npool/util/i18npool \
+ i18npool/source/search/i18nsearch \
linguistic/source/lng \
sfx2/util/sfx \
ucb/source/core/ucb1 \
diff --git a/editeng/qa/unit/core-test.cxx b/editeng/qa/unit/core-test.cxx
index 338a6cb7..4e3da9b 100644
--- a/editeng/qa/unit/core-test.cxx
+++ b/editeng/qa/unit/core-test.cxx
@@ -25,6 +25,9 @@
#include "editeng/postitem.hxx"
#include "editeng/section.hxx"
#include "editeng/editobj.hxx"
+#include "editeng/flditem.hxx"
+#include "svl/srchitem.hxx"
+#include "rtl/strbuf.hxx"
#include <com/sun/star/text/textfield/Type.hpp>
@@ -44,22 +47,22 @@ public:
void testConstruction();
- /**
- * Test UNO service class that implements text field items.
- */
+ /// Test UNO service class that implements text field items.
void testUnoTextFields();
- /**
- * AutoCorrect tests
- */
+ /// AutoCorrect tests
void testAutocorrect();
+ /// Test hyperlinks
+ void testHyperlinkSearch();
+
void testSectionAttributes();
CPPUNIT_TEST_SUITE(Test);
CPPUNIT_TEST(testConstruction);
CPPUNIT_TEST(testUnoTextFields);
CPPUNIT_TEST(testAutocorrect);
+ CPPUNIT_TEST(testHyperlinkSearch);
CPPUNIT_TEST(testSectionAttributes);
CPPUNIT_TEST_SUITE_END();
@@ -340,6 +343,91 @@ void Test::testAutocorrect()
}
}
+namespace {
+ class UrlEditEngine : public EditEngine
+ {
+ public:
+ UrlEditEngine(SfxItemPool *pPool) : EditEngine(pPool) {}
+
+ virtual OUString CalcFieldValue( const SvxFieldItem&, sal_Int32, sal_uInt16, Color*&, Color*& )
+ {
+ return OUString("jim@bob.com"); // a sophisticated view of value:
+ }
+ };
+}
+
+// Odd accounting for hyperlink position & size etc.
+// https://bugzilla.novell.com/show_bug.cgi?id=467459
+void Test::testHyperlinkSearch()
+{
+ UrlEditEngine aEngine(mpItemPool);
+ EditDoc &rDoc = aEngine.GetEditDoc();
+
+ OUString aSampleText = "Please write email to . if you find a fish(not a dog).";
+ aEngine.SetText(aSampleText);
+
+ CPPUNIT_ASSERT_MESSAGE("set text", rDoc.GetParaAsString(sal_Int32(0)) == aSampleText);
+
+ ContentNode *pNode = rDoc.GetObject(0);
+ EditSelection aSel(EditPaM(pNode, 22), EditPaM(pNode, 22));
+ SvxURLField aURLField("mailto:///jim@bob.com", "jim@bob.com",
+ SVXURLFORMAT_REPR);
+ SvxFieldItem aField(aURLField, EE_FEATURE_FIELD);
+
+ aEngine.InsertField(aSel, aField);
+ aEngine.UpdateFields();
+
+ OUString aContent = pNode->GetExpandedText();
+ CPPUNIT_ASSERT_MESSAGE("get text", aContent ==
+ "Please write email to jim@bob.com. if you find a fish(not a dog).");
+ CPPUNIT_ASSERT_MESSAGE("wrong length", rDoc.GetTextLen() == (sal_uLong)aContent.getLength());
+
+ // Check expansion and positioning re-work
+ CPPUNIT_ASSERT_MESSAGE("wrong length", pNode->GetExpandedLen() ==
+ (sal_uLong)aContent.getLength());
+ for (sal_Int32 n = 0; n < aContent.getLength(); n++)
+ {
+ sal_Int32 nStart = n, nEnd = n;
+ pNode->UnExpandPositions(nStart,nEnd);
+ CPPUNIT_ASSERT_MESSAGE("out of bound start", nStart < pNode->Len());
+ CPPUNIT_ASSERT_MESSAGE("out of bound end", nEnd <= pNode->Len());
+ }
+
+ static const struct {
+ sal_Int32 mnStart, mnEnd;
+ sal_Int32 mnNewStart, mnNewEnd;
+ } aTrickyOnes[] = {
+ { 0, 1, /* -> */ 0, 1 },
+ { 21, 25, /* -> */ 21, 23 }, // the field is really just one char
+ { 25, 27, /* -> */ 22, 23 },
+ { 50, 56, /* -> */ 40, 46 }
+ };
+ for (size_t n = 0; n < SAL_N_ELEMENTS(aTrickyOnes); n++)
+ {
+ sal_Int32 nStart = aTrickyOnes[n].mnStart;
+ sal_Int32 nEnd = aTrickyOnes[n].mnEnd;
+ pNode->UnExpandPositions(nStart,nEnd);
+
+ rtl::OStringBuffer aBuf;
+ aBuf = "bound check start is ";
+ aBuf.append(nStart).append(" but should be ").append(aTrickyOnes[n].mnNewStart);
+ aBuf.append(" in row ").append((sal_Int32)n);
+ CPPUNIT_ASSERT_MESSAGE(aBuf.getStr(), nStart == aTrickyOnes[n].mnNewStart);
+ aBuf = "bound check end is ";
+ aBuf.append(nEnd).append(" but should be ").append(aTrickyOnes[n].mnNewEnd);
+ aBuf.append(" in row ").append((sal_Int32)n);
+ CPPUNIT_ASSERT_MESSAGE(aBuf.getStr(), nEnd == aTrickyOnes[n].mnNewEnd);
+ }
+
+ SvxSearchItem aItem(1); //SID_SEARCH_ITEM);
+ aItem.SetBackward(false);
+ aItem.SetSelection(false);
+ aItem.SetSearchString("fish");
+ CPPUNIT_ASSERT_MESSAGE("no fish", aEngine.HasText(aItem));
+ aItem.SetSearchString("dog");
+ CPPUNIT_ASSERT_MESSAGE("no dog", aEngine.HasText(aItem));
+}
+
bool hasBold(const editeng::Section& rSecAttr)
{
std::vector<const SfxPoolItem*>::const_iterator it = rSecAttr.maAttributes.begin(), itEnd = rSecAttr.maAttributes.end();
diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx
index 7eb4398..6b5d6f1 100644
--- a/editeng/source/editeng/editdoc.cxx
+++ b/editeng/source/editeng/editdoc.cxx
@@ -1699,6 +1699,119 @@ const OUString& ContentNode::GetString() const
return maString;
}
+sal_uLong ContentNode::GetExpandedLen() const
+{
+ sal_uLong nLen = maString.getLength();
+
+ // Fields can be longer than the placeholder in the Node
+ const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs();
+ for (sal_Int32 nAttr = rAttrs.size(); nAttr; )
+ {
+ const EditCharAttrib& rAttr = rAttrs[--nAttr];
+ if (rAttr.Which() == EE_FEATURE_FIELD)
+ {
+ nLen += static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength();
+ --nLen; // Standalone, to avoid corner cases when previous getLength() returns 0
+ }
+ }
+
+ return nLen;
+}
+
+OUString ContentNode::GetExpandedText(sal_Int32 nStartPos, sal_Int32 nEndPos, bool bResolveFields) const
+{
+ if ( nEndPos < 0 || nEndPos > Len() )
+ nEndPos = Len();
+
+ DBG_ASSERT( nStartPos <= nEndPos, "Start and End reversed?" );
+
+ sal_Int32 nIndex = nStartPos;
+ OUString aStr;
+ const EditCharAttrib* pNextFeature = GetCharAttribs().FindFeature( nIndex );
+ while ( nIndex < nEndPos )
+ {
+ sal_Int32 nEnd = nEndPos;
+ if ( pNextFeature && ( pNextFeature->GetStart() < nEnd ) )
+ nEnd = pNextFeature->GetStart();
+ else
+ pNextFeature = 0; // Feature does not interest the below
+
+ DBG_ASSERT( nEnd >= nIndex, "End in front of the index?" );
+ //!! beware of sub string length of -1
+ if (nEnd > nIndex)
+ aStr += GetString().copy(nIndex, nEnd - nIndex);
+
+ if ( pNextFeature )
+ {
+ switch ( pNextFeature->GetItem()->Which() )
+ {
+ case EE_FEATURE_TAB: aStr += "\t";
+ break;
+ case EE_FEATURE_LINEBR: aStr += "\x0A";
+ break;
+ case EE_FEATURE_FIELD:
+ if ( bResolveFields )
+ aStr += static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue();
+ break;
+ default: OSL_FAIL( "What feature?" );
+ }
+ pNextFeature = GetCharAttribs().FindFeature( ++nEnd );
+ }
+ nIndex = nEnd;
+ }
+ return aStr;
+}
+
+void ContentNode::UnExpandPosition( sal_Int32 &rPos, bool bBiasStart )
+{
+ sal_Int32 nOffset = 0;
+
+ const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs();
+ for (size_t nAttr = 0; nAttr < rAttrs.size(); ++nAttr )
+ {
+ const EditCharAttrib& rAttr = rAttrs[nAttr];
+ assert (!(nAttr < rAttrs.size() - 1) ||
+ rAttrs[nAttr].GetStart() < rAttrs[nAttr + 1].GetStart());
+
+ nOffset = rAttr.GetStart();
+
+ if (nOffset >= rPos) // happens after the position
+ return;
+
+ sal_Int32 nChunk = 0;
+ if (rAttr.Which() == EE_FEATURE_FIELD)
+ {
+ nChunk += static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength();
+ nChunk--; // Character representing the field in the string
+
+ if (nOffset + nChunk >= rPos) // we're inside the field
+ {
+ if (bBiasStart)
+ rPos = rAttr.GetStart();
+ else
+ rPos = rAttr.GetEnd();
+ return;
+ }
+ // Adjust for the position
+ rPos -= nChunk;
+ }
+ }
+ assert (rPos <= Len());
+}
+
+/*
+ * Fields are represented by a single character in the underlying string
+ * and/or selection, however, they can be expanded to the full value of
+ * the field. When we're dealing with selection / offsets however we need
+ * to deal in character positions inside the real (unexpanded) string.
+ * This method maps us back to character offsets.
+ */
+void ContentNode::UnExpandPositions( sal_Int32 &rStartPos, sal_Int32 &rEndPos )
+{
+ UnExpandPosition( rStartPos, true );
+ UnExpandPosition( rEndPos, false );
+}
+
void ContentNode::SetChar(sal_uInt16 nPos, sal_Unicode c)
{
maString = maString.replaceAt(nPos, 1, OUString(c));
@@ -2141,49 +2254,9 @@ OUString EditDoc::GetParaAsString( sal_Int32 nNode ) const
}
OUString EditDoc::GetParaAsString(
- const ContentNode* pNode, sal_uInt16 nStartPos, sal_uInt16 nEndPos, bool bResolveFields) const
+ const ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos, bool bResolveFields) const
{
- if ( nEndPos > pNode->Len() )
- nEndPos = pNode->Len();
-
- DBG_ASSERT( nStartPos <= nEndPos, "Start and End reversed?" );
-
- sal_uInt16 nIndex = nStartPos;
- OUString aStr;
- const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( nIndex );
- while ( nIndex < nEndPos )
- {
- sal_uInt16 nEnd = nEndPos;
- if ( pNextFeature && ( pNextFeature->GetStart() < nEnd ) )
- nEnd = pNextFeature->GetStart();
- else
- pNextFeature = 0; // Feature does not interest the below
-
- DBG_ASSERT( nEnd >= nIndex, "End in front of the index?" );
- //!! beware of sub string length of -1 which is also defined as STRING_LEN and
- //!! thus would result in adding the whole sub string up to the end of the node !!
- if (nEnd > nIndex)
- aStr += pNode->GetString().copy(nIndex, nEnd - nIndex);
-
- if ( pNextFeature )
- {
- switch ( pNextFeature->GetItem()->Which() )
- {
- case EE_FEATURE_TAB: aStr += "\t";
- break;
- case EE_FEATURE_LINEBR: aStr += "\x0A";
- break;
- case EE_FEATURE_FIELD:
- if ( bResolveFields )
- aStr += static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue();
- break;
- default: OSL_FAIL( "What feature?" );
- }
- pNextFeature = pNode->GetCharAttribs().FindFeature( ++nEnd );
- }
- nIndex = nEnd;
- }
- return aStr;
+ return pNode->GetExpandedText(nStartPos, nEndPos, bResolveFields);
}
EditPaM EditDoc::GetStartPaM() const
@@ -2204,21 +2277,7 @@ sal_uLong EditDoc::GetTextLen() const
for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ )
{
const ContentNode* pNode = GetObject( nNode );
- nLen += pNode->Len();
- // Fields can be longer than the placeholder in the Node
- const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
- for (size_t nAttr = rAttrs.size(); nAttr; )
- {
- const EditCharAttrib& rAttr = rAttrs[--nAttr];
- if (rAttr.Which() == EE_FEATURE_FIELD)
- {
- sal_Int32 nFieldLen = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength();
- if ( !nFieldLen )
- nLen--;
- else
- nLen += nFieldLen-1;
- }
- }
+ nLen += pNode->GetExpandedLen();
}
return nLen;
}
diff --git a/editeng/source/editeng/editdoc.hxx b/editeng/source/editeng/editdoc.hxx
index dd04503..aba2a07 100644
--- a/editeng/source/editeng/editdoc.hxx
+++ b/editeng/source/editeng/editdoc.hxx
@@ -247,6 +247,8 @@ private:
CharAttribList aCharAttribList;
boost::scoped_ptr<WrongList> mpWrongList;
+ void UnExpandPosition( sal_Int32 &rStartPos, bool bBiasStart );
+
public:
ContentNode( SfxItemPool& rItemPool );
ContentNode( const OUString& rStr, const ContentAttribs& rContentAttribs );
@@ -282,8 +284,16 @@ public:
sal_uInt16 Len() const;
const OUString& GetString() const;
+ /// return length including expanded fields
+ sal_uLong GetExpandedLen() const;
+ /// return content including expanded fields
+ OUString GetExpandedText(sal_Int32 nStartPos = 0, sal_Int32 nEndPos = -1, bool bResolveFields = true) const;
+ /// re-write offsets in the expanded text to string offsets
+ void UnExpandPositions( sal_Int32 &rStartPos, sal_Int32 &rEndPos );
+
void SetChar(sal_uInt16 nPos, sal_Unicode c);
void Insert(const OUString& rStr, sal_uInt16 nPos);
+
void Append(const OUString& rStr);
void Erase(sal_uInt16 nPos);
void Erase(sal_uInt16 nPos, sal_uInt16 nCount);
@@ -769,7 +779,7 @@ public:
sal_uLong GetTextLen() const;
OUString GetParaAsString( sal_Int32 nNode ) const;
- OUString GetParaAsString(const ContentNode* pNode, sal_uInt16 nStartPos = 0, sal_uInt16 nEndPos = 0xFFFF, bool bResolveFields = true) const;
+ OUString GetParaAsString(const ContentNode* pNode, sal_Int32 nStartPos = 0, sal_Int32 nEndPos = -1, bool bResolveFields = true) const;
EditPaM GetStartPaM() const;
EditPaM GetEndPaM() const;
diff --git a/editeng/source/editeng/impedit4.cxx b/editeng/source/editeng/impedit4.cxx
index b4ff56e..f4a9953 100644
--- a/editeng/source/editeng/impedit4.cxx
+++ b/editeng/source/editeng/impedit4.cxx
@@ -2647,7 +2647,7 @@ sal_Bool ImpEditEngine::ImpSearch( const SvxSearchItem& rSearchItem,
ContentNode* pNode = aEditDoc.GetObject( nNode );
sal_Int32 nStartPos = 0;
- sal_Int32 nEndPos = pNode->Len();
+ sal_Int32 nEndPos = pNode->GetExpandedLen();
if ( nNode == nStartNode )
{
if ( bBack )
@@ -2664,7 +2664,7 @@ sal_Bool ImpEditEngine::ImpSearch( const SvxSearchItem& rSearchItem,
}
// Searching ...
- OUString aParaStr( GetEditDoc().GetParaAsString( pNode ) );
+ OUString aParaStr( pNode->GetExpandedText() );
bool bFound = false;
if ( bBack )
{
@@ -2681,6 +2681,8 @@ sal_Bool ImpEditEngine::ImpSearch( const SvxSearchItem& rSearchItem,
}
if ( bFound )
{
+ pNode->UnExpandPositions( nStartPos, nEndPos );
+
rFoundSel.Min().SetNode( pNode );
rFoundSel.Min().SetIndex( nStartPos );
rFoundSel.Max().SetNode( pNode );
--
1.9.3