diff options
Diffstat (limited to 'sw/source/core/unocore/unoportenum.cxx')
-rw-r--r-- | sw/source/core/unocore/unoportenum.cxx | 1526 |
1 files changed, 1526 insertions, 0 deletions
diff --git a/sw/source/core/unocore/unoportenum.cxx b/sw/source/core/unocore/unoportenum.cxx new file mode 100644 index 0000000000..4b50a72cf5 --- /dev/null +++ b/sw/source/core/unocore/unoportenum.cxx @@ -0,0 +1,1526 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <utility> + +#include <unoport.hxx> +#include <unotext.hxx> +#include <IMark.hxx> +#include <crossrefbookmark.hxx> +#include <annotationmark.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <txatbase.hxx> +#include <txtatr.hxx> +#include <ndhints.hxx> +#include <ndtxt.hxx> +#include <unocrsr.hxx> +#include <docary.hxx> +#include <textboxhelper.hxx> +#include <tox.hxx> +#include <unoparaframeenum.hxx> +#include <unocrsrhelper.hxx> +#include <unorefmark.hxx> +#include <unobookmark.hxx> +#include <unofield.hxx> +#include <unometa.hxx> +#include <unolinebreak.hxx> +#include <unocontentcontrol.hxx> +#include <fmtfld.hxx> +#include <fldbas.hxx> +#include <fmtmeta.hxx> +#include <fmtanchr.hxx> +#include <fmtrfmrk.hxx> +#include <frmfmt.hxx> +#include <fmtflcnt.hxx> +#include <unoidx.hxx> +#include <unocoll.hxx> +#include <redline.hxx> +#include <txtannotationfld.hxx> +#include <vcl/svapp.hxx> +#include <comphelper/string.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/container/XEnumeration.hpp> +#include <algorithm> +#include <memory> +#include <set> +#include <stack> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::text; + +typedef std::pair< TextRangeList_t * const, SwTextAttr const * const > PortionList_t; +typedef std::stack< PortionList_t > PortionStack_t; + +static void lcl_CreatePortions( + TextRangeList_t & i_rPortions, + css::uno::Reference< SwXText > const& i_xParentText, + SwUnoCursor* pUnoCursor, + FrameClientSortList_t & i_rFrames, + const sal_Int32 i_nStartPos, const sal_Int32 i_nEndPos, bool bOnlyTextFields ); + +namespace +{ + enum class BkmType { + Start, End, StartEnd + }; + + struct SwXBookmarkPortion_Impl + { + Reference<XTextContent> xBookmark; + BkmType nBkmType; + const SwPosition aPosition; + + SwXBookmarkPortion_Impl(uno::Reference<text::XTextContent> xMark, + const BkmType nType, SwPosition _aPosition) + : xBookmark (std::move( xMark )) + , nBkmType ( nType ) + , aPosition (std::move( _aPosition )) + { + } + sal_Int32 getIndex () const + { + return aPosition.GetContentIndex(); + } + }; + typedef std::shared_ptr < SwXBookmarkPortion_Impl > SwXBookmarkPortion_ImplSharedPtr; + struct BookmarkCompareStruct + { + bool operator () ( const SwXBookmarkPortion_ImplSharedPtr &r1, + const SwXBookmarkPortion_ImplSharedPtr &r2 ) const + { + // #i16896# for bookmark portions at the same position, the start should + // always precede the end. Hence compare positions, and use bookmark type + // as tie-breaker for same position. + // return ( r1->nIndex == r2->nIndex ) + // ? ( r1->nBkmType < r2->nBkmType ) + // : ( r1->nIndex < r2->nIndex ); + + // Note that the above code does not correctly handle + // the case when one bookmark ends, and another begins in the same + // position. When this occurs, the above code will return the + // start of the 2nd bookmark BEFORE the end of the first bookmark + // See bug #i58438# for more details. The below code is correct and + // fixes both #i58438 and #i16896# + return r1->aPosition < r2->aPosition; + } + }; + typedef std::multiset < SwXBookmarkPortion_ImplSharedPtr, BookmarkCompareStruct > SwXBookmarkPortion_ImplList; + + /// Inserts pBkmk to rBkmArr in case it starts or ends at rOwnNode + void lcl_FillBookmark(sw::mark::IMark* const pBkmk, const SwNode& rOwnNode, SwDoc& rDoc, SwXBookmarkPortion_ImplList& rBkmArr) + { + bool const hasOther = pBkmk->IsExpanded(); + + const SwPosition& rStartPos = pBkmk->GetMarkStart(); + if(rStartPos.GetNode() == rOwnNode) + { + // #i109272#: cross reference marks: need special handling! + ::sw::mark::CrossRefBookmark *const pCrossRefMark(dynamic_cast< ::sw::mark::CrossRefBookmark*>(pBkmk)); + BkmType const nType = (hasOther || pCrossRefMark) + ? BkmType::Start : BkmType::StartEnd; + rBkmArr.insert(std::make_shared<SwXBookmarkPortion_Impl>( + SwXBookmark::CreateXBookmark(rDoc, pBkmk), + nType, rStartPos)); + } + + const SwPosition& rEndPos = pBkmk->GetMarkEnd(); + if(rEndPos.GetNode() != rOwnNode) + return; + + std::optional<SwPosition> oCrossRefEndPos; + const SwPosition* pEndPos = nullptr; + ::sw::mark::CrossRefBookmark *const pCrossRefMark(dynamic_cast< ::sw::mark::CrossRefBookmark*>(pBkmk)); + if(hasOther) + { + pEndPos = &rEndPos; + } + else if (pCrossRefMark) + { + // Crossrefbookmarks only remember the start position but have to span the whole paragraph + SwTextNode& rEndNd = *rEndPos.GetNode().GetTextNode(); + oCrossRefEndPos.emplace(rEndNd, rEndNd.Len()); + pEndPos = &*oCrossRefEndPos; + } + if(pEndPos) + { + rBkmArr.insert(std::make_shared<SwXBookmarkPortion_Impl>( + SwXBookmark::CreateXBookmark(rDoc, pBkmk), + BkmType::End, *pEndPos)); + } + } + + void lcl_FillBookmarkArray(SwDoc& rDoc, SwUnoCursor& rUnoCursor, SwXBookmarkPortion_ImplList& rBkmArr) + { + IDocumentMarkAccess* const pMarkAccess = rDoc.getIDocumentMarkAccess(); + if(!pMarkAccess->getBookmarksCount()) + return; + + SwTextNode* pTextNode = rUnoCursor.GetPoint()->GetNode().GetTextNode(); + assert(pTextNode); + // A text node already knows its marks via its SwIndexes. + o3tl::sorted_vector<const sw::mark::IMark*> aSeenMarks; + for (const SwContentIndex* pIndex = pTextNode->GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) + { + // Need a non-cost mark here, as we'll create a UNO wrapper around it. + sw::mark::IMark* pBkmk = const_cast<sw::mark::IMark*>(pIndex->GetMark()); + if (!pBkmk) + continue; + IDocumentMarkAccess::MarkType eType = IDocumentMarkAccess::GetType(*pBkmk); + // These are the types stored in the container otherwise accessible via getBookmarks*() + if (eType != IDocumentMarkAccess::MarkType::BOOKMARK && eType != IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK && + eType != IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK) + continue; + // Only handle bookmarks once, if they start and end at this node as well. + if (!aSeenMarks.insert(pBkmk).second) + continue; + lcl_FillBookmark(pBkmk, *pTextNode, rDoc, rBkmArr); + } + } + + struct SwAnnotationStartPortion_Impl + { + + uno::Reference< text::XTextField > mxAnnotationField; + const SwPosition maPosition; + + SwAnnotationStartPortion_Impl( + uno::Reference< text::XTextField > xAnnotationField, + SwPosition aPosition) + : mxAnnotationField (std::move( xAnnotationField )) + , maPosition (std::move( aPosition )) + { + } + + sal_Int32 getIndex () const + { + return maPosition.GetContentIndex(); + } + }; + typedef std::shared_ptr < SwAnnotationStartPortion_Impl > SwAnnotationStartPortion_ImplSharedPtr; + struct AnnotationStartCompareStruct + { + bool operator () ( const SwAnnotationStartPortion_ImplSharedPtr &r1, + const SwAnnotationStartPortion_ImplSharedPtr &r2 ) + const + { + return r1->maPosition < r2->maPosition; + } + }; + typedef std::multiset < SwAnnotationStartPortion_ImplSharedPtr, AnnotationStartCompareStruct > SwAnnotationStartPortion_ImplList; + + void lcl_FillAnnotationStartArray( + SwDoc& rDoc, + SwUnoCursor& rUnoCursor, + SwAnnotationStartPortion_ImplList& rAnnotationStartArr ) + { + IDocumentMarkAccess* const pMarkAccess = rDoc.getIDocumentMarkAccess(); + if ( pMarkAccess->getAnnotationMarksCount() == 0 ) + { + return; + } + + // no need to consider annotation marks starting after aEndOfPara + SwContentNode& rPtNd = *rUnoCursor.GetPoint()->GetNode().GetContentNode(); + SwPosition aEndOfPara( rPtNd, rPtNd.Len() ); + const IDocumentMarkAccess::const_iterator_t pCandidatesEnd = + pMarkAccess->findFirstAnnotationStartsAfter(aEndOfPara); + + // search for all annotation marks that have its start position in this paragraph + const SwNode& rOwnNode = rUnoCursor.GetPoint()->GetNode(); + for( IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getAnnotationMarksBegin(); + ppMark != pCandidatesEnd; + ++ppMark ) + { + ::sw::mark::AnnotationMark* const pAnnotationMark = + dynamic_cast< ::sw::mark::AnnotationMark* >(*ppMark); + + if (!pAnnotationMark) + continue; + + const SwPosition& rStartPos = pAnnotationMark->GetMarkStart(); + if (rStartPos.GetNode() != rOwnNode) + continue; + + const SwFormatField* pAnnotationFormatField = pAnnotationMark->GetAnnotationFormatField(); + if (!pAnnotationFormatField) + { + SAL_WARN("sw.core", "missing annotation format field"); + continue; + } + + rAnnotationStartArr.insert( + std::make_shared<SwAnnotationStartPortion_Impl>( + SwXTextField::CreateXTextField(&rDoc, + pAnnotationFormatField), + rStartPos)); + } + } +} + +OUString SwXTextPortionEnumeration::getImplementationName() +{ + return "SwXTextPortionEnumeration"; +} + +sal_Bool +SwXTextPortionEnumeration::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXTextPortionEnumeration::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextPortionEnumeration" }; +} + +SwXTextPortionEnumeration::SwXTextPortionEnumeration( + SwPaM& rParaCursor, + css::uno::Reference< SwXText > const & xParentText, + const sal_Int32 nStart, + const sal_Int32 nEnd, + bool bOnlyTextFields) +{ + m_pUnoCursor = rParaCursor.GetDoc().CreateUnoCursor(*rParaCursor.GetPoint()); + + OSL_ENSURE(nEnd == -1 || (nStart <= nEnd && + nEnd <= m_pUnoCursor->Start()->GetNode().GetTextNode()->GetText().getLength()), + "start or end value invalid!"); + + // find all frames, graphics and OLEs that are bound AT character in para + FrameClientSortList_t frames; + ::CollectFrameAtNode(m_pUnoCursor->GetPoint()->GetNode(), frames, true); + lcl_CreatePortions(m_Portions, xParentText, &*m_pUnoCursor, frames, nStart, nEnd, bOnlyTextFields); +} + +SwXTextPortionEnumeration::SwXTextPortionEnumeration( + SwPaM& rParaCursor, + TextRangeList_t && rPortions ) + : m_Portions( std::move(rPortions) ) +{ + m_pUnoCursor = rParaCursor.GetDoc().CreateUnoCursor(*rParaCursor.GetPoint()); +} + +SwXTextPortionEnumeration::~SwXTextPortionEnumeration() +{ + SolarMutexGuard aGuard; + if( m_pUnoCursor ) + { + m_pUnoCursor->GetDoc().cleanupUnoCursorTable(); + m_pUnoCursor.reset(nullptr); + } +} + +sal_Bool SwXTextPortionEnumeration::hasMoreElements() +{ + SolarMutexGuard aGuard; + + return !m_Portions.empty(); +} + +uno::Any SwXTextPortionEnumeration::nextElement() +{ + SolarMutexGuard aGuard; + + if (m_Portions.empty()) + throw container::NoSuchElementException(); + + Any any; + any <<= uno::Reference<XTextRange>(m_Portions.front()); + m_Portions.pop_front(); + return any; +} + +static void +lcl_FillFieldMarkArray(std::deque<sal_Int32> & rFieldMarks, SwUnoCursor const & rUnoCursor, + const sal_Int32 i_nStartPos) +{ + const SwTextNode * const pTextNode = + rUnoCursor.GetPoint()->GetNode().GetTextNode(); + if (!pTextNode) return; + + const sal_Unicode fld[] = { + CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDSEP, CH_TXT_ATR_FIELDEND, CH_TXT_ATR_FORMELEMENT, 0 }; + sal_Int32 pos = std::max(static_cast<sal_Int32>(0), i_nStartPos); + while ((pos = ::comphelper::string::indexOfAny(pTextNode->GetText(), fld, pos)) != -1) + { + rFieldMarks.push_back(pos); + ++pos; + } +} + +static rtl::Reference<SwXTextPortion> +lcl_ExportFieldMark( + uno::Reference< text::XText > const & i_xParentText, + SwUnoCursor * const pUnoCursor, + const SwTextNode * const pTextNode ) +{ + rtl::Reference<SwXTextPortion> pPortion; + SwDoc& rDoc = pUnoCursor->GetDoc(); + // maybe it's a good idea to add a special hint to the hints array and rely on the hint segmentation... + const sal_Int32 start = pUnoCursor->Start()->GetContentIndex(); + OSL_ENSURE(pUnoCursor->End()->GetContentIndex() == start, + "hmm --- why is this different"); + + pUnoCursor->Right(1); + if ( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + { + OSL_FAIL("cannot move cursor?"); + return nullptr; + } + + const sal_Unicode Char = pTextNode->GetText()[start]; + if (CH_TXT_ATR_FIELDSTART == Char) + { + ::sw::mark::IFieldmark* pFieldmark = nullptr; + pFieldmark = rDoc.getIDocumentMarkAccess()-> + getFieldmarkAt(*pUnoCursor->GetMark()); + pPortion = new SwXTextPortion( + pUnoCursor, i_xParentText, PORTION_FIELD_START); + if (pFieldmark) + { + pPortion->SetBookmark( + SwXFieldmark::CreateXFieldmark(rDoc, pFieldmark)); + } + } + else if (CH_TXT_ATR_FIELDSEP == Char) + { + // TODO how to get the field? + pPortion = new SwXTextPortion( + pUnoCursor, i_xParentText, PORTION_FIELD_SEP); + } + else if (CH_TXT_ATR_FIELDEND == Char) + { + ::sw::mark::IFieldmark* pFieldmark = nullptr; + pFieldmark = rDoc.getIDocumentMarkAccess()-> + getFieldmarkAt(*pUnoCursor->GetMark()); + pPortion = new SwXTextPortion( + pUnoCursor, i_xParentText, PORTION_FIELD_END); + if (pFieldmark) + { + pPortion->SetBookmark( + SwXFieldmark::CreateXFieldmark(rDoc, pFieldmark)); + } + } + else if (CH_TXT_ATR_FORMELEMENT == Char) + { + ::sw::mark::IFieldmark* pFieldmark = + rDoc.getIDocumentMarkAccess()->getFieldmarkAt(*pUnoCursor->GetMark()); + pPortion = new SwXTextPortion( + pUnoCursor, i_xParentText, PORTION_FIELD_START_END); + if (pFieldmark) + { + pPortion->SetBookmark( + SwXFieldmark::CreateXFieldmark(rDoc, pFieldmark)); + } + } + else + { + OSL_FAIL("no fieldmark found?"); + } + return pPortion; +} + +static rtl::Reference<SwXTextPortion> +lcl_CreateRefMarkPortion( + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + const SwTextAttr & rAttr, const bool bEnd) +{ + SwDoc& rDoc = pUnoCursor->GetDoc(); + SwFormatRefMark& rRefMark = const_cast<SwFormatRefMark&>( + static_cast<const SwFormatRefMark&>(rAttr.GetAttr())); + Reference<XTextContent> xContent; + if (!xContent.is()) + { + xContent = SwXReferenceMark::CreateXReferenceMark(rDoc, &rRefMark); + } + + rtl::Reference<SwXTextPortion> pPortion; + if (!bEnd) + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_REFMARK_START); + pPortion->SetRefMark(xContent); + pPortion->SetCollapsed(rAttr.End() == nullptr); + } + else + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_REFMARK_END); + pPortion->SetRefMark(xContent); + } + return pPortion; +} + +static void +lcl_InsertRubyPortion( + TextRangeList_t & rPortions, + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + const SwTextAttr & rAttr, const bool bEnd) +{ + rtl::Reference<SwXTextPortion> pPortion = new SwXTextPortion(pUnoCursor, + static_txtattr_cast<const SwTextRuby&>(rAttr), xParent, bEnd); + rPortions.emplace_back(pPortion); + pPortion->SetCollapsed(rAttr.End() == nullptr); +} + +static rtl::Reference<SwXTextPortion> +lcl_CreateTOXMarkPortion( + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwTextAttr & rAttr, const bool bEnd) +{ + SwDoc& rDoc = pUnoCursor->GetDoc(); + SwTOXMark & rTOXMark = static_cast<SwTOXMark&>(rAttr.GetAttr()); + + const Reference<XTextContent> xContent = + SwXDocumentIndexMark::CreateXDocumentIndexMark(rDoc, & rTOXMark); + + rtl::Reference<SwXTextPortion> pPortion; + if (!bEnd) + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_TOXMARK_START); + pPortion->SetTOXMark(xContent); + pPortion->SetCollapsed(rAttr.GetEnd() == nullptr); + } + else + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_TOXMARK_END); + pPortion->SetTOXMark(xContent); + } + return pPortion; +} + +static rtl::Reference<SwXTextPortion> +lcl_CreateMetaPortion( + css::uno::Reference<SwXText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwTextAttr & rAttr, std::unique_ptr<TextRangeList_t const> && pPortions) +{ + const rtl::Reference<SwXMeta> xMeta( SwXMeta::CreateXMeta( + *static_cast<SwFormatMeta &>(rAttr.GetAttr()).GetMeta(), + xParent, std::move(pPortions))); + rtl::Reference<SwXTextPortion> pPortion; + if (RES_TXTATR_META == rAttr.Which()) + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_META); + pPortion->SetMeta(xMeta); + } + else + { + const uno::Reference<text::XTextField> xField(static_cast<cppu::OWeakObject*>(xMeta.get()), uno::UNO_QUERY); + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_FIELD); + pPortion->SetTextField(xField); + } + return pPortion; +} + +/// Creates a text portion that has a non-empty ContentControl property. +static rtl::Reference<SwXTextPortion> +lcl_CreateContentControlPortion(const css::uno::Reference<SwXText>& xParent, + const SwUnoCursor* pUnoCursor, SwTextAttr& rAttr, + std::unique_ptr<const TextRangeList_t>&& pPortions) +{ + rtl::Reference<SwXContentControl> xContentControl = SwXContentControl::CreateXContentControl( + *static_cast<SwFormatContentControl&>(rAttr.GetAttr()).GetContentControl(), xParent, + std::move(pPortions)); + rtl::Reference<SwXTextPortion> pPortion; + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_CONTENT_CONTROL); + pPortion->SetContentControl(xContentControl); + return pPortion; +} + +/** + * Exports all bookmarks from rBkmArr into rPortions that have the same start + * or end position as nIndex. + * + * @param rBkmArr the array of bookmarks. If bOnlyFrameStarts is true, then + * this is only read, otherwise consumed entries are removed. + * + * @param rFramePositions the list of positions where there is an at-char / + * anchored frame. + * + * @param bOnlyFrameStarts If true: export only the start of the bookmarks + * which cover an at-char anchored frame. If false: export the end of the same + * bookmarks and everything else. + */ +static void lcl_ExportBookmark( + TextRangeList_t & rPortions, + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwXBookmarkPortion_ImplList& rBkmArr, + const sal_Int32 nIndex, + const o3tl::sorted_vector<sal_Int32>& rFramePositions, + bool bOnlyFrameStarts) +{ + for ( SwXBookmarkPortion_ImplList::iterator aIter = rBkmArr.begin(), aEnd = rBkmArr.end(); aIter != aEnd; ) + { + const SwXBookmarkPortion_ImplSharedPtr& pPtr = *aIter; + if ( nIndex > pPtr->getIndex() ) + { + if (bOnlyFrameStarts) + ++aIter; + else + aIter = rBkmArr.erase(aIter); + continue; + } + if ( nIndex < pPtr->getIndex() ) + break; + + if ((BkmType::Start == pPtr->nBkmType && bOnlyFrameStarts) || + (BkmType::StartEnd == pPtr->nBkmType)) + { + bool bFrameStart = rFramePositions.find(nIndex) != rFramePositions.end(); + bool bEnd = pPtr->nBkmType == BkmType::StartEnd && bFrameStart && !bOnlyFrameStarts; + if (pPtr->nBkmType == BkmType::Start || bFrameStart || !bOnlyFrameStarts) + { + // At this we create a text portion, due to one of these + // reasons: + // - this is the real start of a non-collapsed bookmark + // - this is the real position of a collapsed bookmark + // - this is the start or end (depending on bOnlyFrameStarts) + // of a collapsed bookmark at the same position as an at-char + // anchored frame + rtl::Reference<SwXTextPortion> pPortion = + new SwXTextPortion(pUnoCursor, xParent, bEnd ? PORTION_BOOKMARK_END : PORTION_BOOKMARK_START); + rPortions.emplace_back(pPortion); + pPortion->SetBookmark(pPtr->xBookmark); + pPortion->SetCollapsed( BkmType::StartEnd == pPtr->nBkmType && !bFrameStart ); + } + } + else if (BkmType::End == pPtr->nBkmType && !bOnlyFrameStarts) + { + rtl::Reference<SwXTextPortion> pPortion = + new SwXTextPortion(pUnoCursor, xParent, PORTION_BOOKMARK_END); + rPortions.emplace_back(pPortion); + pPortion->SetBookmark(pPtr->xBookmark); + } + + // next bookmark + if (bOnlyFrameStarts) + ++aIter; + else + aIter = rBkmArr.erase(aIter); + } +} + +static void lcl_ExportSoftPageBreak( + TextRangeList_t & rPortions, + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwSoftPageBreakList& rBreakArr, + const sal_Int32 nIndex) +{ + for ( auto aIter = rBreakArr.begin(); aIter != rBreakArr.end(); ) + { + if ( nIndex > *aIter ) + { + aIter = rBreakArr.erase(aIter); + continue; + } + if ( nIndex < *aIter ) + break; + + rPortions.push_back( + new SwXTextPortion(pUnoCursor, xParent, PORTION_SOFT_PAGEBREAK) ); + aIter = rBreakArr.erase(aIter); + } +} + +namespace { + +struct SwXRedlinePortion_Impl +{ + const SwRangeRedline* m_pRedline; + const bool m_bStart; + + SwXRedlinePortion_Impl ( const SwRangeRedline* pRed, const bool bIsStart ) + : m_pRedline(pRed) + , m_bStart(bIsStart) + { + } + + sal_Int32 getRealIndex () const + { + return m_bStart ? m_pRedline->Start()->GetContentIndex() + : m_pRedline->End() ->GetContentIndex(); + } +}; + +} + +typedef std::shared_ptr < SwXRedlinePortion_Impl > + SwXRedlinePortion_ImplSharedPtr; + +namespace { + +struct RedlineCompareStruct +{ + static const SwPosition& getPosition ( const SwXRedlinePortion_ImplSharedPtr &r ) + { + return *(r->m_bStart ? r->m_pRedline->Start() : r->m_pRedline->End()); + } + + bool operator () ( const SwXRedlinePortion_ImplSharedPtr &r1, + const SwXRedlinePortion_ImplSharedPtr &r2 ) const + { + return getPosition ( r1 ) < getPosition ( r2 ); + } +}; + +} + +typedef std::multiset < SwXRedlinePortion_ImplSharedPtr, RedlineCompareStruct > +SwXRedlinePortion_ImplList; + +static rtl::Reference<SwXTextPortion> +lcl_ExportHints( + PortionStack_t & rPortionStack, + const css::uno::Reference<SwXText> & xParent, + SwUnoCursor * const pUnoCursor, + SwpHints const * const pHints, + const sal_Int32 i_nStartPos, + const sal_Int32 i_nEndPos, + const sal_Int32 nCurrentIndex, + const bool bRightMoveForbidden, + bool & o_rbCursorMoved, + sal_Int32 & o_rNextAttrPosition) +{ + // if the attribute has a dummy character, then xRef is set (except META and CONTENT_CONTROL) + // otherwise, the portion for the attribute is inserted into rPortions! + rtl::Reference<SwXTextPortion> pPortion; + SwDoc& rDoc = pUnoCursor->GetDoc(); + //search for special text attributes - first some ends + size_t nEndIndex = 0; + const auto nHintsCount = pHints->Count(); + for (;;) + { + if (nEndIndex >= nHintsCount) + break; + SwTextAttr * const pAttr = pHints->GetSortedByEnd(nEndIndex); + const sal_Int32* pAttrEnd = pAttr->GetEnd(); + sal_Int32 nNextEnd = 0; + if (pAttrEnd && + nCurrentIndex < (nNextEnd = (*pAttrEnd))) + break; + if(pAttrEnd) + { + if (nNextEnd == nCurrentIndex) + { + const sal_uInt16 nWhich( pAttr->Which() ); + switch (nWhich) + { + case RES_TXTATR_TOXMARK: + { + rtl::Reference<SwXTextPortion> xTmp = lcl_CreateTOXMarkPortion( + xParent, pUnoCursor, *pAttr, true); + rPortionStack.top().first->push_back(xTmp); + } + break; + case RES_TXTATR_REFMARK: + { + rtl::Reference<SwXTextPortion> xTmp = lcl_CreateRefMarkPortion( + xParent, pUnoCursor, *pAttr, true); + rPortionStack.top().first->push_back(xTmp); + } + break; + case RES_TXTATR_CJK_RUBY: + //#i91534# GetEnd() == 0 mixes the order of ruby start/end + if( *pAttr->GetEnd() == pAttr->GetStart()) + { + lcl_InsertRubyPortion( *rPortionStack.top().first, + xParent, pUnoCursor, *pAttr, false); + } + lcl_InsertRubyPortion( *rPortionStack.top().first, + xParent, pUnoCursor, *pAttr, true); + break; + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + { + OSL_ENSURE(pAttr->GetStart() != *pAttr->GetEnd(), + "empty meta?"); + if ((i_nStartPos > 0) && + (pAttr->GetStart() < i_nStartPos)) + { + // force skip pAttr and rest of attribute ends + // at nCurrentIndex + // because they are not contained in the meta pAttr + // and the meta pAttr itself is outside selection! + // (necessary for SwXMeta::createEnumeration) + if (pAttr->GetStart() + 1 == i_nStartPos) + { + nEndIndex = pHints->Count() - 1; + } + break; + } + PortionList_t Top = rPortionStack.top(); + if (Top.second != pAttr) + { + OSL_FAIL("ExportHints: stack error" ); + } + else + { + std::unique_ptr<const TextRangeList_t> + pCurrentPortions(Top.first); + rPortionStack.pop(); + rtl::Reference<SwXTextPortion> xPortion( + lcl_CreateMetaPortion(xParent, pUnoCursor, + *pAttr, std::move(pCurrentPortions))); + rPortionStack.top().first->push_back(xPortion); + } + } + break; + case RES_TXTATR_CONTENTCONTROL: + { + if (pAttr->GetStart() == *pAttr->GetEnd()) + { + SAL_WARN("sw.core", "lcl_ExportHints: empty content control"); + } + if ((i_nStartPos > 0) && (pAttr->GetStart() < i_nStartPos)) + { + // If the start pos is the start of the content of the content control, + // skip it: it'll be handled in SwXContentControl::createEnumeration(). + if (pAttr->GetStart() + 1 == i_nStartPos) + { + nEndIndex = pHints->Count() - 1; + } + break; + } + PortionList_t Top = rPortionStack.top(); + if (Top.second != pAttr) + { + SAL_WARN("sw.core", "lcl_ExportHints: content control is not at the " + "top of the portion stack"); + } + else + { + std::unique_ptr<const TextRangeList_t> pCurrentPortions(Top.first); + rPortionStack.pop(); + rtl::Reference<SwXTextPortion> xPortion( + lcl_CreateContentControlPortion(xParent, pUnoCursor, *pAttr, + std::move(pCurrentPortions))); + rPortionStack.top().first->push_back(xPortion); + } + } + break; + } + } + } + nEndIndex++; + } + + // then some starts + size_t nStartIndex = 0; + sal_Int32 nNextStart = 0; + while(nStartIndex < nHintsCount && + nCurrentIndex >= (nNextStart = pHints->Get(nStartIndex)->GetStart())) + { + SwTextAttr * const pAttr = pHints->Get(nStartIndex); + sal_uInt16 nAttrWhich = pAttr->Which(); + if (nNextStart == nCurrentIndex) + { + switch( nAttrWhich ) + { + case RES_TXTATR_FIELD: + if(!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; + pPortion = new SwXTextPortion( + pUnoCursor, xParent, PORTION_FIELD); + Reference<XTextField> const xField = + SwXTextField::CreateXTextField(&rDoc, + &pAttr->GetFormatField()); + pPortion->SetTextField(xField); + } + break; + + case RES_TXTATR_ANNOTATION: + if(!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; + + const SwTextAnnotationField* pTextAnnotationField = dynamic_cast<const SwTextAnnotationField*>( pAttr ); + ::sw::mark::IMark* pAnnotationMark = pTextAnnotationField ? pTextAnnotationField->GetAnnotationMark() : nullptr; + if ( pAnnotationMark != nullptr ) + { + pPortion = new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION_END ); + pPortion->SetBookmark(SwXBookmark::CreateXBookmark( + rDoc, pAnnotationMark)); + } + else + { + pPortion = new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION ); + Reference<XTextField> xField = + SwXTextField::CreateXTextField(&rDoc, + &pAttr->GetFormatField()); + pPortion->SetTextField(xField); + } + } + break; + + case RES_TXTATR_INPUTFIELD: + if(!bRightMoveForbidden) + { + + pUnoCursor->Right( + pAttr->GetFormatField().GetField()->ExpandField(true, nullptr).getLength() + 2 ); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; + pPortion = new SwXTextPortion( pUnoCursor, xParent, PORTION_FIELD); + Reference<XTextField> xField = + SwXTextField::CreateXTextField(&rDoc, + &pAttr->GetFormatField()); + pPortion->SetTextField(xField); + } + break; + + case RES_TXTATR_FLYCNT: + if(!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; // Robust #i81708# content in covered cells + + // Do not expose inline anchored textboxes. + if (SwTextBoxHelper::isTextBox(pAttr->GetFlyCnt().GetFrameFormat(), RES_FLYFRMFMT)) + break; + + pUnoCursor->Exchange(); + pPortion = new SwXTextPortion( pUnoCursor, xParent, PORTION_FRAME); + } + break; + + case RES_TXTATR_FTN: + { + if(!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; + pPortion = new SwXTextPortion( + pUnoCursor, xParent, PORTION_FOOTNOTE); + Reference<XFootnote> xContent = + SwXFootnotes::GetObject(rDoc, pAttr->GetFootnote()); + pPortion->SetFootnote(xContent); + } + } + break; + + case RES_TXTATR_TOXMARK: + case RES_TXTATR_REFMARK: + { + bool bIsPoint = !(pAttr->GetEnd()); + if (!bRightMoveForbidden || !bIsPoint) + { + if (bIsPoint) + { + pUnoCursor->Right(1); + } + rtl::Reference<SwXTextPortion> xTmp = + (RES_TXTATR_REFMARK == nAttrWhich) + ? lcl_CreateRefMarkPortion( + xParent, pUnoCursor, *pAttr, false) + : lcl_CreateTOXMarkPortion( + xParent, pUnoCursor, *pAttr, false); + if (bIsPoint) // consume CH_TXTATR! + { + pUnoCursor->Normalize(false); + pUnoCursor->DeleteMark(); + pPortion = xTmp; + } + else // just insert it + { + rPortionStack.top().first->push_back(xTmp); + } + } + } + break; + case RES_TXTATR_CJK_RUBY: + //#i91534# GetEnd() == 0 mixes the order of ruby start/end + if(pAttr->GetEnd() && (*pAttr->GetEnd() != pAttr->GetStart())) + { + lcl_InsertRubyPortion( *rPortionStack.top().first, + xParent, pUnoCursor, *pAttr, false); + } + break; + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + case RES_TXTATR_CONTENTCONTROL: + if (pAttr->GetStart() != *pAttr->GetEnd()) + { + if (!bRightMoveForbidden) + { + pUnoCursor->Right(1); + o_rbCursorMoved = true; + // only if the end is included in selection! + if ((i_nEndPos < 0) || + (*pAttr->GetEnd() <= i_nEndPos)) + { + rPortionStack.push( std::make_pair( + new TextRangeList_t, pAttr )); + } + } + } + break; + case RES_TXTATR_LINEBREAK: + if (!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if (*pUnoCursor->GetMark() == *pUnoCursor->GetPoint()) + break; + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_LINEBREAK); + rtl::Reference<SwXLineBreak> xLineBreak + = SwXLineBreak::CreateXLineBreak( + &const_cast<SwFormatLineBreak&>(pAttr->GetLineBreak())); + pPortion->SetLineBreak(xLineBreak); + } + break; + case RES_TXTATR_AUTOFMT: + case RES_TXTATR_INETFMT: + case RES_TXTATR_CHARFMT: + break; // these are handled as properties of a "Text" portion + default: + OSL_FAIL("unknown attribute"); + break; + } + } + nStartIndex++; + } + + if (pPortion.is()) // implies that we have moved the cursor + { + o_rbCursorMoved = true; + } + if (!o_rbCursorMoved) + { + // search for attribute changes behind the current cursor position + // break up at frames, bookmarks, redlines + + nStartIndex = 0; + nNextStart = 0; + while(nStartIndex < pHints->Count() && + nCurrentIndex >= (nNextStart = pHints->Get(nStartIndex)->GetStart())) + nStartIndex++; + + nEndIndex = 0; + sal_Int32 nNextEnd = 0; + while(nEndIndex < pHints->Count() && + nCurrentIndex >= (nNextEnd = pHints->GetSortedByEnd(nEndIndex)->GetAnyEnd())) + nEndIndex++; + + sal_Int32 nNextPos = + ((nNextStart > nCurrentIndex) && (nNextStart < nNextEnd)) + ? nNextStart : nNextEnd; + if (nNextPos > nCurrentIndex) + { + o_rNextAttrPosition = nNextPos; + } + } + return pPortion; +} + +static void lcl_MoveCursor( SwUnoCursor * const pUnoCursor, + const sal_Int32 nCurrentIndex, + const sal_Int32 nNextFrameIndex, + const sal_Int32 nNextPortionIndex, + const sal_Int32 nNextAttrIndex, + const sal_Int32 nNextMarkIndex, + const sal_Int32 nEndPos ) +{ + sal_Int32 nMovePos = pUnoCursor->GetPointContentNode()->Len(); + + if ((nEndPos >= 0) && (nEndPos < nMovePos)) + { + nMovePos = nEndPos; + } + + if ((nNextFrameIndex >= 0) && (nNextFrameIndex < nMovePos)) + { + nMovePos = nNextFrameIndex; + } + + if ((nNextPortionIndex >= 0) && (nNextPortionIndex < nMovePos)) + { + nMovePos = nNextPortionIndex; + } + + if ((nNextAttrIndex >= 0) && (nNextAttrIndex < nMovePos)) + { + nMovePos = nNextAttrIndex; + } + + if ((nNextMarkIndex >= 0) && (nNextMarkIndex < nMovePos)) + { + nMovePos = nNextMarkIndex; + } + + if (nMovePos > nCurrentIndex) + { + pUnoCursor->GetPoint()->SetContent( nMovePos ); + } +} + +static void lcl_FillRedlineArray( + SwDoc const & rDoc, + SwUnoCursor const & rUnoCursor, + SwXRedlinePortion_ImplList& rRedArr ) +{ + const SwRedlineTable& rRedTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + const size_t nRedTableCount = rRedTable.size(); + + if ( nRedTableCount <= 0 ) + return; + + const SwPosition* pStart = rUnoCursor.GetPoint(); + const SwNode& rOwnNode = pStart->GetNode(); + + SwRedlineTable::size_type nRed = rDoc.getIDocumentRedlineAccess().GetRedlinePos(rOwnNode, RedlineType::Any); + for(; nRed < nRedTableCount; ++nRed) + { + const SwRangeRedline* pRedline = rRedTable[nRed]; + auto [pRedStart, pRedEnd]= pRedline->StartEnd(); + if ( rOwnNode == pRedStart->GetNode() ) + rRedArr.insert( std::make_shared<SwXRedlinePortion_Impl>( + pRedline, true ) ); + if( pRedline->HasMark() && pRedEnd->GetNode() == rOwnNode ) + rRedArr.insert( std::make_shared<SwXRedlinePortion_Impl>( + pRedline, false ) ); + } +} + +static void lcl_FillSoftPageBreakArray( + SwUnoCursor const & rUnoCursor, + SwSoftPageBreakList& rBreakArr ) +{ + const SwTextNode *pTextNode = + rUnoCursor.GetPoint()->GetNode().GetTextNode(); + if( pTextNode ) + pTextNode->fillSoftPageBreakList( rBreakArr ); +} + +static void lcl_ExportRedline( + TextRangeList_t & rPortions, + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwXRedlinePortion_ImplList& rRedlineArr, + const sal_Int32 nIndex) +{ + + // We want this loop to iterate over all red lines in this + // array. We will only insert the ones with index matches + for ( SwXRedlinePortion_ImplList::iterator aIter = rRedlineArr.begin(), aEnd = rRedlineArr.end(); + aIter != aEnd; ) + { + SwXRedlinePortion_ImplSharedPtr pPtr = *aIter; + sal_Int32 nRealIndex = pPtr->getRealIndex(); + // If there are elements before nIndex, remove them + if ( nIndex > nRealIndex ) + aIter = rRedlineArr.erase(aIter); + // If the elements match, and them to the list + else if ( nIndex == nRealIndex ) + { + rPortions.push_back( new SwXRedlinePortion( + *pPtr->m_pRedline, pUnoCursor, xParent, pPtr->m_bStart)); + aIter = rRedlineArr.erase(aIter); + } + // If we've iterated past nIndex, exit the loop + else + break; + } +} + +static void lcl_ExportBkmAndRedline( + TextRangeList_t & rPortions, + Reference<XText> const & xParent, + const SwUnoCursor * const pUnoCursor, + SwXBookmarkPortion_ImplList& rBkmArr, + SwXRedlinePortion_ImplList& rRedlineArr, + SwSoftPageBreakList& rBreakArr, + const sal_Int32 nIndex, + const o3tl::sorted_vector<sal_Int32>& rFramePositions, + bool bOnlyFrameBookmarkStarts) +{ + if (!rBkmArr.empty()) + lcl_ExportBookmark(rPortions, xParent, pUnoCursor, rBkmArr, nIndex, rFramePositions, + bOnlyFrameBookmarkStarts); + + if (bOnlyFrameBookmarkStarts) + // Only exporting the start of some collapsed bookmarks: no export of + // other arrays. + return; + + if (!rRedlineArr.empty()) + lcl_ExportRedline(rPortions, xParent, pUnoCursor, rRedlineArr, nIndex); + + if (!rBreakArr.empty()) + lcl_ExportSoftPageBreak(rPortions, xParent, pUnoCursor, rBreakArr, nIndex); +} + +/** + * Exports all start annotation marks from rAnnotationStartArr into rPortions that have the same + * start position as nIndex. + * + * @param rAnnotationStartArr the array of annotation marks. Consumed entries are removed. + * + * @param rFramePositions the list of positions where there is an at-char anchored frame. + * + * @param bOnlyFrame If true: export only the start of annotation marks which cover an at-char + * anchored frame. If false: export everything else. + */ +static void lcl_ExportAnnotationStarts( + TextRangeList_t & rPortions, + Reference<XText> const & xParent, + const SwUnoCursor * const pUnoCursor, + SwAnnotationStartPortion_ImplList& rAnnotationStartArr, + const sal_Int32 nIndex, + const o3tl::sorted_vector<sal_Int32>& rFramePositions, + bool bOnlyFrame) +{ + for ( SwAnnotationStartPortion_ImplList::iterator aIter = rAnnotationStartArr.begin(), aEnd = rAnnotationStartArr.end(); + aIter != aEnd; ) + { + SwAnnotationStartPortion_ImplSharedPtr pPtr = *aIter; + if ( nIndex > pPtr->getIndex() ) + { + aIter = rAnnotationStartArr.erase(aIter); + continue; + } + if ( pPtr->getIndex() > nIndex ) + { + break; + } + + bool bFrameStart = rFramePositions.find(nIndex) != rFramePositions.end(); + if (bFrameStart || !bOnlyFrame) + { + rtl::Reference<SwXTextPortion> pPortion = + new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION ); + pPortion->SetTextField( pPtr->mxAnnotationField ); + rPortions.emplace_back(pPortion); + + aIter = rAnnotationStartArr.erase(aIter); + } + else + ++aIter; + } +} + +/// Fills character positions from rFrames into rFramePositions. +static void lcl_ExtractFramePositions(FrameClientSortList_t& rFrames, sal_Int32 nCurrentIndex, + o3tl::sorted_vector<sal_Int32>& rFramePositions) +{ + for (const auto& rFrame : rFrames) + { + if (rFrame.nIndex < nCurrentIndex) + continue; + + if (rFrame.nIndex > nCurrentIndex) + break; + + const auto pFrame = static_cast<const SwFrameFormat*>(rFrame.pFrameClient->GetRegisteredIn()); + if (!pFrame) + continue; + + auto& rFormat = *const_cast<SwFrameFormat*>(pFrame); + const SwFormatAnchor& rAnchor = rFormat.GetAnchor(); + if (!rAnchor.GetAnchorNode()) + continue; + + rFramePositions.insert(rAnchor.GetAnchorContentOffset()); + } +} + +/** + * Exports at-char anchored frames. + * + * @param i_rFrames the frames for this paragraph, frames at <= i_nCurrentIndex + * are removed from the container. + */ +static sal_Int32 lcl_ExportFrames( + TextRangeList_t & rPortions, + Reference<XText> const & i_xParent, + SwUnoCursor const * const i_pUnoCursor, + FrameClientSortList_t & i_rFrames, + sal_Int32 const i_nCurrentIndex) +{ + // Ignore frames which are not exported, as we are exporting a selection + // and they are anchored before the start of the selection. + while (!i_rFrames.empty() && i_rFrames.front().nIndex < i_nCurrentIndex) + i_rFrames.pop_front(); + + // find first Frame in (sorted) i_rFrames at current position + while (!i_rFrames.empty() && (i_rFrames.front().nIndex == i_nCurrentIndex)) + // do not check for i_nEnd here; this is done implicitly by lcl_MoveCursor + { + auto pFrame = static_cast<SwFrameFormat*>(i_rFrames.front().pFrameClient->GetRegisteredIn()); + if (pFrame) // Frame could be disposed + { + rtl::Reference<SwXTextPortion> pPortion = new SwXTextPortion(i_pUnoCursor, i_xParent, *pFrame ); + rPortions.emplace_back(pPortion); + } + i_rFrames.pop_front(); + } + + return !i_rFrames.empty() ? i_rFrames.front().nIndex : -1; +} + +static sal_Int32 lcl_GetNextIndex( + SwXBookmarkPortion_ImplList const & rBkmArr, + SwXRedlinePortion_ImplList const & rRedlineArr, + SwSoftPageBreakList const & rBreakArr ) +{ + sal_Int32 nRet = -1; + if(!rBkmArr.empty()) + { + SwXBookmarkPortion_ImplSharedPtr pPtr = *rBkmArr.begin(); + nRet = pPtr->getIndex(); + } + if(!rRedlineArr.empty()) + { + SwXRedlinePortion_ImplSharedPtr pPtr = *rRedlineArr.begin(); + sal_Int32 nTmp = pPtr->getRealIndex(); + if(nRet < 0 || nTmp < nRet) + nRet = nTmp; + } + if(!rBreakArr.empty()) + { + if(nRet < 0 || *rBreakArr.begin() < nRet) + nRet = *rBreakArr.begin(); + } + return nRet; +}; + +static void lcl_CreatePortions( + TextRangeList_t & i_rPortions, + css::uno::Reference< SwXText > const & i_xParentText, + SwUnoCursor * const pUnoCursor, + FrameClientSortList_t & i_rFrames, + const sal_Int32 i_nStartPos, + const sal_Int32 i_nEndPos, + bool bOnlyTextFields ) +{ + if (!pUnoCursor) + return; + + // set the start if a selection should be exported + if ((i_nStartPos > 0) && + (pUnoCursor->Start()->GetContentIndex() != i_nStartPos)) + { + pUnoCursor->DeleteMark(); + OSL_ENSURE(pUnoCursor->Start()->GetNode().GetTextNode() && + (i_nStartPos <= pUnoCursor->Start()->GetNode().GetTextNode()-> + GetText().getLength()), "Incorrect start position" ); + // ??? should this be i_nStartPos - current position ? + pUnoCursor->Right(i_nStartPos); + } + + SwDoc& rDoc = pUnoCursor->GetDoc(); + + std::deque<sal_Int32> FieldMarks; + if (!bOnlyTextFields) + lcl_FillFieldMarkArray(FieldMarks, *pUnoCursor, i_nStartPos); + + SwXBookmarkPortion_ImplList Bookmarks; + if (!bOnlyTextFields) + lcl_FillBookmarkArray(rDoc, *pUnoCursor, Bookmarks); + + SwXRedlinePortion_ImplList Redlines; + if (!bOnlyTextFields) + lcl_FillRedlineArray(rDoc, *pUnoCursor, Redlines); + + SwSoftPageBreakList SoftPageBreaks; + if (!bOnlyTextFields) + lcl_FillSoftPageBreakArray(*pUnoCursor, SoftPageBreaks); + + SwAnnotationStartPortion_ImplList AnnotationStarts; + if (!bOnlyTextFields) + lcl_FillAnnotationStartArray( rDoc, *pUnoCursor, AnnotationStarts ); + + PortionStack_t PortionStack; + PortionStack.push( PortionList_t(&i_rPortions, nullptr) ); + + bool bAtEnd( false ); + while (!bAtEnd) // every iteration consumes at least current character! + { + if (pUnoCursor->HasMark()) + { + pUnoCursor->Normalize(false); + pUnoCursor->DeleteMark(); + } + + SwTextNode * const pTextNode = pUnoCursor->GetPointNode().GetTextNode(); + if (!pTextNode) + { + OSL_FAIL("lcl_CreatePortions: no TextNode - what now ?"); + return; + } + + SwpHints * const pHints = pTextNode->GetpSwpHints(); + const sal_Int32 nCurrentIndex = + pUnoCursor->GetPoint()->GetContentIndex(); + // this contains the portion which consumes the character in the + // text at nCurrentIndex; i.e. it must be set _once_ per iteration + rtl::Reference<SwXTextPortion> xRef; + + SwUnoCursorHelper::SelectPam(*pUnoCursor, true); // set mark + + // First remember the frame positions. + o3tl::sorted_vector<sal_Int32> aFramePositions; + lcl_ExtractFramePositions(i_rFrames, nCurrentIndex, aFramePositions); + + // Then export start of collapsed bookmarks which "cover" at-char + // anchored frames. + lcl_ExportBkmAndRedline( *PortionStack.top().first, i_xParentText, + pUnoCursor, Bookmarks, Redlines, SoftPageBreaks, nCurrentIndex, aFramePositions, /*bOnlyFrameBookmarkStarts=*/true ); + + lcl_ExportAnnotationStarts( + *PortionStack.top().first, + i_xParentText, + pUnoCursor, + AnnotationStarts, + nCurrentIndex, + aFramePositions, + /*bOnlyFrame=*/true ); + + const sal_Int32 nFirstFrameIndex = + lcl_ExportFrames( *PortionStack.top().first, + i_xParentText, pUnoCursor, i_rFrames, nCurrentIndex); + + // Export ends of the previously started collapsed bookmarks + all + // other bookmarks, redlines, etc. + lcl_ExportBkmAndRedline( *PortionStack.top().first, i_xParentText, + pUnoCursor, Bookmarks, Redlines, SoftPageBreaks, nCurrentIndex, aFramePositions, /*bOnlyFrameBookmarkStarts=*/false ); + + lcl_ExportAnnotationStarts( + *PortionStack.top().first, + i_xParentText, + pUnoCursor, + AnnotationStarts, + nCurrentIndex, + aFramePositions, + /*bOnlyFrame=*/false ); + + bool bCursorMoved( false ); + sal_Int32 nNextAttrIndex = -1; + // #111716# the cursor must not move right at the + // end position of a selection! + bAtEnd = ((i_nEndPos >= 0) && (nCurrentIndex >= i_nEndPos)) + || (nCurrentIndex >= pTextNode->Len()); + if (pHints) + { + // N.B.: side-effects nNextAttrIndex, bCursorMoved; may move cursor + xRef = lcl_ExportHints(PortionStack, i_xParentText, pUnoCursor, + pHints, i_nStartPos, i_nEndPos, nCurrentIndex, bAtEnd, + bCursorMoved, nNextAttrIndex); + if (PortionStack.empty()) + { + OSL_FAIL("CreatePortions: stack underflow"); + return; + } + } + + if (!xRef.is() && !bCursorMoved) + { + if (!bAtEnd && + !FieldMarks.empty() && (FieldMarks.front() == nCurrentIndex)) + { + // moves cursor + xRef = lcl_ExportFieldMark(i_xParentText, pUnoCursor, pTextNode); + FieldMarks.pop_front(); + } + } + else + { + OSL_ENSURE(FieldMarks.empty() || + (FieldMarks.front() != nCurrentIndex), + "fieldmark and hint with CH_TXTATR at same pos?"); + } + + if (!bAtEnd && !xRef.is() && !bCursorMoved) + { + const sal_Int32 nNextPortionIndex = + lcl_GetNextIndex(Bookmarks, Redlines, SoftPageBreaks); + + sal_Int32 nNextMarkIndex = ( !FieldMarks.empty() ? FieldMarks.front() : -1 ); + if ( !AnnotationStarts.empty() + && ( nNextMarkIndex == -1 + || (*AnnotationStarts.begin())->getIndex() < nNextMarkIndex ) ) + { + nNextMarkIndex = (*AnnotationStarts.begin())->getIndex(); + } + + lcl_MoveCursor( + pUnoCursor, + nCurrentIndex, + nFirstFrameIndex, + nNextPortionIndex, + nNextAttrIndex, + nNextMarkIndex, + i_nEndPos ); + + xRef = new SwXTextPortion(pUnoCursor, i_xParentText, PORTION_TEXT); + } + else if (bAtEnd && !xRef.is() && !pTextNode->Len()) + { + // special case: for an empty paragraph, we better put out a + // text portion because there may be a hyperlink attribute + xRef = new SwXTextPortion(pUnoCursor, i_xParentText, PORTION_TEXT); + } + else if (bAtEnd && !xRef.is() && pHints) + { + // See if there is an empty autofmt at the paragraph end. If so, export it, since that + // affects the formatting of number portions. + for (size_t i = 0; i < pHints->Count(); ++i) + { + const SwTextAttr* pHint = pHints->GetSortedByEnd(i); + if (pHint->GetStart() < pTextNode->Len()) + { + break; + } + if (pHint->Which() == RES_TXTATR_AUTOFMT && pHint->GetEnd() + && pHint->GetStart() == *pHint->GetEnd() + && pHint->GetStart() == pTextNode->Len()) + { + xRef = new SwXTextPortion(pUnoCursor, i_xParentText, PORTION_TEXT); + break; + } + } + } + + if (xRef.is()) + { + PortionStack.top().first->push_back(xRef); + } + } + + OSL_ENSURE((PortionStack.size() == 1) && !PortionStack.top().second, + "CreatePortions: stack error" ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |