Blob Blame History Raw
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