290c8f6e04
With all the changes done for Items we can now do deeper basic changes to the ItemSet itself with manageable risk. I already did https://gerrit.libreoffice.org/c/core/+/166455 aka 'ITEM: Add measurements for SfxItemSet usages' to get some statistical information about the fill/usage grade of the ItemSet's fixed PtrArray to SfxPoolItems, check that out to get an own picture. Those results show that an average usage is between some extremes ranging from 0% to 50%, but when using more checks and using multiple files/interactions/edits in all applications we end up with around typical 12%-19% of that array used, the rest is nullptr's. Thus I thought about one of the initial ideas of this series of changes (ITEM), to use a std::unordered_map (A) instead of that fixed array of SfxPoolItem Ptr's (B). Tthat again was for a complete type-based rewrite, which I once did a POC, but the code cannot be adapted to that, just too much work. Those are very different in architecture, (B) is done since a long time (since ever), but as pointed out above, (A) is now possible. There are many aspects to it, let's grep some: Speed (iterate): (A) and (B) are both linear. (A) has less entries, but may touch different mem areas (buckets). (B) is linear, but many empty spaces which are usually uselessly iterated. Speed (access Item by WhichID): (A) is hashed by WhichID, so mostly linear for unordered_set/hash. (B) is in a linear array, but has to calculate the offset for each WhichID access. So I guess speed will mostly equal out. Memory: (A) will be dynamically allocated (buckets), but stl is highly optimized and may even re-use areas, has to provide some extra info but will need less places for Items since it's dynamic and can start empty. (B) will be allocated once (except AllItemSet) and may even be 'derived' to the ItemSet (SfxItemSetFixed), but has to allocate all space at once. I can go in lots of more detail here, but due to the results of the statistics I just made a test now, including measuring some results (will include in gerrit, not here). I used two pro versions for that. That way I have some data now. Result is: - It is now possible to do this, it runs stable :-) - Speed: As expected, mostly no change - Memory: Depending on usage, 0% to 25% less, usually around 8-10% Side effects: - SfxAllItemSet could be done without WhichRanges, thus without expensive 'merges' - SfxItemSetFixed is not needed. While the idea is good, it needs a lot of extra stuff - Access to Items is linear if set - WhichRanges: Still needed, but for vaildity checking/filtering of ItemSet content - WhichRanges: Worth to think about if these are needed at all, probably just exist for historical reasons since allocation/number of added Items was never ever dynamic -> just not allocatable Putting the current version on gerrit, may still trigger some UTs (checked SW/SC/SD...) I did not like that functionality at ItemSet that hands out a vector of the set items for cases where to avoid iterating and deleting items at the same time at an ItemSet, so changed to using a local vector of remembered WhichIDs and deleting after the iterator is done. I also saw some strange usages of SfxItemIter in sw which i will have to check. Since there are still problems with UTs which I can not reproduce locally I have now added asserts to the case that an ItemSet gets changed with still having active SfxItemIter(s). That is always an error, but with the architecture of that fixed array of ItemPtrs did not have devastating effects (purely by coincidence). With that asserts, UTs run well in SC and SD, but in SW I get 11 (eleven!) asserts from the UTs, all of them from 'ITEM: SfxItemSet ClearItem' BTW. I guess these have to be fixed before thinking about this change... Good news is that all 11 cases were the same in SW, in SwHistorySetAttrSet::SwHistorySetAttrSet which does some strange things using two SfxItemIter in parallel. Thus SW UTs are also clear and I see no more errors caused by ItemSets being changed while SfxItemIters are alive. Bad news is that I still have errors to hunt... NOTE: Have now cleaned all UTs, this showed that there are some unexpected side-effects of the Items being processed in another order when SfxItemIter is used, also found one case where a WhichRangesContainer is constructed for a SfxItemSet using the set items from another ItemSet and SfxItemIter to do so. There *might* be more cases not covered by UTs. NOTE: While speed stays the same and mem is reduced up to 25% (see measurements in 1st comment) another *important* aspect is that this frees the way for using ItemSets *without* WhichRanges - these are necessary mainly to create that fixed array of pointers to the Items in a *manageable* size. With a dynamic structure like unordered_set there is in principle *no need* anymore to use WhichRanges to pre-define the Items a Set could hold. There is one exception: We have cases where one ItemSet is set at another one with defined WhichRanges to *filter* the contained Items - these would have to be identified. This is a rare case and we would have to check cases where an ItemSet gets set at another ItemSet. This would be as if all ItemSets would be AllItemSets in principle - much easier for everyone. NOTE: Waited for 24.8 split just to not take unnecessary risks. Change-Id: I75b0ac1d8a4495a3ee93c1117bdf618702785990 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166972 Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com> Tested-by: Jenkins
1464 lines
49 KiB
C++
1464 lines
49 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <com/sun/star/lang/Locale.hpp>
|
|
#include <com/sun/star/util/SearchAlgorithms2.hpp>
|
|
#include <com/sun/star/util/SearchFlags.hpp>
|
|
#include <i18nlangtag/languagetag.hxx>
|
|
#include <i18nutil/searchopt.hxx>
|
|
#include <osl/diagnose.h>
|
|
#include <unotools/syslocale.hxx>
|
|
#include <hintids.hxx>
|
|
#include <svl/itemiter.hxx>
|
|
#include <svl/srchitem.hxx>
|
|
#include <svl/whiter.hxx>
|
|
#include <editeng/colritem.hxx>
|
|
#include <editeng/fontitem.hxx>
|
|
#include <fmtpdsc.hxx>
|
|
#include <txatbase.hxx>
|
|
#include <charfmt.hxx>
|
|
#include <crsrsh.hxx>
|
|
#include <doc.hxx>
|
|
#include <IDocumentUndoRedo.hxx>
|
|
#include <IDocumentState.hxx>
|
|
#include <swcrsr.hxx>
|
|
#include <ndtxt.hxx>
|
|
#include <pamtyp.hxx>
|
|
#include <txtfrm.hxx>
|
|
#include <swundo.hxx>
|
|
#include <optional>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace ::com::sun::star::lang;
|
|
using namespace ::com::sun::star::util;
|
|
|
|
// Special case for SvxFontItem: only compare the name
|
|
static bool CmpAttr( const SfxPoolItem& rItem1, const SfxPoolItem& rItem2 )
|
|
{
|
|
switch( rItem1.Which() )
|
|
{
|
|
case RES_CHRATR_FONT:
|
|
return rItem1.StaticWhichCast(RES_CHRATR_FONT).GetFamilyName() == rItem2.StaticWhichCast(RES_CHRATR_FONT).GetFamilyName();
|
|
|
|
case RES_CHRATR_COLOR:
|
|
return rItem1.StaticWhichCast(RES_CHRATR_COLOR).GetValue().IsRGBEqual(rItem2.StaticWhichCast(RES_CHRATR_COLOR).GetValue());
|
|
case RES_PAGEDESC:
|
|
::std::optional<sal_uInt16> const oNumOffset1 = rItem1.StaticWhichCast(RES_PAGEDESC).GetNumOffset();
|
|
::std::optional<sal_uInt16> const oNumOffset2 = rItem2.StaticWhichCast(RES_PAGEDESC).GetNumOffset();
|
|
|
|
if (oNumOffset1 != oNumOffset2)
|
|
return false;
|
|
|
|
return rItem1.StaticWhichCast(RES_PAGEDESC).GetPageDesc() == rItem2.StaticWhichCast(RES_PAGEDESC).GetPageDesc();
|
|
}
|
|
return rItem1 == rItem2;
|
|
}
|
|
|
|
const SwTextAttr* GetFrwrdTextHint( const SwpHints& rHtsArr, size_t& rPos,
|
|
sal_Int32 nContentPos )
|
|
{
|
|
while( rPos < rHtsArr.Count() )
|
|
{
|
|
const SwTextAttr *pTextHt = rHtsArr.Get( rPos++ );
|
|
// the start of an attribute has to be in the section
|
|
if( pTextHt->GetStart() >= nContentPos )
|
|
return pTextHt; // valid text attribute
|
|
}
|
|
return nullptr; // invalid text attribute
|
|
}
|
|
|
|
const SwTextAttr* GetBkwrdTextHint( const SwpHints& rHtsArr, size_t& rPos,
|
|
sal_Int32 nContentPos )
|
|
{
|
|
while( rPos > 0 )
|
|
{
|
|
const SwTextAttr *pTextHt = rHtsArr.Get( --rPos );
|
|
// the start of an attribute has to be in the section
|
|
if( pTextHt->GetStart() < nContentPos )
|
|
return pTextHt; // valid text attribute
|
|
}
|
|
return nullptr; // invalid text attribute
|
|
}
|
|
|
|
static void lcl_SetAttrPam( SwPaM& rPam, sal_Int32 nStart, const sal_Int32* pEnd,
|
|
const bool bSaveMark )
|
|
{
|
|
sal_Int32 nContentPos;
|
|
if( bSaveMark )
|
|
nContentPos = rPam.GetMark()->GetContentIndex();
|
|
else
|
|
nContentPos = rPam.GetPoint()->GetContentIndex();
|
|
bool bTstEnd = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode();
|
|
|
|
rPam.GetPoint()->SetContent( nStart );
|
|
rPam.SetMark(); // Point == GetMark
|
|
|
|
// Point points to end of search area or end of attribute
|
|
if( pEnd )
|
|
{
|
|
if( bTstEnd && *pEnd > nContentPos )
|
|
rPam.GetPoint()->SetContent(nContentPos);
|
|
else
|
|
rPam.GetPoint()->SetContent(*pEnd);
|
|
}
|
|
}
|
|
|
|
// TODO: provide documentation
|
|
/** search for a text attribute
|
|
|
|
This function searches in a text node for a given attribute.
|
|
If that is found then the SwPaM contains the section that surrounds the
|
|
attribute (w.r.t. the search area).
|
|
|
|
@param rTextNd Text node to search in.
|
|
@param rPam ???
|
|
@param rCmpItem ???
|
|
@param fnMove ???
|
|
@return Returns <true> if found, <false> otherwise.
|
|
*/
|
|
static bool lcl_SearchAttr( const SwTextNode& rTextNd, SwPaM& rPam,
|
|
const SfxPoolItem& rCmpItem,
|
|
SwMoveFnCollection const & fnMove)
|
|
{
|
|
if ( !rTextNd.HasHints() )
|
|
return false;
|
|
|
|
const SwTextAttr *pTextHt = nullptr;
|
|
bool bForward = &fnMove == &fnMoveForward;
|
|
size_t nPos = bForward ? 0 : rTextNd.GetSwpHints().Count();
|
|
sal_Int32 nContentPos = rPam.GetPoint()->GetContentIndex();
|
|
|
|
while( nullptr != ( pTextHt=(*fnMove.fnGetHint)(rTextNd.GetSwpHints(),nPos,nContentPos)))
|
|
if (pTextHt->Which() == rCmpItem.Which())
|
|
{
|
|
lcl_SetAttrPam( rPam, pTextHt->GetStart(), pTextHt->End(), bForward );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// search for multiple text attributes
|
|
struct SwSrchChrAttr
|
|
{
|
|
sal_uInt16 nWhich;
|
|
sal_Int32 nStt;
|
|
sal_Int32 nEnd;
|
|
|
|
SwSrchChrAttr(): nWhich(0), nStt(0), nEnd(0) {}
|
|
|
|
SwSrchChrAttr( const SfxPoolItem& rItem,
|
|
sal_Int32 nStart, sal_Int32 nAnyEnd )
|
|
: nWhich( rItem.Which() ), nStt( nStart ), nEnd( nAnyEnd )
|
|
{}
|
|
};
|
|
|
|
class SwAttrCheckArr
|
|
{
|
|
SwSrchChrAttr *m_pFindArr, *m_pStackArr;
|
|
sal_Int32 m_nNodeStart;
|
|
sal_Int32 m_nNodeEnd;
|
|
sal_uInt16 m_nArrStart, m_nArrLen;
|
|
sal_uInt16 m_nFound, m_nStackCount;
|
|
SfxItemSet m_aComapeSet;
|
|
bool m_bNoColls;
|
|
bool m_bForward;
|
|
|
|
public:
|
|
SwAttrCheckArr( const SfxItemSet& rSet, bool bForward, bool bNoCollections );
|
|
~SwAttrCheckArr();
|
|
|
|
void SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam );
|
|
|
|
/// how many attributes are there in total?
|
|
sal_uInt16 Count() const { return m_aComapeSet.Count(); }
|
|
bool Found() const { return m_nFound == m_aComapeSet.Count(); }
|
|
bool CheckStack();
|
|
|
|
sal_Int32 Start() const;
|
|
sal_Int32 End() const;
|
|
|
|
sal_Int32 GetNdStt() const { return m_nNodeStart; }
|
|
sal_Int32 GetNdEnd() const { return m_nNodeEnd; }
|
|
|
|
bool SetAttrFwd( const SwTextAttr& rAttr );
|
|
bool SetAttrBwd( const SwTextAttr& rAttr );
|
|
};
|
|
|
|
}
|
|
|
|
SwAttrCheckArr::SwAttrCheckArr( const SfxItemSet& rSet, bool bFwd,
|
|
bool bNoCollections )
|
|
: m_nNodeStart(0)
|
|
, m_nNodeEnd(0)
|
|
, m_nFound(0)
|
|
, m_nStackCount(0)
|
|
, m_aComapeSet( *rSet.GetPool(), svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END-1> )
|
|
, m_bNoColls(bNoCollections)
|
|
, m_bForward(bFwd)
|
|
{
|
|
m_aComapeSet.Put( rSet, false );
|
|
|
|
// determine area of Fnd/Stack array (Min/Max)
|
|
sal_uInt16 nMinUsedWhichID(0);
|
|
sal_uInt16 nMaxUsedWhichID(0);
|
|
|
|
if (0 != m_aComapeSet.Count())
|
|
{
|
|
nMinUsedWhichID = 5000; // SFX_WHICH_MAX+1;
|
|
for (SfxItemIter aIter(m_aComapeSet); !aIter.IsAtEnd(); aIter.NextItem())
|
|
{
|
|
const sal_uInt16 nCurrentWhich(aIter.GetCurWhich());
|
|
if (SfxItemPool::IsSlot(nCurrentWhich))
|
|
continue;
|
|
nMinUsedWhichID = std::min(nMinUsedWhichID, nCurrentWhich);
|
|
nMaxUsedWhichID = std::max(nMaxUsedWhichID, nCurrentWhich);
|
|
}
|
|
|
|
if (nMinUsedWhichID > nMaxUsedWhichID)
|
|
nMinUsedWhichID = nMaxUsedWhichID = 0;
|
|
}
|
|
|
|
m_nArrStart = nMinUsedWhichID;//m_aComapeSet.GetWhichByOffset( aIter.GetFirstPos() );
|
|
m_nArrLen = nMaxUsedWhichID - nMinUsedWhichID + 1;//m_aComapeSet.GetWhichByOffset( aIter.GetLastPos() ) - m_nArrStart+1;
|
|
|
|
char* pFndChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ];
|
|
char* pStackChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ];
|
|
|
|
m_pFindArr = reinterpret_cast<SwSrchChrAttr*>(pFndChar);
|
|
m_pStackArr = reinterpret_cast<SwSrchChrAttr*>(pStackChar);
|
|
}
|
|
|
|
SwAttrCheckArr::~SwAttrCheckArr()
|
|
{
|
|
delete[] reinterpret_cast<char*>(m_pFindArr);
|
|
delete[] reinterpret_cast<char*>(m_pStackArr);
|
|
}
|
|
|
|
void SwAttrCheckArr::SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam )
|
|
{
|
|
std::fill(m_pFindArr, m_pFindArr + m_nArrLen, SwSrchChrAttr());
|
|
std::fill(m_pStackArr, m_pStackArr + m_nArrLen, SwSrchChrAttr());
|
|
m_nFound = 0;
|
|
m_nStackCount = 0;
|
|
|
|
if( m_bForward )
|
|
{
|
|
m_nNodeStart = rPam.GetPoint()->GetContentIndex();
|
|
m_nNodeEnd = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode()
|
|
? rPam.GetMark()->GetContentIndex()
|
|
: rTextNd.GetText().getLength();
|
|
}
|
|
else
|
|
{
|
|
m_nNodeEnd = rPam.GetPoint()->GetContentIndex();
|
|
m_nNodeStart = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode()
|
|
? rPam.GetMark()->GetContentIndex()
|
|
: 0;
|
|
}
|
|
|
|
if( m_bNoColls && !rTextNd.HasSwAttrSet() )
|
|
return ;
|
|
|
|
const SfxItemSet& rSet = rTextNd.GetSwAttrSet();
|
|
|
|
SfxItemIter aIter( m_aComapeSet );
|
|
const SfxPoolItem* pItem = aIter.GetCurItem();
|
|
const SfxPoolItem* pFndItem;
|
|
sal_uInt16 nWhich;
|
|
|
|
do
|
|
{
|
|
if( IsInvalidItem( pItem ) )
|
|
{
|
|
nWhich = aIter.GetCurWhich();
|
|
if( RES_TXTATR_END <= nWhich )
|
|
break; // end of text attributes
|
|
|
|
if( SfxItemState::SET == rSet.GetItemState( nWhich, !m_bNoColls, &pFndItem )
|
|
&& !CmpAttr( *pFndItem, rSet.GetPool()->GetUserOrPoolDefaultItem( nWhich ) ))
|
|
{
|
|
m_pFindArr[ nWhich - m_nArrStart ] =
|
|
SwSrchChrAttr( *pFndItem, m_nNodeStart, m_nNodeEnd );
|
|
m_nFound++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nWhich = pItem->Which();
|
|
if( RES_TXTATR_END <= nWhich )
|
|
break; // end of text attributes
|
|
|
|
if( CmpAttr( rSet.Get( nWhich, !m_bNoColls ), *pItem ) )
|
|
{
|
|
m_pFindArr[ nWhich - m_nArrStart ] =
|
|
SwSrchChrAttr( *pItem, m_nNodeStart, m_nNodeEnd );
|
|
m_nFound++;
|
|
}
|
|
}
|
|
|
|
pItem = aIter.NextItem();
|
|
} while (pItem);
|
|
}
|
|
|
|
static bool
|
|
lcl_IsAttributeIgnorable(sal_Int32 const nNdStart, sal_Int32 const nNdEnd,
|
|
SwSrchChrAttr const& rTmp)
|
|
{
|
|
// #i115528#: if there is a paragraph attribute, it has been added by the
|
|
// SwAttrCheckArr ctor, and nFound is 1.
|
|
// if the paragraph is entirely covered by hints that override the paragraph
|
|
// attribute, then this function must find an attribute to decrement nFound!
|
|
// so check for an empty search range, let attributes that start/end there
|
|
// cover it, and hope for the best...
|
|
return ((nNdEnd == nNdStart)
|
|
? ((rTmp.nEnd < nNdStart) || (nNdEnd < rTmp.nStt))
|
|
: ((rTmp.nEnd <= nNdStart) || (nNdEnd <= rTmp.nStt)));
|
|
}
|
|
|
|
bool SwAttrCheckArr::SetAttrFwd( const SwTextAttr& rAttr )
|
|
{
|
|
SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() );
|
|
|
|
// ignore all attributes not in search range
|
|
if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp))
|
|
{
|
|
return Found();
|
|
}
|
|
|
|
const SfxPoolItem* pItem;
|
|
// here we explicitly also search in character templates
|
|
sal_uInt16 nWhch = rAttr.Which();
|
|
std::optional<SfxWhichIter> oIter;
|
|
const SfxPoolItem* pTmpItem = nullptr;
|
|
const SfxItemSet* pSet = nullptr;
|
|
if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch )
|
|
{
|
|
if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch )
|
|
return Found();
|
|
pTmpItem = nullptr;
|
|
pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
|
|
if ( pSet )
|
|
{
|
|
oIter.emplace( *pSet );
|
|
nWhch = oIter->FirstWhich();
|
|
while( nWhch &&
|
|
SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
|
|
nWhch = oIter->NextWhich();
|
|
if( !nWhch )
|
|
pTmpItem = nullptr;
|
|
}
|
|
}
|
|
else
|
|
pTmpItem = &rAttr.GetAttr();
|
|
|
|
while( pTmpItem )
|
|
{
|
|
SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem );
|
|
if( SfxItemState::INVALID == eState || SfxItemState::SET == eState )
|
|
{
|
|
sal_uInt16 n;
|
|
SwSrchChrAttr* pCmp;
|
|
|
|
// first delete all up to start position that are already invalid
|
|
SwSrchChrAttr* pArrPtr;
|
|
if( m_nFound )
|
|
for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen;
|
|
++n, ++pArrPtr )
|
|
if( pArrPtr->nWhich && pArrPtr->nEnd <= aTmp.nStt )
|
|
{
|
|
pArrPtr->nWhich = 0; // deleted
|
|
m_nFound--;
|
|
}
|
|
|
|
// delete all up to start position that are already invalid and
|
|
// move all "open" ones (= stick out over start position) from stack
|
|
// into FndSet
|
|
if( m_nStackCount )
|
|
for( pArrPtr = m_pStackArr, n=0; n < m_nArrLen; ++n, ++pArrPtr )
|
|
{
|
|
if( !pArrPtr->nWhich )
|
|
continue;
|
|
|
|
if( pArrPtr->nEnd <= aTmp.nStt )
|
|
{
|
|
pArrPtr->nWhich = 0; // deleted
|
|
if( !--m_nStackCount )
|
|
break;
|
|
}
|
|
else if( pArrPtr->nStt <= aTmp.nStt )
|
|
{
|
|
pCmp = &m_pFindArr[ n ];
|
|
if( pCmp->nWhich )
|
|
{
|
|
if( pCmp->nEnd < pArrPtr->nEnd ) // extend
|
|
pCmp->nEnd = pArrPtr->nEnd;
|
|
}
|
|
else
|
|
{
|
|
*pCmp = *pArrPtr;
|
|
m_nFound++;
|
|
}
|
|
pArrPtr->nWhich = 0;
|
|
if( !--m_nStackCount )
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool bContinue = false;
|
|
|
|
if( SfxItemState::INVALID == eState )
|
|
{
|
|
// Will the attribute become valid?
|
|
if( !CmpAttr( m_aComapeSet.GetPool()->GetUserOrPoolDefaultItem( nWhch ),
|
|
*pTmpItem ))
|
|
{
|
|
// search attribute and extend if needed
|
|
pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
|
|
if( !pCmp->nWhich )
|
|
{
|
|
*pCmp = aTmp; // not found, insert
|
|
m_nFound++;
|
|
}
|
|
else if( pCmp->nEnd < aTmp.nEnd ) // extend?
|
|
pCmp->nEnd = aTmp.nEnd;
|
|
|
|
bContinue = true;
|
|
}
|
|
}
|
|
// Will the attribute become valid?
|
|
else if( CmpAttr( *pItem, *pTmpItem ) )
|
|
{
|
|
m_pFindArr[ nWhch - m_nArrStart ] = aTmp;
|
|
++m_nFound;
|
|
bContinue = true;
|
|
}
|
|
|
|
// then is has to go on the stack
|
|
if( !bContinue )
|
|
{
|
|
pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
|
|
if (pCmp->nWhich )
|
|
{
|
|
// exists on stack, only if it is even bigger
|
|
if( pCmp->nEnd > aTmp.nEnd )
|
|
{
|
|
OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich,
|
|
"slot on stack is still in use" );
|
|
|
|
if( aTmp.nStt <= pCmp->nStt )
|
|
pCmp->nStt = aTmp.nEnd;
|
|
else
|
|
pCmp->nEnd = aTmp.nStt;
|
|
|
|
m_pStackArr[ nWhch - m_nArrStart ] = *pCmp;
|
|
m_nStackCount++;
|
|
}
|
|
pCmp->nWhich = 0;
|
|
m_nFound--;
|
|
}
|
|
}
|
|
}
|
|
if( oIter )
|
|
{
|
|
assert(pSet && "otherwise no oIter");
|
|
nWhch = oIter->NextWhich();
|
|
while( nWhch &&
|
|
SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
|
|
nWhch = oIter->NextWhich();
|
|
if( !nWhch )
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
oIter.reset();
|
|
return Found();
|
|
}
|
|
|
|
bool SwAttrCheckArr::SetAttrBwd( const SwTextAttr& rAttr )
|
|
{
|
|
SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() );
|
|
|
|
// ignore all attributes not in search range
|
|
if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp))
|
|
{
|
|
return Found();
|
|
}
|
|
|
|
const SfxPoolItem* pItem;
|
|
// here we explicitly also search in character templates
|
|
sal_uInt16 nWhch = rAttr.Which();
|
|
std::optional<SfxWhichIter> oIter;
|
|
const SfxPoolItem* pTmpItem = nullptr;
|
|
const SfxItemSet* pSet = nullptr;
|
|
if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch )
|
|
{
|
|
if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch )
|
|
return Found();
|
|
|
|
pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
|
|
if ( pSet )
|
|
{
|
|
oIter.emplace( *pSet );
|
|
nWhch = oIter->FirstWhich();
|
|
while( nWhch &&
|
|
SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
|
|
nWhch = oIter->NextWhich();
|
|
if( !nWhch )
|
|
pTmpItem = nullptr;
|
|
}
|
|
}
|
|
else
|
|
pTmpItem = &rAttr.GetAttr();
|
|
|
|
while( pTmpItem )
|
|
{
|
|
SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem );
|
|
if( SfxItemState::INVALID == eState || SfxItemState::SET == eState )
|
|
{
|
|
sal_uInt16 n;
|
|
SwSrchChrAttr* pCmp;
|
|
|
|
// first delete all up to start position that are already invalid
|
|
SwSrchChrAttr* pArrPtr;
|
|
if( m_nFound )
|
|
for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
|
|
if( pArrPtr->nWhich && pArrPtr->nStt >= aTmp.nEnd )
|
|
{
|
|
pArrPtr->nWhich = 0; // deleted
|
|
m_nFound--;
|
|
}
|
|
|
|
// delete all up to start position that are already invalid and
|
|
// move all "open" ones (= stick out over start position) from stack
|
|
// into FndSet
|
|
if( m_nStackCount )
|
|
for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
|
|
{
|
|
if( !pArrPtr->nWhich )
|
|
continue;
|
|
|
|
if( pArrPtr->nStt >= aTmp.nEnd )
|
|
{
|
|
pArrPtr->nWhich = 0; // deleted
|
|
if( !--m_nStackCount )
|
|
break;
|
|
}
|
|
else if( pArrPtr->nEnd >= aTmp.nEnd )
|
|
{
|
|
pCmp = &m_pFindArr[ n ];
|
|
if( pCmp->nWhich )
|
|
{
|
|
if( pCmp->nStt > pArrPtr->nStt ) // extend
|
|
pCmp->nStt = pArrPtr->nStt;
|
|
}
|
|
else
|
|
{
|
|
*pCmp = *pArrPtr;
|
|
m_nFound++;
|
|
}
|
|
pArrPtr->nWhich = 0;
|
|
if( !--m_nStackCount )
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool bContinue = false;
|
|
if( SfxItemState::INVALID == eState )
|
|
{
|
|
// Will the attribute become valid?
|
|
if( !CmpAttr( m_aComapeSet.GetPool()->GetUserOrPoolDefaultItem( nWhch ),
|
|
*pTmpItem ) )
|
|
{
|
|
// search attribute and extend if needed
|
|
pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
|
|
if( !pCmp->nWhich )
|
|
{
|
|
*pCmp = aTmp; // not found, insert
|
|
m_nFound++;
|
|
}
|
|
else if( pCmp->nStt > aTmp.nStt ) // extend?
|
|
pCmp->nStt = aTmp.nStt;
|
|
|
|
bContinue = true;
|
|
}
|
|
}
|
|
// Will the attribute become valid?
|
|
else if( CmpAttr( *pItem, *pTmpItem ))
|
|
{
|
|
m_pFindArr[ nWhch - m_nArrStart ] = aTmp;
|
|
++m_nFound;
|
|
bContinue = true;
|
|
}
|
|
|
|
// then is has to go on the stack
|
|
if( !bContinue )
|
|
{
|
|
pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
|
|
if( pCmp->nWhich )
|
|
{
|
|
// exists on stack, only if it is even bigger
|
|
if( pCmp->nStt < aTmp.nStt )
|
|
{
|
|
OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich,
|
|
"slot on stack is still in use" );
|
|
|
|
if( aTmp.nEnd <= pCmp->nEnd )
|
|
pCmp->nEnd = aTmp.nStt;
|
|
else
|
|
pCmp->nStt = aTmp.nEnd;
|
|
|
|
m_pStackArr[ nWhch - m_nArrStart ] = *pCmp;
|
|
m_nStackCount++;
|
|
}
|
|
pCmp->nWhich = 0;
|
|
m_nFound--;
|
|
}
|
|
}
|
|
}
|
|
if( oIter )
|
|
{
|
|
assert(pSet && "otherwise no oIter");
|
|
nWhch = oIter->NextWhich();
|
|
while( nWhch &&
|
|
SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
|
|
nWhch = oIter->NextWhich();
|
|
if( !nWhch )
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
oIter.reset();
|
|
return Found();
|
|
}
|
|
|
|
sal_Int32 SwAttrCheckArr::Start() const
|
|
{
|
|
sal_Int32 nStart = m_nNodeStart;
|
|
SwSrchChrAttr* pArrPtr = m_pFindArr;
|
|
for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr )
|
|
if( pArrPtr->nWhich && pArrPtr->nStt > nStart )
|
|
nStart = pArrPtr->nStt;
|
|
|
|
return nStart;
|
|
}
|
|
|
|
sal_Int32 SwAttrCheckArr::End() const
|
|
{
|
|
SwSrchChrAttr* pArrPtr = m_pFindArr;
|
|
sal_Int32 nEnd = m_nNodeEnd;
|
|
for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr )
|
|
if( pArrPtr->nWhich && pArrPtr->nEnd < nEnd )
|
|
nEnd = pArrPtr->nEnd;
|
|
|
|
return nEnd;
|
|
}
|
|
|
|
bool SwAttrCheckArr::CheckStack()
|
|
{
|
|
if( !m_nStackCount )
|
|
return false;
|
|
|
|
sal_uInt16 n;
|
|
const sal_Int32 nSttPos = Start();
|
|
const sal_Int32 nEndPos = End();
|
|
SwSrchChrAttr* pArrPtr;
|
|
for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
|
|
{
|
|
if( !pArrPtr->nWhich )
|
|
continue;
|
|
|
|
if( m_bForward ? pArrPtr->nEnd <= nSttPos : pArrPtr->nStt >= nEndPos )
|
|
{
|
|
pArrPtr->nWhich = 0; // deleted
|
|
if( !--m_nStackCount )
|
|
return m_nFound == m_aComapeSet.Count();
|
|
}
|
|
else if( m_bForward ? pArrPtr->nStt < nEndPos : pArrPtr->nEnd > nSttPos )
|
|
{
|
|
// move all "open" ones (= stick out over start position) into FndSet
|
|
OSL_ENSURE( !m_pFindArr[ n ].nWhich, "slot in array is already in use" );
|
|
m_pFindArr[ n ] = *pArrPtr;
|
|
pArrPtr->nWhich = 0;
|
|
m_nFound++;
|
|
if( !--m_nStackCount )
|
|
return m_nFound == m_aComapeSet.Count();
|
|
}
|
|
}
|
|
return m_nFound == m_aComapeSet.Count();
|
|
}
|
|
|
|
static bool lcl_SearchForward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr,
|
|
SwPaM& rPam )
|
|
{
|
|
sal_Int32 nEndPos;
|
|
rCmpArr.SetNewSet( rTextNd, rPam );
|
|
if( !rTextNd.HasHints() )
|
|
{
|
|
if( !rCmpArr.Found() )
|
|
return false;
|
|
nEndPos = rCmpArr.GetNdEnd();
|
|
lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true );
|
|
return true;
|
|
}
|
|
|
|
const SwpHints& rHtArr = rTextNd.GetSwpHints();
|
|
const SwTextAttr* pAttr;
|
|
size_t nPos = 0;
|
|
|
|
// if everything is already there then check with which it will be ended
|
|
if( rCmpArr.Found() )
|
|
{
|
|
for( ; nPos < rHtArr.Count(); ++nPos )
|
|
{
|
|
pAttr = rHtArr.Get( nPos );
|
|
if( !rCmpArr.SetAttrFwd( *pAttr ) )
|
|
{
|
|
if( rCmpArr.GetNdStt() < pAttr->GetStart() )
|
|
{
|
|
// found end
|
|
auto nTmpStart = pAttr->GetStart();
|
|
lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(),
|
|
&nTmpStart, true );
|
|
return true;
|
|
}
|
|
// continue search
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( nPos == rHtArr.Count() && rCmpArr.Found() )
|
|
{
|
|
// found
|
|
nEndPos = rCmpArr.GetNdEnd();
|
|
lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
sal_Int32 nSttPos;
|
|
for( ; nPos < rHtArr.Count(); ++nPos )
|
|
{
|
|
pAttr = rHtArr.Get( nPos );
|
|
if( rCmpArr.SetAttrFwd( *pAttr ) )
|
|
{
|
|
// Do multiple start at that position? Do also check those:
|
|
nSttPos = pAttr->GetStart();
|
|
while( ++nPos < rHtArr.Count() )
|
|
{
|
|
pAttr = rHtArr.Get( nPos );
|
|
if( nSttPos != pAttr->GetStart() || !rCmpArr.SetAttrFwd( *pAttr ) )
|
|
break;
|
|
}
|
|
|
|
if( !rCmpArr.Found() )
|
|
continue;
|
|
|
|
// then we have our search area
|
|
nSttPos = rCmpArr.Start();
|
|
nEndPos = rCmpArr.End();
|
|
if( nSttPos > nEndPos )
|
|
return false;
|
|
|
|
lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if( !rCmpArr.CheckStack() )
|
|
return false;
|
|
nSttPos = rCmpArr.Start();
|
|
nEndPos = rCmpArr.End();
|
|
if( nSttPos > nEndPos )
|
|
return false;
|
|
|
|
lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true );
|
|
return true;
|
|
}
|
|
|
|
static bool lcl_SearchBackward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr,
|
|
SwPaM& rPam )
|
|
{
|
|
sal_Int32 nEndPos;
|
|
rCmpArr.SetNewSet( rTextNd, rPam );
|
|
if( !rTextNd.HasHints() )
|
|
{
|
|
if( !rCmpArr.Found() )
|
|
return false;
|
|
nEndPos = rCmpArr.GetNdEnd();
|
|
lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false );
|
|
return true;
|
|
}
|
|
|
|
const SwpHints& rHtArr = rTextNd.GetSwpHints();
|
|
const SwTextAttr* pAttr;
|
|
size_t nPos = rHtArr.Count();
|
|
sal_Int32 nSttPos;
|
|
|
|
// if everything is already there then check with which it will be ended
|
|
if( rCmpArr.Found() )
|
|
{
|
|
while( nPos )
|
|
{
|
|
pAttr = rHtArr.GetSortedByEnd( --nPos );
|
|
if( !rCmpArr.SetAttrBwd( *pAttr ) )
|
|
{
|
|
nSttPos = pAttr->GetAnyEnd();
|
|
if( nSttPos < rCmpArr.GetNdEnd() )
|
|
{
|
|
// found end
|
|
nEndPos = rCmpArr.GetNdEnd();
|
|
lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
|
|
return true;
|
|
}
|
|
|
|
// continue search
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !nPos && rCmpArr.Found() )
|
|
{
|
|
// found
|
|
nEndPos = rCmpArr.GetNdEnd();
|
|
lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
while( nPos )
|
|
{
|
|
pAttr = rHtArr.GetSortedByEnd( --nPos );
|
|
if( rCmpArr.SetAttrBwd( *pAttr ) )
|
|
{
|
|
// Do multiple start at that position? Do also check those:
|
|
if( nPos )
|
|
{
|
|
nEndPos = pAttr->GetAnyEnd();
|
|
while( --nPos )
|
|
{
|
|
pAttr = rHtArr.GetSortedByEnd( nPos );
|
|
if( nEndPos != pAttr->GetAnyEnd() || !rCmpArr.SetAttrBwd( *pAttr ) )
|
|
break;
|
|
}
|
|
}
|
|
if( !rCmpArr.Found() )
|
|
continue;
|
|
|
|
// then we have our search area
|
|
nSttPos = rCmpArr.Start();
|
|
nEndPos = rCmpArr.End();
|
|
if( nSttPos > nEndPos )
|
|
return false;
|
|
|
|
lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if( !rCmpArr.CheckStack() )
|
|
return false;
|
|
nSttPos = rCmpArr.Start();
|
|
nEndPos = rCmpArr.End();
|
|
if( nSttPos > nEndPos )
|
|
return false;
|
|
|
|
lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
|
|
return true;
|
|
}
|
|
|
|
static bool lcl_Search( const SwContentNode& rCNd, const SfxItemSet& rCmpSet, bool bNoColls )
|
|
{
|
|
// search only hard attribution?
|
|
if( bNoColls && !rCNd.HasSwAttrSet() )
|
|
return false;
|
|
|
|
const SfxItemSet& rNdSet = rCNd.GetSwAttrSet();
|
|
SfxItemIter aIter( rCmpSet );
|
|
const SfxPoolItem* pItem = aIter.GetCurItem();
|
|
const SfxPoolItem* pNdItem;
|
|
sal_uInt16 nWhich;
|
|
|
|
do
|
|
{
|
|
if( IsInvalidItem( pItem ))
|
|
{
|
|
nWhich = aIter.GetCurWhich();
|
|
if( SfxItemState::SET != rNdSet.GetItemState( nWhich, !bNoColls, &pNdItem )
|
|
|| CmpAttr( *pNdItem, rNdSet.GetPool()->GetUserOrPoolDefaultItem( nWhich ) ))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
nWhich = pItem->Which();
|
|
|
|
if( !CmpAttr( rNdSet.Get( nWhich, !bNoColls ), *pItem ))
|
|
return false;
|
|
}
|
|
|
|
pItem = aIter.NextItem();
|
|
} while (pItem);
|
|
return true; // found
|
|
}
|
|
|
|
namespace sw {
|
|
|
|
bool FindAttrImpl(SwPaM & rSearchPam,
|
|
const SfxPoolItem& rAttr, SwMoveFnCollection const & fnMove,
|
|
const SwPaM & rRegion, bool bInReadOnly,
|
|
SwRootFrame const*const pLayout)
|
|
{
|
|
// determine which attribute is searched:
|
|
const sal_uInt16 nWhich = rAttr.Which();
|
|
bool bCharAttr = isCHRATR(nWhich) || isTXTATR(nWhich);
|
|
assert(isTXTATR(nWhich)); // sw_redlinehide: only works for non-formatting hints such as needed in UpdateFields; use FindAttrsImpl for others
|
|
|
|
std::optional<SwPaM> oPam;
|
|
sw::MakeRegion(fnMove, rRegion, oPam);
|
|
|
|
bool bFound = false;
|
|
bool bFirst = true;
|
|
const bool bSrchForward = &fnMove == &fnMoveForward;
|
|
SwContentNode * pNode;
|
|
|
|
// if at beginning/end then move it out of the node
|
|
if( bSrchForward
|
|
? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len()
|
|
: !oPam->GetPoint()->GetContentIndex() )
|
|
{
|
|
if( !(*fnMove.fnPos)( oPam->GetPoint(), false ))
|
|
{
|
|
return false;
|
|
}
|
|
SwContentNode *pNd = oPam->GetPointContentNode();
|
|
oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() );
|
|
}
|
|
|
|
while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
|
|
{
|
|
if( bCharAttr )
|
|
{
|
|
if( !pNode->IsTextNode() ) // CharAttr are only in text nodes
|
|
continue;
|
|
|
|
SwTextFrame const*const pFrame(pLayout
|
|
? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout))
|
|
: nullptr);
|
|
if (pFrame)
|
|
{
|
|
SwTextNode const* pAttrNode(nullptr);
|
|
SwTextAttr const* pAttr(nullptr);
|
|
if (bSrchForward)
|
|
{
|
|
sw::MergedAttrIter iter(*pFrame);
|
|
do
|
|
{
|
|
pAttr = iter.NextAttr(&pAttrNode);
|
|
}
|
|
while (pAttr
|
|
&& (pAttrNode->GetIndex() < oPam->GetPoint()->GetNodeIndex()
|
|
|| (pAttrNode->GetIndex() == oPam->GetPoint()->GetNodeIndex()
|
|
&& pAttr->GetStart() < oPam->GetPoint()->GetContentIndex())
|
|
|| pAttr->Which() != nWhich));
|
|
}
|
|
else
|
|
{
|
|
sw::MergedAttrIterReverse iter(*pFrame);
|
|
do
|
|
{
|
|
pAttr = iter.PrevAttr(&pAttrNode);
|
|
}
|
|
while (pAttr
|
|
&& (oPam->GetPoint()->GetNodeIndex() < pAttrNode->GetIndex()
|
|
|| (oPam->GetPoint()->GetNodeIndex() == pAttrNode->GetIndex()
|
|
&& oPam->GetPoint()->GetContentIndex() <= pAttr->GetStart())
|
|
|| pAttr->Which() != nWhich));
|
|
}
|
|
if (pAttr)
|
|
{
|
|
assert(pAttrNode);
|
|
oPam->GetPoint()->Assign(*pAttrNode);
|
|
lcl_SetAttrPam(*oPam, pAttr->GetStart(), pAttr->End(), bSrchForward);
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (!pLayout && pNode->GetTextNode()->HasHints() &&
|
|
lcl_SearchAttr(*pNode->GetTextNode(), *oPam, rAttr, fnMove))
|
|
{
|
|
bFound = true;
|
|
}
|
|
if (bFound)
|
|
{
|
|
// set to the values of the attribute
|
|
rSearchPam.SetMark();
|
|
*rSearchPam.GetPoint() = *oPam->GetPoint();
|
|
*rSearchPam.GetMark() = *oPam->GetMark();
|
|
break;
|
|
}
|
|
else if (isTXTATR(nWhich))
|
|
continue;
|
|
}
|
|
|
|
#if 0
|
|
// no hard attribution, so check if node was asked for this attr before
|
|
if( !pNode->HasSwAttrSet() )
|
|
{
|
|
SwFormat* pTmpFormat = pNode->GetFormatColl();
|
|
if( !aFormatArr.insert( pTmpFormat ).second )
|
|
continue; // collection was requested earlier
|
|
}
|
|
|
|
if( SfxItemState::SET == pNode->GetSwAttrSet().GetItemState( nWhich,
|
|
true, &pItem ))
|
|
{
|
|
// FORWARD: SPoint at the end, GetMark at the beginning of the node
|
|
// BACKWARD: SPoint at the beginning, GetMark at the end of the node
|
|
// always: incl. start and incl. end
|
|
*rSearchPam.GetPoint() = *pPam->GetPoint();
|
|
rSearchPam.SetMark();
|
|
rSearchPam.GetPoint()->SetContent(pNode->Len());
|
|
bFound = true;
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// if backward search, switch point and mark
|
|
if( bFound && !bSrchForward )
|
|
rSearchPam.Exchange();
|
|
|
|
return bFound;
|
|
}
|
|
|
|
} // namespace sw
|
|
|
|
typedef bool (*FnSearchAttr)( const SwTextNode&, SwAttrCheckArr&, SwPaM& );
|
|
|
|
static bool FindAttrsImpl(SwPaM & rSearchPam,
|
|
const SfxItemSet& rSet, bool bNoColls, SwMoveFnCollection const & fnMove,
|
|
const SwPaM & rRegion, bool bInReadOnly, bool bMoveFirst,
|
|
SwRootFrame const*const pLayout)
|
|
{
|
|
std::optional<SwPaM> oPam;
|
|
sw::MakeRegion(fnMove, rRegion, oPam);
|
|
|
|
bool bFound = false;
|
|
bool bFirst = true;
|
|
const bool bSrchForward = &fnMove == &fnMoveForward;
|
|
SwContentNode * pNode;
|
|
o3tl::sorted_vector<SwFormat*> aFormatArr;
|
|
|
|
// check which text/char attributes are searched
|
|
SwAttrCheckArr aCmpArr( rSet, bSrchForward, bNoColls );
|
|
SfxItemSetFixed<RES_PARATR_BEGIN, RES_GRFATR_END-1> aOtherSet( rSearchPam.GetDoc().GetAttrPool() );
|
|
aOtherSet.Put( rSet, false ); // got all invalid items
|
|
|
|
FnSearchAttr fnSearch = bSrchForward
|
|
? (&::lcl_SearchForward)
|
|
: (&::lcl_SearchBackward);
|
|
|
|
// if at beginning/end then move it out of the node
|
|
if( bMoveFirst &&
|
|
( bSrchForward
|
|
? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len()
|
|
: !oPam->GetPoint()->GetContentIndex() ) )
|
|
{
|
|
if( !(*fnMove.fnPos)( oPam->GetPoint(), false ))
|
|
{
|
|
return false;
|
|
}
|
|
SwContentNode *pNd = oPam->GetPointContentNode();
|
|
oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() );
|
|
}
|
|
|
|
while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
|
|
{
|
|
SwTextFrame const*const pFrame(pLayout && pNode->IsTextNode()
|
|
? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout))
|
|
: nullptr);
|
|
assert(!pLayout || !pNode->IsTextNode() || pFrame);
|
|
// sw_redlinehide: it's apparently not possible to find break items
|
|
// with the UI, so checking one node is enough
|
|
SwContentNode const& rPropsNode(*(pFrame
|
|
? pFrame->GetTextNodeForParaProps()
|
|
: pNode));
|
|
|
|
if( aCmpArr.Count() )
|
|
{
|
|
if( !pNode->IsTextNode() ) // CharAttr are only in text nodes
|
|
continue;
|
|
|
|
if (aOtherSet.Count() &&
|
|
!lcl_Search(rPropsNode, aOtherSet, bNoColls))
|
|
{
|
|
continue;
|
|
}
|
|
sw::MergedPara const*const pMergedPara(pFrame ? pFrame->GetMergedPara() : nullptr);
|
|
if (pMergedPara)
|
|
{
|
|
SwPosition const& rStart(*oPam->Start());
|
|
SwPosition const& rEnd(*oPam->End());
|
|
// no extents? fall back to searching index 0 of propsnode
|
|
// to find its node items
|
|
if (pMergedPara->extents.empty())
|
|
{
|
|
if (rStart.GetNodeIndex() <= rPropsNode.GetIndex()
|
|
&& rPropsNode.GetIndex() <= rEnd.GetNodeIndex())
|
|
{
|
|
SwPaM tmp(rPropsNode, 0, rPropsNode, 0);
|
|
bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, tmp);
|
|
if (bFound)
|
|
{
|
|
*oPam = tmp;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// iterate the extents, and intersect with input pPam:
|
|
// the found ranges should never include delete redlines
|
|
// so that subsequent Replace will not affect them
|
|
for (size_t i = 0; i < pMergedPara->extents.size(); ++i)
|
|
{
|
|
auto const rExtent(pMergedPara->extents[bSrchForward
|
|
? i
|
|
: pMergedPara->extents.size() - i - 1]);
|
|
if (rExtent.pNode->GetIndex() < rStart.GetNodeIndex()
|
|
|| rEnd.GetNodeIndex() < rExtent.pNode->GetIndex())
|
|
{
|
|
continue;
|
|
}
|
|
sal_Int32 const nStart(rExtent.pNode == &rStart.GetNode()
|
|
? rStart.GetContentIndex()
|
|
: 0);
|
|
if (rExtent.nEnd <= nStart)
|
|
{
|
|
continue;
|
|
}
|
|
sal_Int32 const nEnd(rExtent.pNode == &rEnd.GetNode()
|
|
? rEnd.GetContentIndex()
|
|
: rExtent.pNode->Len());
|
|
if (nEnd < rExtent.nStart
|
|
|| (nStart != nEnd && nEnd == rExtent.nStart))
|
|
{
|
|
continue;
|
|
}
|
|
SwPaM tmp(*rExtent.pNode, std::max(nStart, rExtent.nStart),
|
|
*rExtent.pNode, std::min(nEnd, rExtent.nEnd));
|
|
tmp.Normalize(bSrchForward);
|
|
bFound = (*fnSearch)(*rExtent.pNode, aCmpArr, tmp);
|
|
if (bFound)
|
|
{
|
|
*oPam = tmp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, *oPam);
|
|
}
|
|
if (bFound)
|
|
{
|
|
// set to the values of the attribute
|
|
rSearchPam.SetMark();
|
|
*rSearchPam.GetPoint() = *oPam->GetPoint();
|
|
*rSearchPam.GetMark() = *oPam->GetMark();
|
|
break;
|
|
}
|
|
continue; // text attribute
|
|
}
|
|
|
|
if( !aOtherSet.Count() )
|
|
continue;
|
|
|
|
// no hard attribution, so check if node was asked for this attr before
|
|
// (as an optimisation)
|
|
if (!rPropsNode.HasSwAttrSet())
|
|
{
|
|
SwFormat* pTmpFormat = rPropsNode.GetFormatColl();
|
|
if( !aFormatArr.insert( pTmpFormat ).second )
|
|
continue; // collection was requested earlier
|
|
}
|
|
|
|
if (lcl_Search(rPropsNode, aOtherSet, bNoColls))
|
|
{
|
|
// FORWARD: SPoint at the end, GetMark at the beginning of the node
|
|
// BACKWARD: SPoint at the beginning, GetMark at the end of the node
|
|
if (pFrame)
|
|
{
|
|
*rSearchPam.GetPoint() = *oPam->GetPoint();
|
|
rSearchPam.SetMark();
|
|
*rSearchPam.GetMark() = pFrame->MapViewToModelPos(
|
|
TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0));
|
|
}
|
|
else
|
|
{
|
|
*rSearchPam.GetPoint() = *oPam->GetPoint();
|
|
rSearchPam.SetMark();
|
|
if (bSrchForward)
|
|
{
|
|
rSearchPam.GetPoint()->SetContent(pNode->Len());
|
|
}
|
|
else
|
|
{
|
|
rSearchPam.GetPoint()->SetContent(0);
|
|
}
|
|
}
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// in search direction, mark precedes point, because the next iteration
|
|
// starts at point
|
|
if (bFound)
|
|
{
|
|
rSearchPam.Normalize(!bSrchForward);
|
|
}
|
|
|
|
return bFound;
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// parameters for search for attributes
|
|
struct SwFindParaAttr : public SwFindParas
|
|
{
|
|
bool m_bNoCollection;
|
|
const SfxItemSet *pSet, *pReplSet;
|
|
const i18nutil::SearchOptions2 *pSearchOpt;
|
|
SwCursor& m_rCursor;
|
|
SwRootFrame const* m_pLayout;
|
|
std::unique_ptr<utl::TextSearch> pSText;
|
|
|
|
SwFindParaAttr( const SfxItemSet& rSet, bool bNoCollection,
|
|
const i18nutil::SearchOptions2* pOpt, const SfxItemSet* pRSet,
|
|
SwCursor& rCursor, SwRootFrame const*const pLayout)
|
|
: m_bNoCollection(bNoCollection)
|
|
, pSet( &rSet )
|
|
, pReplSet( pRSet )
|
|
, pSearchOpt( pOpt )
|
|
, m_rCursor(rCursor)
|
|
, m_pLayout(pLayout)
|
|
{}
|
|
|
|
virtual ~SwFindParaAttr() {}
|
|
|
|
virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly,
|
|
std::unique_ptr<SvxSearchItem>& xSearchItem) override;
|
|
virtual bool IsReplaceMode() const override;
|
|
};
|
|
|
|
}
|
|
|
|
int SwFindParaAttr::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove,
|
|
const SwPaM & rRegion, bool bInReadOnly,
|
|
std::unique_ptr<SvxSearchItem>& xSearchItem)
|
|
{
|
|
// replace string (only if text given and search is not parameterized)?
|
|
bool bReplaceText = pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() ||
|
|
!pSet->Count() );
|
|
bool bReplaceAttr = pReplSet && pReplSet->Count();
|
|
bool bMoveFirst = !bReplaceAttr;
|
|
if( bInReadOnly && (bReplaceAttr || bReplaceText ))
|
|
bInReadOnly = false;
|
|
|
|
// We search for attributes, should we search for text as well?
|
|
{
|
|
SwPaM aRegion( *rRegion.GetMark(), *rRegion.GetPoint() );
|
|
SwPaM* pTextRegion = &aRegion;
|
|
SwPaM aSrchPam( *rCursor.GetPoint() );
|
|
|
|
while( true )
|
|
{
|
|
if( pSet->Count() ) // any attributes?
|
|
{
|
|
// first attributes
|
|
if (!FindAttrsImpl(aSrchPam, *pSet, m_bNoCollection, fnMove, aRegion, bInReadOnly, bMoveFirst, m_pLayout))
|
|
return FIND_NOT_FOUND;
|
|
bMoveFirst = true;
|
|
|
|
if( !pSearchOpt )
|
|
break; // ok, only attributes, so found
|
|
|
|
pTextRegion = &aSrchPam;
|
|
}
|
|
else if( !pSearchOpt )
|
|
return FIND_NOT_FOUND;
|
|
|
|
// then search in text of it
|
|
if( !pSText )
|
|
{
|
|
i18nutil::SearchOptions2 aTmp( *pSearchOpt );
|
|
|
|
// search in selection
|
|
aTmp.searchFlag |= (SearchFlags::REG_NOT_BEGINOFLINE |
|
|
SearchFlags::REG_NOT_ENDOFLINE);
|
|
|
|
aTmp.Locale = SvtSysLocale().GetLanguageTag().getLocale();
|
|
|
|
pSText.reset( new utl::TextSearch( aTmp ) );
|
|
}
|
|
|
|
// TODO: searching for attributes in Outliner text?!
|
|
|
|
// continue search in correct section (pTextRegion)
|
|
if (sw::FindTextImpl(aSrchPam, *pSearchOpt, false/*bSearchInNotes*/, *pSText, fnMove, *pTextRegion, bInReadOnly, m_pLayout, xSearchItem) &&
|
|
*aSrchPam.GetMark() != *aSrchPam.GetPoint() )
|
|
break; // found
|
|
else if( !pSet->Count() )
|
|
return FIND_NOT_FOUND; // only text and nothing found
|
|
|
|
*aRegion.GetMark() = *aSrchPam.GetPoint();
|
|
}
|
|
|
|
*rCursor.GetPoint() = *aSrchPam.GetPoint();
|
|
rCursor.SetMark();
|
|
*rCursor.GetMark() = *aSrchPam.GetMark();
|
|
}
|
|
|
|
if( bReplaceText )
|
|
{
|
|
const bool bRegExp(
|
|
SearchAlgorithms2::REGEXP == pSearchOpt->AlgorithmType2);
|
|
SwPosition& rSttCntPos = *rCursor.Start();
|
|
const sal_Int32 nSttCnt = rSttCntPos.GetContentIndex();
|
|
|
|
// add to shell-cursor-ring so that the regions will be moved eventually
|
|
SwPaM* pPrevRing(nullptr);
|
|
if( bRegExp )
|
|
{
|
|
pPrevRing = const_cast<SwPaM &>(rRegion).GetPrev();
|
|
const_cast<SwPaM &>(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() );
|
|
}
|
|
|
|
std::optional<OUString> xRepl;
|
|
if (bRegExp)
|
|
xRepl = sw::ReplaceBackReferences(*pSearchOpt, &rCursor, m_pLayout);
|
|
sw::ReplaceImpl(rCursor,
|
|
xRepl ? *xRepl : pSearchOpt->replaceString, bRegExp,
|
|
m_rCursor.GetDoc(), m_pLayout);
|
|
|
|
m_rCursor.SaveTableBoxContent( rCursor.GetPoint() );
|
|
|
|
if( bRegExp )
|
|
{
|
|
// and remove region again
|
|
SwPaM* p;
|
|
SwPaM* pNext = const_cast<SwPaM*>(&rRegion);
|
|
do {
|
|
p = pNext;
|
|
pNext = p->GetNext();
|
|
p->MoveTo(const_cast<SwPaM*>(&rRegion));
|
|
} while( p != pPrevRing );
|
|
}
|
|
rSttCntPos.SetContent(nSttCnt);
|
|
}
|
|
|
|
if( bReplaceAttr )
|
|
{
|
|
// is the selection still existent?
|
|
// all searched attributes are reset to default if
|
|
// they are not in ReplaceSet
|
|
if( !pSet->Count() )
|
|
{
|
|
rCursor.GetDoc().getIDocumentContentOperations().InsertItemSet(
|
|
rCursor, *pReplSet, SetAttrMode::DEFAULT, m_pLayout);
|
|
}
|
|
else
|
|
{
|
|
SfxItemPool* pPool = pReplSet->GetPool();
|
|
SfxItemSet aSet( *pPool, pReplSet->GetRanges() );
|
|
|
|
SfxItemIter aIter( *pSet );
|
|
const SfxPoolItem* pItem = aIter.GetCurItem();
|
|
do
|
|
{
|
|
// reset all that are not set with pool defaults
|
|
if( !IsInvalidItem( pItem ) && SfxItemState::SET !=
|
|
pReplSet->GetItemState( pItem->Which(), false ))
|
|
aSet.Put( pPool->GetUserOrPoolDefaultItem( pItem->Which() ));
|
|
|
|
pItem = aIter.NextItem();
|
|
} while (pItem);
|
|
aSet.Put( *pReplSet );
|
|
rCursor.GetDoc().getIDocumentContentOperations().InsertItemSet(
|
|
rCursor, aSet, SetAttrMode::DEFAULT, m_pLayout);
|
|
}
|
|
|
|
return FIND_NO_RING;
|
|
}
|
|
else
|
|
return FIND_FOUND;
|
|
}
|
|
|
|
bool SwFindParaAttr::IsReplaceMode() const
|
|
{
|
|
return ( pSearchOpt && !pSearchOpt->replaceString.isEmpty() ) ||
|
|
( pReplSet && pReplSet->Count() );
|
|
}
|
|
|
|
/// search for attributes
|
|
sal_Int32 SwCursor::FindAttrs( const SfxItemSet& rSet, bool bNoCollections,
|
|
SwDocPositions nStart, SwDocPositions nEnd,
|
|
bool& bCancel, FindRanges eFndRngs,
|
|
const i18nutil::SearchOptions2* pSearchOpt,
|
|
const SfxItemSet* pReplSet,
|
|
SwRootFrame const*const pLayout)
|
|
{
|
|
// switch off OLE-notifications
|
|
SwDoc& rDoc = GetDoc();
|
|
Link<bool,void> aLnk( rDoc.GetOle2Link() );
|
|
rDoc.SetOle2Link( Link<bool,void>() );
|
|
|
|
bool bReplace = ( pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() ||
|
|
!rSet.Count() ) ) ||
|
|
(pReplSet && pReplSet->Count());
|
|
bool const bStartUndo = rDoc.GetIDocumentUndoRedo().DoesUndo() && bReplace;
|
|
if (bStartUndo)
|
|
{
|
|
rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr );
|
|
}
|
|
|
|
SwFindParaAttr aSwFindParaAttr( rSet, bNoCollections, pSearchOpt,
|
|
pReplSet, *this, pLayout );
|
|
|
|
sal_Int32 nRet = FindAll( aSwFindParaAttr, nStart, nEnd, eFndRngs, bCancel );
|
|
rDoc.SetOle2Link( aLnk );
|
|
if( nRet && bReplace )
|
|
rDoc.getIDocumentState().SetModified();
|
|
|
|
if (bStartUndo)
|
|
{
|
|
rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, nullptr );
|
|
}
|
|
|
|
return nRet;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|