LibreOffice Module sw (master)  1
unoportenum.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 
23 #include <utility>
24 
25 #include <unoport.hxx>
26 #include <IMark.hxx>
27 #include <crossrefbookmark.hxx>
28 #include <annotationmark.hxx>
29 #include <doc.hxx>
31 #include <txatbase.hxx>
32 #include <txtatr.hxx>
33 #include <ndhints.hxx>
34 #include <ndtxt.hxx>
35 #include <unocrsr.hxx>
36 #include <docary.hxx>
37 #include <textboxhelper.hxx>
38 #include <tox.hxx>
39 #include <unomid.h>
40 #include <unoparaframeenum.hxx>
41 #include <unocrsrhelper.hxx>
42 #include <unorefmark.hxx>
43 #include <unobookmark.hxx>
44 #include <unoredline.hxx>
45 #include <unofield.hxx>
46 #include <unometa.hxx>
47 #include <fmtfld.hxx>
48 #include <fldbas.hxx>
49 #include <fmtmeta.hxx>
50 #include <fmtanchr.hxx>
51 #include <fmtrfmrk.hxx>
52 #include <frmfmt.hxx>
53 #include <fmtflcnt.hxx>
54 #include <unoidx.hxx>
55 #include <unocoll.hxx>
56 #include <redline.hxx>
57 #include <docufld.hxx>
58 #include <txtfld.hxx>
59 #include <txtannotationfld.hxx>
60 #include <vcl/svapp.hxx>
61 #include <comphelper/string.hxx>
64 #include <com/sun/star/container/XEnumeration.hpp>
65 #include <algorithm>
66 #include <memory>
67 #include <set>
68 #include <stack>
69 
70 using namespace ::com::sun::star;
71 using namespace ::com::sun::star::uno;
72 using namespace ::com::sun::star::text;
73 using namespace ::std;
74 
75 typedef std::pair< TextRangeList_t * const, SwTextAttr const * const > PortionList_t;
76 typedef std::stack< PortionList_t > PortionStack_t;
77 
78 static void lcl_CreatePortions(
79  TextRangeList_t & i_rPortions,
80  uno::Reference< text::XText > const& i_xParentText,
81  SwUnoCursor* pUnoCursor,
82  FrameClientSortList_t & i_rFrames,
83  const sal_Int32 i_nStartPos, const sal_Int32 i_nEndPos );
84 
85 namespace
86 {
87  enum class BkmType {
88  Start, End, StartEnd
89  };
90 
91  struct SwXBookmarkPortion_Impl
92  {
93  Reference<XTextContent> xBookmark;
94  BkmType const nBkmType;
95  const SwPosition aPosition;
96 
97  SwXBookmarkPortion_Impl(uno::Reference<text::XTextContent> const& xMark,
98  const BkmType nType, SwPosition const& rPosition)
99  : xBookmark ( xMark )
100  , nBkmType ( nType )
101  , aPosition ( rPosition )
102  {
103  }
104  sal_Int32 getIndex ()
105  {
106  return aPosition.nContent.GetIndex();
107  }
108  };
109  typedef std::shared_ptr < SwXBookmarkPortion_Impl > SwXBookmarkPortion_ImplSharedPtr;
110  struct BookmarkCompareStruct
111  {
112  bool operator () ( const SwXBookmarkPortion_ImplSharedPtr &r1,
113  const SwXBookmarkPortion_ImplSharedPtr &r2 ) const
114  {
115  // #i16896# for bookmark portions at the same position, the start should
116  // always precede the end. Hence compare positions, and use bookmark type
117  // as tie-breaker for same position.
118  // return ( r1->nIndex == r2->nIndex )
119  // ? ( r1->nBkmType < r2->nBkmType )
120  // : ( r1->nIndex < r2->nIndex );
121 
122  // Note that the above code does not correctly handle
123  // the case when one bookmark ends, and another begins in the same
124  // position. When this occurs, the above code will return the
125  // start of the 2nd bookmark BEFORE the end of the first bookmark
126  // See bug #i58438# for more details. The below code is correct and
127  // fixes both #i58438 and #i16896#
128  return r1->aPosition < r2->aPosition;
129  }
130  };
131  typedef std::multiset < SwXBookmarkPortion_ImplSharedPtr, BookmarkCompareStruct > SwXBookmarkPortion_ImplList;
132 
134  void lcl_FillBookmark(sw::mark::IMark* const pBkmk, const SwNodeIndex& nOwnNode, SwDoc& rDoc, SwXBookmarkPortion_ImplList& rBkmArr)
135  {
136  bool const hasOther = pBkmk->IsExpanded();
137 
138  const SwPosition& rStartPos = pBkmk->GetMarkStart();
139  if(rStartPos.nNode == nOwnNode)
140  {
141  // #i109272#: cross reference marks: need special handling!
142  ::sw::mark::CrossRefBookmark *const pCrossRefMark(dynamic_cast< ::sw::mark::CrossRefBookmark*>(pBkmk));
143  BkmType const nType = (hasOther || pCrossRefMark)
144  ? BkmType::Start : BkmType::StartEnd;
145  rBkmArr.insert(std::make_shared<SwXBookmarkPortion_Impl>(
146  SwXBookmark::CreateXBookmark(rDoc, pBkmk),
147  nType, rStartPos));
148  }
149 
150  const SwPosition& rEndPos = pBkmk->GetMarkEnd();
151  if(rEndPos.nNode == nOwnNode)
152  {
153  unique_ptr<SwPosition> pCrossRefEndPos;
154  const SwPosition* pEndPos = nullptr;
155  ::sw::mark::CrossRefBookmark *const pCrossRefMark(dynamic_cast< ::sw::mark::CrossRefBookmark*>(pBkmk));
156  if(hasOther)
157  {
158  pEndPos = &rEndPos;
159  }
160  else if (pCrossRefMark)
161  {
162  // Crossrefbookmarks only remember the start position but have to span the whole paragraph
163  pCrossRefEndPos = std::make_unique<SwPosition>(rEndPos);
164  pCrossRefEndPos->nContent = pCrossRefEndPos->nNode.GetNode().GetTextNode()->Len();
165  pEndPos = pCrossRefEndPos.get();
166  }
167  if(pEndPos)
168  {
169  rBkmArr.insert(std::make_shared<SwXBookmarkPortion_Impl>(
170  SwXBookmark::CreateXBookmark(rDoc, pBkmk),
171  BkmType::End, *pEndPos));
172  }
173  }
174  }
175 
176  void lcl_FillBookmarkArray(SwDoc& rDoc, SwUnoCursor& rUnoCursor, SwXBookmarkPortion_ImplList& rBkmArr)
177  {
178  IDocumentMarkAccess* const pMarkAccess = rDoc.getIDocumentMarkAccess();
179  if(!pMarkAccess->getBookmarksCount())
180  return;
181 
182  const SwNodeIndex nOwnNode = rUnoCursor.GetPoint()->nNode;
183  SwTextNode* pTextNode = nOwnNode.GetNode().GetTextNode();
184  if (!pTextNode)
185  {
186  // no need to consider marks starting after aEndOfPara
187  SwPosition aEndOfPara(*rUnoCursor.GetPoint());
188  aEndOfPara.nContent = aEndOfPara.nNode.GetNode().GetTextNode()->Len();
189  const IDocumentMarkAccess::const_iterator_t pCandidatesEnd =
190  pMarkAccess->findFirstBookmarkStartsAfter(aEndOfPara);
191 
192  // search for all bookmarks that start or end in this paragraph
193  for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getBookmarksBegin();
194  ppMark != pCandidatesEnd;
195  ++ppMark)
196  {
197  ::sw::mark::IMark* const pBkmk = *ppMark;
198  lcl_FillBookmark(pBkmk, nOwnNode, rDoc, rBkmArr);
199  }
200  }
201  else
202  {
203  // A text node already knows its marks via its SwIndexes.
205  for (const SwIndex* pIndex = pTextNode->GetFirstIndex(); pIndex; pIndex = pIndex->GetNext())
206  {
207  // Need a non-cost mark here, as we'll create a UNO wrapper around it.
208  sw::mark::IMark* pBkmk = const_cast<sw::mark::IMark*>(pIndex->GetMark());
209  if (!pBkmk)
210  continue;
212  // These are the types stored in the container otherwise accessible via getBookmarks*()
215  continue;
216  // Only handle bookmarks once, if they start and end at this node as well.
217  if (!aSeenMarks.insert(pBkmk).second)
218  continue;
219  lcl_FillBookmark(pBkmk, nOwnNode, rDoc, rBkmArr);
220  }
221  }
222  }
223 
224  class theSwXTextPortionEnumerationUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextPortionEnumerationUnoTunnelId > {};
225  struct SwAnnotationStartPortion_Impl
226  {
227 
228  uno::Reference< text::XTextField > mxAnnotationField;
229  const SwPosition maPosition;
230 
231  SwAnnotationStartPortion_Impl(
232  uno::Reference< text::XTextField > const& xAnnotationField,
233  SwPosition const& rPosition)
234  : mxAnnotationField ( xAnnotationField )
235  , maPosition ( rPosition )
236  {
237  }
238 
239  sal_Int32 getIndex ()
240  {
241  return maPosition.nContent.GetIndex();
242  }
243  };
244  typedef std::shared_ptr < SwAnnotationStartPortion_Impl > SwAnnotationStartPortion_ImplSharedPtr;
245  struct AnnotationStartCompareStruct
246  {
247  bool operator () ( const SwAnnotationStartPortion_ImplSharedPtr &r1,
248  const SwAnnotationStartPortion_ImplSharedPtr &r2 )
249  const
250  {
251  return r1->maPosition < r2->maPosition;
252  }
253  };
254  typedef std::multiset < SwAnnotationStartPortion_ImplSharedPtr, AnnotationStartCompareStruct > SwAnnotationStartPortion_ImplList;
255 
256  void lcl_FillAnnotationStartArray(
257  SwDoc& rDoc,
258  SwUnoCursor& rUnoCursor,
259  SwAnnotationStartPortion_ImplList& rAnnotationStartArr )
260  {
261  IDocumentMarkAccess* const pMarkAccess = rDoc.getIDocumentMarkAccess();
262  if ( pMarkAccess->getAnnotationMarksCount() == 0 )
263  {
264  return;
265  }
266 
267  // no need to consider annotation marks starting after aEndOfPara
268  SwPosition aEndOfPara(*rUnoCursor.GetPoint());
269  aEndOfPara.nContent = aEndOfPara.nNode.GetNode().GetTextNode()->Len();
270  const IDocumentMarkAccess::const_iterator_t pCandidatesEnd =
271  pMarkAccess->findFirstAnnotationStartsAfter(aEndOfPara);
272 
273  // search for all annotation marks that have its start position in this paragraph
274  const SwNodeIndex nOwnNode = rUnoCursor.GetPoint()->nNode;
276  ppMark != pCandidatesEnd;
277  ++ppMark )
278  {
279  ::sw::mark::AnnotationMark* const pAnnotationMark =
280  dynamic_cast< ::sw::mark::AnnotationMark* >(*ppMark);
281 
282  if (!pAnnotationMark)
283  continue;
284 
285  const SwPosition& rStartPos = pAnnotationMark->GetMarkStart();
286  if (rStartPos.nNode != nOwnNode)
287  continue;
288 
289  const SwFormatField* pAnnotationFormatField = pAnnotationMark->GetAnnotationFormatField();
290  if (!pAnnotationFormatField)
291  {
292  SAL_WARN("sw.core", "missing annotation format field");
293  continue;
294  }
295 
296  rAnnotationStartArr.insert(
297  std::make_shared<SwAnnotationStartPortion_Impl>(
299  pAnnotationFormatField),
300  rStartPos));
301  }
302  }
303 }
304 
305 const uno::Sequence< sal_Int8 > & SwXTextPortionEnumeration::getUnoTunnelId()
306 {
308 }
309 
311  const uno::Sequence< sal_Int8 >& rId )
312 {
313  if( isUnoTunnelId<SwXTextPortionEnumeration>(rId) )
314  {
315  return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >( this ) );
316  }
317  return 0;
318 }
319 
321 {
322  return "SwXTextPortionEnumeration";
323 }
324 
325 sal_Bool
326 SwXTextPortionEnumeration::supportsService(const OUString& rServiceName)
327 {
328  return cppu::supportsService(this, rServiceName);
329 }
330 
332 {
333  Sequence<OUString> aRet { "com.sun.star.text.TextPortionEnumeration" };
334  return aRet;
335 }
336 
338  SwPaM& rParaCursor,
339  uno::Reference< XText > const & xParentText,
340  const sal_Int32 nStart,
341  const sal_Int32 nEnd )
342  : m_Portions()
343 {
344  m_pUnoCursor = rParaCursor.GetDoc()->CreateUnoCursor(*rParaCursor.GetPoint());
345 
346  OSL_ENSURE(nEnd == -1 || (nStart <= nEnd &&
347  nEnd <= m_pUnoCursor->Start()->nNode.GetNode().GetTextNode()->GetText().getLength()),
348  "start or end value invalid!");
349 
350  // find all frames, graphics and OLEs that are bound AT character in para
351  FrameClientSortList_t frames;
352  ::CollectFrameAtNode(m_pUnoCursor->GetPoint()->nNode, frames, true);
353  lcl_CreatePortions(m_Portions, xParentText, &*m_pUnoCursor, frames, nStart, nEnd);
354 }
355 
357  SwPaM& rParaCursor,
358  TextRangeList_t const & rPortions )
359  : m_Portions( rPortions )
360 {
361  m_pUnoCursor = rParaCursor.GetDoc()->CreateUnoCursor(*rParaCursor.GetPoint());
362 }
363 
365 {
366  SolarMutexGuard aGuard;
367  m_pUnoCursor.reset(nullptr);
368 }
369 
371 {
372  SolarMutexGuard aGuard;
373 
374  return !m_Portions.empty();
375 }
376 
378 {
379  SolarMutexGuard aGuard;
380 
381  if (m_Portions.empty())
382  throw container::NoSuchElementException();
383 
384  Any any;
385  any <<= m_Portions.front();
386  m_Portions.pop_front();
387  return any;
388 }
389 
390 static void
391 lcl_FillFieldMarkArray(std::deque<sal_Int32> & rFieldMarks, SwUnoCursor const & rUnoCursor,
392  const sal_Int32 i_nStartPos)
393 {
394  const SwTextNode * const pTextNode =
395  rUnoCursor.GetPoint()->nNode.GetNode().GetTextNode();
396  if (!pTextNode) return;
397 
398  const sal_Unicode fld[] = {
400  sal_Int32 pos = std::max(static_cast<sal_Int32>(0), i_nStartPos);
401  while ((pos = ::comphelper::string::indexOfAny(pTextNode->GetText(), fld, pos)) != -1)
402  {
403  rFieldMarks.push_back(pos);
404  ++pos;
405  }
406 }
407 
408 static uno::Reference<text::XTextRange>
410  uno::Reference< text::XText > const & i_xParentText,
411  SwUnoCursor * const pUnoCursor,
412  const SwTextNode * const pTextNode )
413 {
414  uno::Reference<text::XTextRange> xRef;
415  SwDoc* pDoc = pUnoCursor->GetDoc();
416  // maybe it's a good idea to add a special hint to the hints array and rely on the hint segmentation...
417  const sal_Int32 start = pUnoCursor->Start()->nContent.GetIndex();
418  OSL_ENSURE(pUnoCursor->End()->nContent.GetIndex() == start,
419  "hmm --- why is this different");
420 
421  pUnoCursor->Right(1);
422  if ( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() )
423  {
424  OSL_FAIL("cannot move cursor?");
425  return nullptr;
426  }
427 
428  const sal_Unicode Char = pTextNode->GetText()[start];
429  if (CH_TXT_ATR_FIELDSTART == Char)
430  {
431  ::sw::mark::IFieldmark* pFieldmark = nullptr;
432  if (pDoc)
433  {
434  pFieldmark = pDoc->getIDocumentMarkAccess()->
435  getFieldmarkFor(*pUnoCursor->GetMark());
436  }
437  SwXTextPortion* pPortion = new SwXTextPortion(
438  pUnoCursor, i_xParentText, PORTION_FIELD_START);
439  xRef = pPortion;
440  if (pFieldmark && pDoc)
441  {
442  pPortion->SetBookmark(
443  SwXFieldmark::CreateXFieldmark(*pDoc, pFieldmark));
444  }
445  }
446  else if (CH_TXT_ATR_FIELDEND == Char)
447  {
448  ::sw::mark::IFieldmark* pFieldmark = nullptr;
449  if (pDoc)
450  {
451  pFieldmark = pDoc->getIDocumentMarkAccess()->
452  getFieldmarkFor(*pUnoCursor->GetMark());
453  }
454  SwXTextPortion* pPortion = new SwXTextPortion(
455  pUnoCursor, i_xParentText, PORTION_FIELD_END);
456  xRef = pPortion;
457  if (pFieldmark && pDoc)
458  {
459  pPortion->SetBookmark(
460  SwXFieldmark::CreateXFieldmark(*pDoc, pFieldmark));
461  }
462  }
463  else if (CH_TXT_ATR_FORMELEMENT == Char)
464  {
465  ::sw::mark::IFieldmark* pFieldmark = nullptr;
466  if (pDoc)
467  {
468  pFieldmark = pDoc->getIDocumentMarkAccess()->getFieldmarkFor(*pUnoCursor->GetMark());
469  }
470  SwXTextPortion* pPortion = new SwXTextPortion(
471  pUnoCursor, i_xParentText, PORTION_FIELD_START_END);
472  xRef = pPortion;
473  if (pFieldmark && pDoc)
474  {
475  pPortion->SetBookmark(
476  SwXFieldmark::CreateXFieldmark(*pDoc, pFieldmark));
477  }
478  }
479  else
480  {
481  OSL_FAIL("no fieldmark found?");
482  }
483  return xRef;
484 }
485 
486 static Reference<XTextRange>
488  Reference<XText> const& xParent,
489  const SwUnoCursor * const pUnoCursor,
490  const SwTextAttr & rAttr, const bool bEnd)
491 {
492  SwDoc* pDoc = pUnoCursor->GetDoc();
493  SwFormatRefMark& rRefMark = const_cast<SwFormatRefMark&>(
494  static_cast<const SwFormatRefMark&>(rAttr.GetAttr()));
495  Reference<XTextContent> xContent;
496  if (!xContent.is())
497  {
498  xContent = SwXReferenceMark::CreateXReferenceMark(*pDoc, &rRefMark);
499  }
500 
501  SwXTextPortion* pPortion = nullptr;
502  if (!bEnd)
503  {
504  pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_REFMARK_START);
505  pPortion->SetRefMark(xContent);
506  pPortion->SetCollapsed(rAttr.End() == nullptr);
507  }
508  else
509  {
510  pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_REFMARK_END);
511  pPortion->SetRefMark(xContent);
512  }
513  return pPortion;
514 }
515 
516 static void
518  TextRangeList_t & rPortions,
519  Reference<XText> const& xParent,
520  const SwUnoCursor * const pUnoCursor,
521  const SwTextAttr & rAttr, const bool bEnd)
522 {
523  SwXTextPortion* pPortion = new SwXTextPortion(pUnoCursor,
524  static_txtattr_cast<const SwTextRuby&>(rAttr), xParent, bEnd);
525  rPortions.emplace_back(pPortion);
526  pPortion->SetCollapsed(rAttr.End() == nullptr);
527 }
528 
529 static Reference<XTextRange>
531  Reference<XText> const& xParent,
532  const SwUnoCursor * const pUnoCursor,
533  SwTextAttr & rAttr, const bool bEnd)
534 {
535  SwDoc* pDoc = pUnoCursor->GetDoc();
536  SwTOXMark & rTOXMark = static_cast<SwTOXMark&>(rAttr.GetAttr());
537 
538  const Reference<XTextContent> xContent =
540 
541  SwXTextPortion* pPortion = nullptr;
542  if (!bEnd)
543  {
544  pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_TOXMARK_START);
545  pPortion->SetTOXMark(xContent);
546  pPortion->SetCollapsed(rAttr.GetEnd() == nullptr);
547  }
548  else
549  {
550  pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_TOXMARK_END);
551  pPortion->SetTOXMark(xContent);
552  }
553  return pPortion;
554 }
555 
556 static uno::Reference<text::XTextRange>
558  uno::Reference<text::XText> const& xParent,
559  const SwUnoCursor * const pUnoCursor,
560  SwTextAttr & rAttr, std::unique_ptr<TextRangeList_t const> && pPortions)
561 {
562  const uno::Reference<rdf::XMetadatable> xMeta( SwXMeta::CreateXMeta(
563  *static_cast<SwFormatMeta &>(rAttr.GetAttr()).GetMeta(),
564  xParent, std::move(pPortions)));
565  SwXTextPortion * pPortion(nullptr);
566  if (RES_TXTATR_META == rAttr.Which())
567  {
568  const uno::Reference<text::XTextContent> xContent(xMeta,
569  uno::UNO_QUERY);
570  pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_META);
571  pPortion->SetMeta(xContent);
572  }
573  else
574  {
575  const uno::Reference<text::XTextField> xField(xMeta, uno::UNO_QUERY);
576  pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_FIELD);
577  pPortion->SetTextField(xField);
578  }
579  return pPortion;
580 }
581 
596 static void lcl_ExportBookmark(
597  TextRangeList_t & rPortions,
598  Reference<XText> const& xParent,
599  const SwUnoCursor * const pUnoCursor,
600  SwXBookmarkPortion_ImplList& rBkmArr,
601  const sal_Int32 nIndex,
602  const std::set<sal_Int32>& rFramePositions,
603  bool bOnlyFrameStarts)
604 {
605  for ( SwXBookmarkPortion_ImplList::iterator aIter = rBkmArr.begin(), aEnd = rBkmArr.end(); aIter != aEnd; )
606  {
607  const SwXBookmarkPortion_ImplSharedPtr& pPtr = *aIter;
608  if ( nIndex > pPtr->getIndex() )
609  {
610  if (bOnlyFrameStarts)
611  ++aIter;
612  else
613  aIter = rBkmArr.erase(aIter);
614  continue;
615  }
616  if ( nIndex < pPtr->getIndex() )
617  break;
618 
619  if ((BkmType::Start == pPtr->nBkmType && bOnlyFrameStarts) ||
620  (BkmType::StartEnd == pPtr->nBkmType))
621  {
622  bool bFrameStart = rFramePositions.find(nIndex) != rFramePositions.end();
623  bool bEnd = pPtr->nBkmType == BkmType::StartEnd && bFrameStart && !bOnlyFrameStarts;
624  if (pPtr->nBkmType == BkmType::Start || bFrameStart || !bOnlyFrameStarts)
625  {
626  // At this we create a text portion, due to one of these
627  // reasons:
628  // - this is the real start of a non-collapsed bookmark
629  // - this is the real position of a collapsed bookmark
630  // - this is the start or end (depending on bOnlyFrameStarts)
631  // of a collapsed bookmark at the same position as an at-char
632  // anchored frame
633  SwXTextPortion* pPortion =
634  new SwXTextPortion(pUnoCursor, xParent, bEnd ? PORTION_BOOKMARK_END : PORTION_BOOKMARK_START);
635  rPortions.emplace_back(pPortion);
636  pPortion->SetBookmark(pPtr->xBookmark);
637  pPortion->SetCollapsed( BkmType::StartEnd == pPtr->nBkmType && !bFrameStart );
638  }
639  }
640  else if (BkmType::End == pPtr->nBkmType && !bOnlyFrameStarts)
641  {
642  SwXTextPortion* pPortion =
643  new SwXTextPortion(pUnoCursor, xParent, PORTION_BOOKMARK_END);
644  rPortions.emplace_back(pPortion);
645  pPortion->SetBookmark(pPtr->xBookmark);
646  }
647 
648  // next bookmark
649  if (bOnlyFrameStarts)
650  ++aIter;
651  else
652  aIter = rBkmArr.erase(aIter);
653  }
654 }
655 
657  TextRangeList_t & rPortions,
658  Reference<XText> const& xParent,
659  const SwUnoCursor * const pUnoCursor,
660  SwSoftPageBreakList& rBreakArr,
661  const sal_Int32 nIndex)
662 {
663  for ( SwSoftPageBreakList::iterator aIter = rBreakArr.begin(),
664  aEnd = rBreakArr.end();
665  aIter != aEnd; )
666  {
667  if ( nIndex > *aIter )
668  {
669  aIter = rBreakArr.erase(aIter);
670  continue;
671  }
672  if ( nIndex < *aIter )
673  break;
674 
675  rPortions.push_back(
676  new SwXTextPortion(pUnoCursor, xParent, PORTION_SOFT_PAGEBREAK) );
677  aIter = rBreakArr.erase(aIter);
678  }
679 }
680 
682 {
684  const bool m_bStart;
685 
686  SwXRedlinePortion_Impl ( const SwRangeRedline* pRed, const bool bIsStart )
687  : m_pRedline(pRed)
688  , m_bStart(bIsStart)
689  {
690  }
691 
692  sal_Int32 getRealIndex ()
693  {
694  return m_bStart ? m_pRedline->Start()->nContent.GetIndex()
695  : m_pRedline->End() ->nContent.GetIndex();
696  }
697 };
698 
699 typedef std::shared_ptr < SwXRedlinePortion_Impl >
701 
703 {
705  {
706  return *(r->m_bStart ? r->m_pRedline->Start() : r->m_pRedline->End());
707  }
708 
710  const SwXRedlinePortion_ImplSharedPtr &r2 ) const
711  {
712  return getPosition ( r1 ) < getPosition ( r2 );
713  }
714 };
715 
716 typedef std::multiset < SwXRedlinePortion_ImplSharedPtr, RedlineCompareStruct >
718 
719 static Reference<XTextRange>
721  PortionStack_t & rPortionStack,
722  const Reference<XText> & xParent,
723  SwUnoCursor * const pUnoCursor,
724  SwpHints const * const pHints,
725  const sal_Int32 i_nStartPos,
726  const sal_Int32 i_nEndPos,
727  const sal_Int32 nCurrentIndex,
728  const bool bRightMoveForbidden,
729  bool & o_rbCursorMoved,
730  sal_Int32 & o_rNextAttrPosition)
731 {
732  // if the attribute has a dummy character, then xRef is set (except META)
733  // otherwise, the portion for the attribute is inserted into rPortions!
734  Reference<XTextRange> xRef;
735  SwDoc* pDoc = pUnoCursor->GetDoc();
736  //search for special text attributes - first some ends
737  size_t nEndIndex = 0;
738  sal_Int32 nNextEnd = 0;
739  while(nEndIndex < pHints->Count() &&
740  (!pHints->GetSortedByEnd(nEndIndex)->GetEnd() ||
741  nCurrentIndex >= (nNextEnd = (*pHints->GetSortedByEnd(nEndIndex)->GetEnd()))))
742  {
743  if(pHints->GetSortedByEnd(nEndIndex)->GetEnd())
744  {
745  SwTextAttr * const pAttr = pHints->GetSortedByEnd(nEndIndex);
746  if (nNextEnd == nCurrentIndex)
747  {
748  const sal_uInt16 nWhich( pAttr->Which() );
749  switch (nWhich)
750  {
751  case RES_TXTATR_TOXMARK:
752  {
753  Reference<XTextRange> xTmp = lcl_CreateTOXMarkPortion(
754  xParent, pUnoCursor, *pAttr, true);
755  rPortionStack.top().first->push_back(xTmp);
756  }
757  break;
758  case RES_TXTATR_REFMARK:
759  {
760  Reference<XTextRange> xTmp = lcl_CreateRefMarkPortion(
761  xParent, pUnoCursor, *pAttr, true);
762  rPortionStack.top().first->push_back(xTmp);
763  }
764  break;
765  case RES_TXTATR_CJK_RUBY:
766  //#i91534# GetEnd() == 0 mixes the order of ruby start/end
767  if( *pAttr->GetEnd() == pAttr->GetStart())
768  {
769  lcl_InsertRubyPortion( *rPortionStack.top().first,
770  xParent, pUnoCursor, *pAttr, false);
771  }
772  lcl_InsertRubyPortion( *rPortionStack.top().first,
773  xParent, pUnoCursor, *pAttr, true);
774  break;
775  case RES_TXTATR_META:
777  {
778  OSL_ENSURE(pAttr->GetStart() != *pAttr->GetEnd(),
779  "empty meta?");
780  if ((i_nStartPos > 0) &&
781  (pAttr->GetStart() < i_nStartPos))
782  {
783  // force skip pAttr and rest of attribute ends
784  // at nCurrentIndex
785  // because they are not contained in the meta pAttr
786  // and the meta pAttr itself is outside selection!
787  // (necessary for SwXMeta::createEnumeration)
788  if (pAttr->GetStart() + 1 == i_nStartPos)
789  {
790  nEndIndex = pHints->Count() - 1;
791  }
792  break;
793  }
794  PortionList_t Top = rPortionStack.top();
795  if (Top.second != pAttr)
796  {
797  OSL_FAIL("ExportHints: stack error" );
798  }
799  else
800  {
801  std::unique_ptr<const TextRangeList_t>
802  pCurrentPortions(Top.first);
803  rPortionStack.pop();
804  const uno::Reference<text::XTextRange> xPortion(
805  lcl_CreateMetaPortion(xParent, pUnoCursor,
806  *pAttr, std::move(pCurrentPortions)));
807  rPortionStack.top().first->push_back(xPortion);
808  }
809  }
810  break;
811  }
812  }
813  }
814  nEndIndex++;
815  }
816 
817  // then some starts
818  size_t nStartIndex = 0;
819  sal_Int32 nNextStart = 0;
820  while(nStartIndex < pHints->Count() &&
821  nCurrentIndex >= (nNextStart = pHints->Get(nStartIndex)->GetStart()))
822  {
823  SwTextAttr * const pAttr = pHints->Get(nStartIndex);
824  sal_uInt16 nAttrWhich = pAttr->Which();
825  if (nNextStart == nCurrentIndex)
826  {
827  switch( nAttrWhich )
828  {
829  case RES_TXTATR_FIELD:
830  if(!bRightMoveForbidden)
831  {
832  pUnoCursor->Right(1);
833  if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() )
834  break;
835  SwXTextPortion* pPortion;
836  xRef = pPortion =
837  new SwXTextPortion(
838  pUnoCursor, xParent, PORTION_FIELD);
839  Reference<XTextField> const xField =
841  &pAttr->GetFormatField());
842  pPortion->SetTextField(xField);
843  }
844  break;
845 
847  if(!bRightMoveForbidden)
848  {
849  pUnoCursor->Right(1);
850  if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() )
851  break;
852 
853  const SwTextAnnotationField* pTextAnnotationField = dynamic_cast<const SwTextAnnotationField*>( pAttr );
854  ::sw::mark::IMark* pAnnotationMark = pTextAnnotationField ? pTextAnnotationField->GetAnnotationMark() : nullptr;
855  if ( pAnnotationMark != nullptr )
856  {
857  SwXTextPortion* pPortion = new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION_END );
859  *pDoc, pAnnotationMark));
860  xRef = pPortion;
861  }
862  else
863  {
864  SwXTextPortion* pPortion = new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION );
865  Reference<XTextField> xField =
867  &pAttr->GetFormatField());
868  pPortion->SetTextField(xField);
869  xRef = pPortion;
870  }
871  }
872  break;
873 
875  if(!bRightMoveForbidden)
876  {
877 
878  pUnoCursor->Right(
879  pAttr->GetFormatField().GetField()->ExpandField(true, nullptr).getLength() + 2 );
880  if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() )
881  break;
882  SwXTextPortion* pPortion =
883  new SwXTextPortion( pUnoCursor, xParent, PORTION_FIELD);
884  xRef = pPortion;
885  Reference<XTextField> xField =
887  &pAttr->GetFormatField());
888  pPortion->SetTextField(xField);
889  }
890  break;
891 
892  case RES_TXTATR_FLYCNT:
893  if(!bRightMoveForbidden)
894  {
895  pUnoCursor->Right(1);
896  if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() )
897  break; // Robust #i81708# content in covered cells
898 
899  // Do not expose inline anchored textboxes.
901  break;
902 
903  pUnoCursor->Exchange();
904  xRef = new SwXTextPortion( pUnoCursor, xParent, PORTION_FRAME);
905  }
906  break;
907 
908  case RES_TXTATR_FTN:
909  {
910  if(!bRightMoveForbidden)
911  {
912  pUnoCursor->Right(1);
913  if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() )
914  break;
915  SwXTextPortion* pPortion;
916  xRef = pPortion = new SwXTextPortion(
917  pUnoCursor, xParent, PORTION_FOOTNOTE);
918  Reference<XFootnote> xContent =
919  SwXFootnotes::GetObject(*pDoc, pAttr->GetFootnote());
920  pPortion->SetFootnote(xContent);
921  }
922  }
923  break;
924 
925  case RES_TXTATR_TOXMARK:
926  case RES_TXTATR_REFMARK:
927  {
928  bool bIsPoint = !(pAttr->GetEnd());
929  if (!bRightMoveForbidden || !bIsPoint)
930  {
931  if (bIsPoint)
932  {
933  pUnoCursor->Right(1);
934  }
935  Reference<XTextRange> xTmp =
936  (RES_TXTATR_REFMARK == nAttrWhich)
938  xParent, pUnoCursor, *pAttr, false)
940  xParent, pUnoCursor, *pAttr, false);
941  if (bIsPoint) // consume CH_TXTATR!
942  {
943  pUnoCursor->Normalize(false);
944  pUnoCursor->DeleteMark();
945  xRef = xTmp;
946  }
947  else // just insert it
948  {
949  rPortionStack.top().first->push_back(xTmp);
950  }
951  }
952  }
953  break;
954  case RES_TXTATR_CJK_RUBY:
955  //#i91534# GetEnd() == 0 mixes the order of ruby start/end
956  if(pAttr->GetEnd() && (*pAttr->GetEnd() != pAttr->GetStart()))
957  {
958  lcl_InsertRubyPortion( *rPortionStack.top().first,
959  xParent, pUnoCursor, *pAttr, false);
960  }
961  break;
962  case RES_TXTATR_META:
964  if (pAttr->GetStart() != *pAttr->GetEnd())
965  {
966  if (!bRightMoveForbidden)
967  {
968  pUnoCursor->Right(1);
969  o_rbCursorMoved = true;
970  // only if the end is included in selection!
971  if ((i_nEndPos < 0) ||
972  (*pAttr->GetEnd() <= i_nEndPos))
973  {
974  rPortionStack.push( std::make_pair(
975  new TextRangeList_t, pAttr ));
976  }
977  }
978  }
979  break;
980  case RES_TXTATR_AUTOFMT:
981  case RES_TXTATR_INETFMT:
982  case RES_TXTATR_CHARFMT:
983  break; // these are handled as properties of a "Text" portion
984  default:
985  OSL_FAIL("unknown attribute");
986  break;
987  }
988  }
989  nStartIndex++;
990  }
991 
992  if (xRef.is()) // implies that we have moved the cursor
993  {
994  o_rbCursorMoved = true;
995  }
996  if (!o_rbCursorMoved)
997  {
998  // search for attribute changes behind the current cursor position
999  // break up at frames, bookmarks, redlines
1000 
1001  nStartIndex = 0;
1002  nNextStart = 0;
1003  while(nStartIndex < pHints->Count() &&
1004  nCurrentIndex >= (nNextStart = pHints->Get(nStartIndex)->GetStart()))
1005  nStartIndex++;
1006 
1007  nEndIndex = 0;
1008  nNextEnd = 0;
1009  while(nEndIndex < pHints->Count() &&
1010  nCurrentIndex >= (nNextEnd = pHints->GetSortedByEnd(nEndIndex)->GetAnyEnd()))
1011  nEndIndex++;
1012 
1013  sal_Int32 nNextPos =
1014  ((nNextStart > nCurrentIndex) && (nNextStart < nNextEnd))
1015  ? nNextStart : nNextEnd;
1016  if (nNextPos > nCurrentIndex)
1017  {
1018  o_rNextAttrPosition = nNextPos;
1019  }
1020  }
1021  return xRef;
1022 }
1023 
1024 static void lcl_MoveCursor( SwUnoCursor * const pUnoCursor,
1025  const sal_Int32 nCurrentIndex,
1026  const sal_Int32 nNextFrameIndex,
1027  const sal_Int32 nNextPortionIndex,
1028  const sal_Int32 nNextAttrIndex,
1029  const sal_Int32 nNextMarkIndex,
1030  const sal_Int32 nEndPos )
1031 {
1032  sal_Int32 nMovePos = pUnoCursor->GetContentNode()->Len();
1033 
1034  if ((nEndPos >= 0) && (nEndPos < nMovePos))
1035  {
1036  nMovePos = nEndPos;
1037  }
1038 
1039  if ((nNextFrameIndex >= 0) && (nNextFrameIndex < nMovePos))
1040  {
1041  nMovePos = nNextFrameIndex;
1042  }
1043 
1044  if ((nNextPortionIndex >= 0) && (nNextPortionIndex < nMovePos))
1045  {
1046  nMovePos = nNextPortionIndex;
1047  }
1048 
1049  if ((nNextAttrIndex >= 0) && (nNextAttrIndex < nMovePos))
1050  {
1051  nMovePos = nNextAttrIndex;
1052  }
1053 
1054  if ((nNextMarkIndex >= 0) && (nNextMarkIndex < nMovePos))
1055  {
1056  nMovePos = nNextMarkIndex;
1057  }
1058 
1059  if (nMovePos > nCurrentIndex)
1060  {
1061  pUnoCursor->GetPoint()->nContent = nMovePos;
1062  }
1063 }
1064 
1066  SwDoc const & rDoc,
1067  SwUnoCursor const & rUnoCursor,
1068  SwXRedlinePortion_ImplList& rRedArr )
1069 {
1070  const SwRedlineTable& rRedTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
1071  const size_t nRedTableCount = rRedTable.size();
1072 
1073  if ( nRedTableCount > 0 )
1074  {
1075  const SwPosition* pStart = rUnoCursor.GetPoint();
1076  const SwNodeIndex nOwnNode = pStart->nNode;
1077 
1078  for(size_t nRed = 0; nRed < nRedTableCount; ++nRed)
1079  {
1080  const SwRangeRedline* pRedline = rRedTable[nRed];
1081  const SwPosition* pRedStart = pRedline->Start();
1082  const SwNodeIndex nRedNode = pRedStart->nNode;
1083  if ( nOwnNode == nRedNode )
1084  rRedArr.insert( std::make_shared<SwXRedlinePortion_Impl>(
1085  pRedline, true ) );
1086  if( pRedline->HasMark() && pRedline->End()->nNode == nOwnNode )
1087  rRedArr.insert( std::make_shared<SwXRedlinePortion_Impl>(
1088  pRedline, false ) );
1089  }
1090  }
1091 }
1092 
1094  SwUnoCursor const & rUnoCursor,
1095  SwSoftPageBreakList& rBreakArr )
1096 {
1097  const SwTextNode *pTextNode =
1098  rUnoCursor.GetPoint()->nNode.GetNode().GetTextNode();
1099  if( pTextNode )
1100  pTextNode->fillSoftPageBreakList( rBreakArr );
1101 }
1102 
1103 static void lcl_ExportRedline(
1104  TextRangeList_t & rPortions,
1105  Reference<XText> const& xParent,
1106  const SwUnoCursor * const pUnoCursor,
1107  SwXRedlinePortion_ImplList& rRedlineArr,
1108  const sal_Int32 nIndex)
1109 {
1110 
1111  // We want this loop to iterate over all red lines in this
1112  // array. We will only insert the ones with index matches
1113  for ( SwXRedlinePortion_ImplList::iterator aIter = rRedlineArr.begin(), aEnd = rRedlineArr.end();
1114  aIter != aEnd; )
1115  {
1116  SwXRedlinePortion_ImplSharedPtr pPtr = *aIter;
1117  sal_Int32 nRealIndex = pPtr->getRealIndex();
1118  // If there are elements before nIndex, remove them
1119  if ( nIndex > nRealIndex )
1120  aIter = rRedlineArr.erase(aIter);
1121  // If the elements match, and them to the list
1122  else if ( nIndex == nRealIndex )
1123  {
1124  rPortions.push_back( new SwXRedlinePortion(
1125  *pPtr->m_pRedline, pUnoCursor, xParent, pPtr->m_bStart));
1126  aIter = rRedlineArr.erase(aIter);
1127  }
1128  // If we've iterated past nIndex, exit the loop
1129  else
1130  break;
1131  }
1132 }
1133 
1135  TextRangeList_t & rPortions,
1136  Reference<XText> const & xParent,
1137  const SwUnoCursor * const pUnoCursor,
1138  SwXBookmarkPortion_ImplList& rBkmArr,
1139  SwXRedlinePortion_ImplList& rRedlineArr,
1140  SwSoftPageBreakList& rBreakArr,
1141  const sal_Int32 nIndex,
1142  const std::set<sal_Int32>& rFramePositions,
1143  bool bOnlyFrameBookmarkStarts)
1144 {
1145  if (!rBkmArr.empty())
1146  lcl_ExportBookmark(rPortions, xParent, pUnoCursor, rBkmArr, nIndex, rFramePositions,
1147  bOnlyFrameBookmarkStarts);
1148 
1149  if (bOnlyFrameBookmarkStarts)
1150  // Only exporting the start of some collapsed bookmarks: no export of
1151  // other arrays.
1152  return;
1153 
1154  if (!rRedlineArr.empty())
1155  lcl_ExportRedline(rPortions, xParent, pUnoCursor, rRedlineArr, nIndex);
1156 
1157  if (!rBreakArr.empty())
1158  lcl_ExportSoftPageBreak(rPortions, xParent, pUnoCursor, rBreakArr, nIndex);
1159 }
1160 
1173  TextRangeList_t & rPortions,
1174  Reference<XText> const & xParent,
1175  const SwUnoCursor * const pUnoCursor,
1176  SwAnnotationStartPortion_ImplList& rAnnotationStartArr,
1177  const sal_Int32 nIndex,
1178  const std::set<sal_Int32>& rFramePositions,
1179  bool bOnlyFrame)
1180 {
1181  for ( SwAnnotationStartPortion_ImplList::iterator aIter = rAnnotationStartArr.begin(), aEnd = rAnnotationStartArr.end();
1182  aIter != aEnd; )
1183  {
1184  SwAnnotationStartPortion_ImplSharedPtr pPtr = *aIter;
1185  if ( nIndex > pPtr->getIndex() )
1186  {
1187  aIter = rAnnotationStartArr.erase(aIter);
1188  continue;
1189  }
1190  if ( pPtr->getIndex() > nIndex )
1191  {
1192  break;
1193  }
1194 
1195  bool bFrameStart = rFramePositions.find(nIndex) != rFramePositions.end();
1196  if (bFrameStart || !bOnlyFrame)
1197  {
1198  SwXTextPortion* pPortion =
1199  new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION );
1200  pPortion->SetTextField( pPtr->mxAnnotationField );
1201  rPortions.emplace_back(pPortion);
1202 
1203  aIter = rAnnotationStartArr.erase(aIter);
1204  }
1205  else
1206  ++aIter;
1207  }
1208 }
1209 
1211 static void lcl_ExtractFramePositions(FrameClientSortList_t& rFrames, sal_Int32 nCurrentIndex,
1212  std::set<sal_Int32>& rFramePositions)
1213 {
1214  for (const auto& rFrame : rFrames)
1215  {
1216  if (rFrame.nIndex < nCurrentIndex)
1217  continue;
1218 
1219  if (rFrame.nIndex > nCurrentIndex)
1220  break;
1221 
1222  const SwModify* pFrame = rFrame.pFrameClient->GetRegisteredIn();
1223  if (!pFrame)
1224  continue;
1225 
1226  auto& rFormat = *static_cast<SwFrameFormat*>(const_cast<SwModify*>(pFrame));
1227  const SwFormatAnchor& rAnchor = rFormat.GetAnchor();
1228  const SwPosition* pPosition = rAnchor.GetContentAnchor();
1229  if (!pPosition)
1230  continue;
1231 
1232  rFramePositions.insert(pPosition->nContent.GetIndex());
1233  }
1234 }
1235 
1242 static sal_Int32 lcl_ExportFrames(
1243  TextRangeList_t & rPortions,
1244  Reference<XText> const & i_xParent,
1245  SwUnoCursor const * const i_pUnoCursor,
1246  FrameClientSortList_t & i_rFrames,
1247  sal_Int32 const i_nCurrentIndex)
1248 {
1249  // Ignore frames which are not exported, as we are exporting a selection
1250  // and they are anchored before the start of the selection.
1251  while (!i_rFrames.empty() && i_rFrames.front().nIndex < i_nCurrentIndex)
1252  i_rFrames.pop_front();
1253 
1254  // find first Frame in (sorted) i_rFrames at current position
1255  while (!i_rFrames.empty() && (i_rFrames.front().nIndex == i_nCurrentIndex))
1256  // do not check for i_nEnd here; this is done implicitly by lcl_MoveCursor
1257  {
1258  const SwModify * const pFrame =
1259  i_rFrames.front().pFrameClient->GetRegisteredIn();
1260  if (pFrame) // Frame could be disposed
1261  {
1262  SwXTextPortion* pPortion = new SwXTextPortion(i_pUnoCursor, i_xParent,
1263  *static_cast<SwFrameFormat*>( const_cast<SwModify*>( pFrame ) ) );
1264  rPortions.emplace_back(pPortion);
1265  }
1266  i_rFrames.pop_front();
1267  }
1268 
1269  return !i_rFrames.empty() ? i_rFrames.front().nIndex : -1;
1270 }
1271 
1272 static sal_Int32 lcl_GetNextIndex(
1273  SwXBookmarkPortion_ImplList const & rBkmArr,
1274  SwXRedlinePortion_ImplList const & rRedlineArr,
1275  SwSoftPageBreakList const & rBreakArr )
1276 {
1277  sal_Int32 nRet = -1;
1278  if(!rBkmArr.empty())
1279  {
1280  SwXBookmarkPortion_ImplSharedPtr pPtr = *rBkmArr.begin();
1281  nRet = pPtr->getIndex();
1282  }
1283  if(!rRedlineArr.empty())
1284  {
1285  SwXRedlinePortion_ImplSharedPtr pPtr = *rRedlineArr.begin();
1286  sal_Int32 nTmp = pPtr->getRealIndex();
1287  if(nRet < 0 || nTmp < nRet)
1288  nRet = nTmp;
1289  }
1290  if(!rBreakArr.empty())
1291  {
1292  if(nRet < 0 || *rBreakArr.begin() < nRet)
1293  nRet = *rBreakArr.begin();
1294  }
1295  return nRet;
1296 };
1297 
1299  TextRangeList_t & i_rPortions,
1300  uno::Reference< text::XText > const & i_xParentText,
1301  SwUnoCursor * const pUnoCursor,
1302  FrameClientSortList_t & i_rFrames,
1303  const sal_Int32 i_nStartPos,
1304  const sal_Int32 i_nEndPos )
1305 {
1306  if (!pUnoCursor)
1307  return;
1308 
1309  // set the start if a selection should be exported
1310  if ((i_nStartPos > 0) &&
1311  (pUnoCursor->Start()->nContent.GetIndex() != i_nStartPos))
1312  {
1313  pUnoCursor->DeleteMark();
1314  OSL_ENSURE(pUnoCursor->Start()->nNode.GetNode().GetTextNode() &&
1315  (i_nStartPos <= pUnoCursor->Start()->nNode.GetNode().GetTextNode()->
1316  GetText().getLength()), "Incorrect start position" );
1317  // ??? should this be i_nStartPos - current position ?
1318  pUnoCursor->Right(i_nStartPos);
1319  }
1320 
1321  SwDoc * const pDoc = pUnoCursor->GetDoc();
1322 
1323  std::deque<sal_Int32> FieldMarks;
1324  lcl_FillFieldMarkArray(FieldMarks, *pUnoCursor, i_nStartPos);
1325 
1326  SwXBookmarkPortion_ImplList Bookmarks;
1327  lcl_FillBookmarkArray(*pDoc, *pUnoCursor, Bookmarks);
1328 
1329  SwXRedlinePortion_ImplList Redlines;
1330  lcl_FillRedlineArray(*pDoc, *pUnoCursor, Redlines);
1331 
1332  SwSoftPageBreakList SoftPageBreaks;
1333  lcl_FillSoftPageBreakArray(*pUnoCursor, SoftPageBreaks);
1334 
1335  SwAnnotationStartPortion_ImplList AnnotationStarts;
1336  lcl_FillAnnotationStartArray( *pDoc, *pUnoCursor, AnnotationStarts );
1337 
1338  PortionStack_t PortionStack;
1339  PortionStack.push( PortionList_t(&i_rPortions, nullptr) );
1340 
1341  bool bAtEnd( false );
1342  while (!bAtEnd) // every iteration consumes at least current character!
1343  {
1344  if (pUnoCursor->HasMark())
1345  {
1346  pUnoCursor->Normalize(false);
1347  pUnoCursor->DeleteMark();
1348  }
1349 
1350  SwTextNode * const pTextNode = pUnoCursor->GetNode().GetTextNode();
1351  if (!pTextNode)
1352  {
1353  OSL_FAIL("lcl_CreatePortions: no TextNode - what now ?");
1354  return;
1355  }
1356 
1357  SwpHints * const pHints = pTextNode->GetpSwpHints();
1358  const sal_Int32 nCurrentIndex =
1359  pUnoCursor->GetPoint()->nContent.GetIndex();
1360  // this contains the portion which consumes the character in the
1361  // text at nCurrentIndex; i.e. it must be set _once_ per iteration
1362  uno::Reference< XTextRange > xRef;
1363 
1364  SwUnoCursorHelper::SelectPam(*pUnoCursor, true); // set mark
1365 
1366  // First remember the frame positions.
1367  std::set<sal_Int32> aFramePositions;
1368  lcl_ExtractFramePositions(i_rFrames, nCurrentIndex, aFramePositions);
1369 
1370  // Then export start of collapsed bookmarks which "cover" at-char
1371  // anchored frames.
1372  lcl_ExportBkmAndRedline( *PortionStack.top().first, i_xParentText,
1373  pUnoCursor, Bookmarks, Redlines, SoftPageBreaks, nCurrentIndex, aFramePositions, /*bOnlyFrameBookmarkStarts=*/true );
1374 
1376  *PortionStack.top().first,
1377  i_xParentText,
1378  pUnoCursor,
1379  AnnotationStarts,
1380  nCurrentIndex,
1381  aFramePositions,
1382  /*bOnlyFrame=*/true );
1383 
1384  const sal_Int32 nFirstFrameIndex =
1385  lcl_ExportFrames( *PortionStack.top().first,
1386  i_xParentText, pUnoCursor, i_rFrames, nCurrentIndex);
1387 
1388  // Export ends of the previously started collapsed bookmarks + all
1389  // other bookmarks, redlines, etc.
1390  lcl_ExportBkmAndRedline( *PortionStack.top().first, i_xParentText,
1391  pUnoCursor, Bookmarks, Redlines, SoftPageBreaks, nCurrentIndex, aFramePositions, /*bOnlyFrameBookmarkStarts=*/false );
1392 
1394  *PortionStack.top().first,
1395  i_xParentText,
1396  pUnoCursor,
1397  AnnotationStarts,
1398  nCurrentIndex,
1399  aFramePositions,
1400  /*bOnlyFrame=*/false );
1401 
1402  bool bCursorMoved( false );
1403  sal_Int32 nNextAttrIndex = -1;
1404  // #111716# the cursor must not move right at the
1405  // end position of a selection!
1406  bAtEnd = ((i_nEndPos >= 0) && (nCurrentIndex >= i_nEndPos))
1407  || (nCurrentIndex >= pTextNode->Len());
1408  if (pHints)
1409  {
1410  // N.B.: side-effects nNextAttrIndex, bCursorMoved; may move cursor
1411  xRef = lcl_ExportHints(PortionStack, i_xParentText, pUnoCursor,
1412  pHints, i_nStartPos, i_nEndPos, nCurrentIndex, bAtEnd,
1413  bCursorMoved, nNextAttrIndex);
1414  if (PortionStack.empty())
1415  {
1416  OSL_FAIL("CreatePortions: stack underflow");
1417  return;
1418  }
1419  }
1420 
1421  if (!xRef.is() && !bCursorMoved)
1422  {
1423  if (!bAtEnd &&
1424  !FieldMarks.empty() && (FieldMarks.front() == nCurrentIndex))
1425  {
1426  // moves cursor
1427  xRef = lcl_ExportFieldMark(i_xParentText, pUnoCursor, pTextNode);
1428  FieldMarks.pop_front();
1429  }
1430  }
1431  else
1432  {
1433  OSL_ENSURE(FieldMarks.empty() ||
1434  (FieldMarks.front() != nCurrentIndex),
1435  "fieldmark and hint with CH_TXTATR at same pos?");
1436  }
1437 
1438  if (!bAtEnd && !xRef.is() && !bCursorMoved)
1439  {
1440  const sal_Int32 nNextPortionIndex =
1441  lcl_GetNextIndex(Bookmarks, Redlines, SoftPageBreaks);
1442 
1443  sal_Int32 nNextMarkIndex = ( !FieldMarks.empty() ? FieldMarks.front() : -1 );
1444  if ( !AnnotationStarts.empty()
1445  && ( nNextMarkIndex == -1
1446  || (*AnnotationStarts.begin())->getIndex() < nNextMarkIndex ) )
1447  {
1448  nNextMarkIndex = (*AnnotationStarts.begin())->getIndex();
1449  }
1450 
1452  pUnoCursor,
1453  nCurrentIndex,
1454  nFirstFrameIndex,
1455  nNextPortionIndex,
1456  nNextAttrIndex,
1457  nNextMarkIndex,
1458  i_nEndPos );
1459 
1460  xRef = new SwXTextPortion(pUnoCursor, i_xParentText, PORTION_TEXT);
1461  }
1462  else if (bAtEnd && !xRef.is() && !pTextNode->Len())
1463  {
1464  // special case: for an empty paragraph, we better put out a
1465  // text portion because there may be a hyperlink attribute
1466  xRef = new SwXTextPortion(pUnoCursor, i_xParentText, PORTION_TEXT);
1467  }
1468 
1469  if (xRef.is())
1470  {
1471  PortionStack.top().first->push_back(xRef);
1472  }
1473  }
1474 
1475  OSL_ENSURE((PortionStack.size() == 1) && !PortionStack.top().second,
1476  "CreatePortions: stack error" );
1477 }
1478 
1479 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
css::uno::Reference< css::linguistic2::XProofreadingIterator > get(css::uno::Reference< css::uno::XComponentContext > const &context)
std::deque< css::uno::Reference< css::text::XTextRange > > TextRangeList_t
Definition: unometa.hxx:42
static css::uno::Reference< css::rdf::XMetadatable > CreateXMeta(::sw::Meta &rMeta, css::uno::Reference< css::text::XText > const &xParentText=nullptr, std::unique_ptr< TextRangeList_t const > &&pPortions=std::unique_ptr< TextRangeList_t const >())
std::shared_ptr< SwUnoCursor > CreateUnoCursor(const SwPosition &rPos, bool bTableCursor=false)
Definition: doc.cxx:1807
virtual sal_Int32 Len() const
Definition: node.cxx:1181
void DeleteMark()
Definition: pam.hxx:177
SwNode & GetNode(bool bPoint=true) const
Definition: pam.hxx:223
Marks a position in the document model.
Definition: pam.hxx:35
static Reference< XTextRange > lcl_CreateTOXMarkPortion(Reference< XText > const &xParent, const SwUnoCursor *const pUnoCursor, SwTextAttr &rAttr, const bool bEnd)
virtual css::uno::Any SAL_CALL nextElement() override
const SwField * GetField() const
Definition: fmtfld.hxx:71
#define RES_TXTATR_CJK_RUBY
Definition: hintids.hxx:143
const OUString & GetText() const
Definition: ndtxt.hxx:211
#define RES_TXTATR_METAFIELD
Definition: hintids.hxx:139
SwpHints * GetpSwpHints()
Definition: ndtxt.hxx:219
SwNodeIndex nNode
Definition: pam.hxx:37
virtual sal_Int32 getBookmarksCount() const =0
returns the number of IBookmarks.
wrapper iterator: wraps iterator of implementation while hiding MarkBase class; only IMark instances ...
std::deque< FrameClientSortListEntry > FrameClientSortList_t
static void lcl_ExportAnnotationStarts(TextRangeList_t &rPortions, Reference< XText > const &xParent, const SwUnoCursor *const pUnoCursor, SwAnnotationStartPortion_ImplList &rAnnotationStartArr, const sal_Int32 nIndex, const std::set< sal_Int32 > &rFramePositions, bool bOnlyFrame)
Exports all start annotation marks from rAnnotationStartArr into rPortions that have the same start p...
virtual const sal_Int32 * GetEnd() const
end position
Definition: txatbase.cxx:49
virtual sal_Int32 Len() const override
Definition: ndtxt.cxx:284
static SW_DLLPUBLIC MarkType GetType(const ::sw::mark::IMark &rMark)
Returns the MarkType used to create the mark.
Definition: docbm.cxx:483
virtual sal_Bool SAL_CALL hasMoreElements() override
const SwPosition * GetMark() const
Definition: pam.hxx:209
Provides access to the marks of a document.
Definition: doc.hxx:185
virtual SwPosition & GetMarkStart() const override
Definition: bookmrk.hxx:65
virtual const_iterator_t findFirstBookmarkStartsAfter(const SwPosition &rPos) const =0
Finds the first mark that is starting after.
virtual ~SwXTextPortionEnumeration() override
std::shared_ptr< SwXRedlinePortion_Impl > SwXRedlinePortion_ImplSharedPtr
SwNode & GetNode() const
Definition: ndindex.hxx:118
SwTextAttr * GetSortedByEnd(size_t nPos) const
Definition: ndhints.hxx:158
IDocumentMarkAccess * getIDocumentMarkAccess()
Definition: docbm.cxx:1552
#define RES_TXTATR_CHARFMT
Definition: hintids.hxx:142
sal_uInt16 Which() const
Definition: txatbase.hxx:110
static css::uno::Reference< css::text::XTextField > CreateXTextField(SwDoc *pDoc, SwFormatField const *pFormat, SwServiceType nServiceId=SwServiceType::Invalid)
Definition: unofield.cxx:1255
virtual sal_Int32 getAnnotationMarksCount() const =0
sal_Int32 GetAnyEnd() const
end (if available), else start
Definition: txatbase.hxx:153
#define CH_TXT_ATR_FORMELEMENT
Definition: hintids.hxx:50
const SwIndex * GetNext() const
Definition: index.hxx:102
size_type size() const
Definition: docary.hxx:368
SwContentNode * GetContentNode(bool bPoint=true) const
Definition: pam.hxx:229
#define RES_TXTATR_META
Definition: hintids.hxx:138
sal_uInt16 sal_Unicode
FUNC_TYPE const nType
SwIndex nContent
Definition: pam.hxx:38
static void lcl_FillRedlineArray(SwDoc const &rDoc, SwUnoCursor const &rUnoCursor, SwXRedlinePortion_ImplList &rRedArr)
virtual ::sw::mark::IFieldmark * getFieldmarkFor(const SwPosition &pos) const =0
bool operator()(const SwXRedlinePortion_ImplSharedPtr &r1, const SwXRedlinePortion_ImplSharedPtr &r2) const
static bool isTextBox(const SwFrameFormat *pFormat, sal_uInt16 nType)
Is the frame format a text box?
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
sal_Int32 GetStart() const
Definition: txatbase.hxx:82
virtual const_iterator_t findFirstAnnotationStartsAfter(const SwPosition &rPos) const =0
Finds the first mark that is starting after.
static css::uno::Reference< css::text::XTextContent > CreateXFieldmark(SwDoc &rDoc,::sw::mark::IMark *pMark, bool isReplacementObject=false)
Definition: unobkm.cxx:651
const SwRangeRedline * m_pRedline
void SetTOXMark(css::uno::Reference< css::text::XTextContent > const &xMark)
Definition: unoport.hxx:211
static const SwPosition & getPosition(const SwXRedlinePortion_ImplSharedPtr &r)
const SwFormatField & GetFormatField() const
Definition: txatbase.hxx:191
virtual sal_Bool SAL_CALL supportsService(const OUString &ServiceName) override
void Normalize(bool bPointFirst=true)
Normalizes PaM, i.e.
Definition: pam.cxx:518
static Reference< XTextRange > lcl_CreateRefMarkPortion(Reference< XText > const &xParent, const SwUnoCursor *const pUnoCursor, const SwTextAttr &rAttr, const bool bEnd)
void SelectPam(SwPaM &rPam, const bool bExpand)
Definition: unoobj.cxx:150
virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
#define RES_FLYFRMFMT
Definition: hintids.hxx:274
PaM is Point and Mark: a selection of the document model.
Definition: pam.hxx:136
void SetFootnote(css::uno::Reference< css::text::XFootnote > const &xNote)
Definition: unoport.hxx:217
static css::uno::Reference< css::text::XTextContent > CreateXBookmark(SwDoc &rDoc,::sw::mark::IMark *pBookmark)
Definition: unobkm.cxx:156
Style of a layout element.
Definition: frmfmt.hxx:57
size_t Count() const
Definition: ndhints.hxx:142
SwTextAttr * Get(size_t nPos) const
Definition: ndhints.hxx:144
static void lcl_ExportRedline(TextRangeList_t &rPortions, Reference< XText > const &xParent, const SwUnoCursor *const pUnoCursor, SwXRedlinePortion_ImplList &rRedlineArr, const sal_Int32 nIndex)
virtual OUString SAL_CALL getImplementationName() override
::sw::mark::IMark * GetAnnotationMark() const
Definition: atrfld.cxx:653
const SwPosition * GetPoint() const
Definition: pam.hxx:207
#define RES_TXTATR_FTN
Definition: hintids.hxx:152
const SwPosition * GetContentAnchor() const
Definition: fmtanchr.hxx:67
void SetBookmark(css::uno::Reference< css::text::XTextContent > const &xMark)
Definition: unoport.hxx:214
Count
void Exchange()
Definition: pam.cxx:469
static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId()
static uno::Reference< text::XTextRange > lcl_ExportFieldMark(uno::Reference< text::XText > const &i_xParentText, SwUnoCursor *const pUnoCursor, const SwTextNode *const pTextNode)
FlyAnchors.
Definition: fmtanchr.hxx:34
void CollectFrameAtNode(const SwNodeIndex &rIdx, FrameClientSortList_t &rFrames, const bool bAtCharAnchoredObjs)
Definition: unoobj2.cxx:179
bool HasMark() const
A PaM marks a selection if Point and Mark are distinct positions.
Definition: pam.hxx:205
SwDoc * GetDoc() const
Definition: pam.hxx:243
sal_Int32 indexOfAny(OUString const &rIn, sal_Unicode const *const pChars, sal_Int32 const nPos)
Marks a character position inside a document model node.
Definition: index.hxx:37
static void lcl_ExtractFramePositions(FrameClientSortList_t &rFrames, sal_Int32 nCurrentIndex, std::set< sal_Int32 > &rFramePositions)
Fills character positions from rFrames into rFramePositions.
#define CH_TXT_ATR_FIELDSTART
Definition: hintids.hxx:52
unsigned char sal_Bool
std::pair< TextRangeList_t *const, SwTextAttr const *const > PortionList_t
Definition: unoportenum.cxx:75
static css::uno::Reference< css::text::XDocumentIndexMark > CreateXDocumentIndexMark(SwDoc &rDoc, SwTOXMark *pMark, TOXTypes eType=TOX_INDEX)
Definition: unoidx.cxx:1628
void SetTextField(css::uno::Reference< css::text::XTextField > const &xField)
Definition: unoport.hxx:220
static void lcl_InsertRubyPortion(TextRangeList_t &rPortions, Reference< XText > const &xParent, const SwUnoCursor *const pUnoCursor, const SwTextAttr &rAttr, const bool bEnd)
virtual const_iterator_t getAnnotationMarksBegin() const =0
#define RES_TXTATR_TOXMARK
Definition: hintids.hxx:137
Marks a node in the document model.
Definition: ndindex.hxx:31
void SetCollapsed(bool bSet)
Definition: unoport.hxx:226
static void lcl_ExportBookmark(TextRangeList_t &rPortions, Reference< XText > const &xParent, const SwUnoCursor *const pUnoCursor, SwXBookmarkPortion_ImplList &rBkmArr, const sal_Int32 nIndex, const std::set< sal_Int32 > &rFramePositions, bool bOnlyFrameStarts)
Exports all bookmarks from rBkmArr into rPortions that have the same start or end position as nIndex...
std::stack< PortionList_t > PortionStack_t
Definition: unoportenum.cxx:76
DocumentType const eType
static css::uno::Reference< css::text::XTextContent > CreateXReferenceMark(SwDoc &rDoc, SwFormatRefMark *pMarkFormat)
Definition: unorefmk.cxx:131
SwXRedlinePortion_Impl(const SwRangeRedline *pRed, const bool bIsStart)
#define RES_TXTATR_INETFMT
Definition: hintids.hxx:141
const SwFormatFootnote & GetFootnote() const
Definition: txatbase.hxx:200
static sal_Int32 lcl_GetNextIndex(SwXBookmarkPortion_ImplList const &rBkmArr, SwXRedlinePortion_ImplList const &rRedlineArr, SwSoftPageBreakList const &rBreakArr)
const SwPosition * Start() const
Definition: pam.hxx:212
BkmType
Definition: unoportenum.cxx:87
static void lcl_FillFieldMarkArray(std::deque< sal_Int32 > &rFieldMarks, SwUnoCursor const &rUnoCursor, const sal_Int32 i_nStartPos)
void fillSoftPageBreakList(SwSoftPageBreakList &rBreak) const
SwTextNode is a paragraph in the document model.
Definition: ndtxt.hxx:79
OUString ExpandField(bool bCached, SwRootFrame const *pLayout) const
expand the field.
Definition: fldbas.cxx:412
IDocumentRedlineAccess const & getIDocumentRedlineAccess() const
Definition: doc.cxx:367
#define RES_TXTATR_FIELD
Definition: hintids.hxx:150
SwXTextPortionEnumeration(SwPaM &rParaCursor, css::uno::Reference< css::text::XText > const &xParent, const sal_Int32 nStart, const sal_Int32 nEnd)
#define RES_TXTATR_AUTOFMT
Definition: hintids.hxx:140
static void lcl_FillSoftPageBreakArray(SwUnoCursor const &rUnoCursor, SwSoftPageBreakList &rBreakArr)
An SwTextAttr container, stores all directly formatted text portions for a text node.
Definition: ndhints.hxx:67
sw::UnoCursorPointer m_pUnoCursor
Definition: unoport.hxx:242
std::multiset< SwXRedlinePortion_ImplSharedPtr, RedlineCompareStruct > SwXRedlinePortion_ImplList
std::set< sal_Int32 > SwSoftPageBreakList
Definition: wrtww8.hxx:137
void reset(std::shared_ptr< SwUnoCursor > pNew)
Definition: unocrsr.hxx:157
bool Right(sal_uInt16 nCnt)
Definition: swcrsr.hxx:171
#define RES_TXTATR_ANNOTATION
Definition: hintids.hxx:153
virtual const_iterator_t getBookmarksBegin() const =0
returns a STL-like random access iterator to the begin of the sequence the IBookmarks.
sal_Int32 GetIndex() const
Definition: index.hxx:95
virtual sal_Int64 SAL_CALL getSomething(const css::uno::Sequence< sal_Int8 > &aIdentifier) override
const SwPosition * End() const
Definition: pam.hxx:217
const SwModify * GetRegisteredIn() const
Definition: calbck.hxx:157
const sal_Int32 * End() const
Definition: txatbase.hxx:148
static Reference< XTextRange > lcl_ExportHints(PortionStack_t &rPortionStack, const Reference< XText > &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)
void SetRefMark(css::uno::Reference< css::text::XTextContent > const &xMark)
Definition: unoport.hxx:208
#define RES_TXTATR_FLYCNT
Definition: hintids.hxx:151
#define RES_TXTATR_REFMARK
Definition: hintids.hxx:136
virtual const SwPosition & GetMarkEnd() const =0
#define CH_TXT_ATR_FIELDEND
Definition: hintids.hxx:53
const SfxPoolItem & GetAttr() const
Definition: txatbase.hxx:159
#define SAL_WARN(area, stream)
SwFrameFormat * GetFrameFormat() const
Definition: fmtflcnt.hxx:45
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)
void SetMeta(css::uno::Reference< css::text::XTextContent > const &xMeta)
Definition: unoport.hxx:223
const SwIndex * GetFirstIndex() const
Definition: index.hxx:129
virtual const SwPosition & GetMarkStart() const =0
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)
Exports at-char anchored frames.
TextRangeList_t m_Portions
Definition: unoport.hxx:241
virtual const SwRedlineTable & GetRedlineTable() const =0
std::pair< const_iterator, bool > insert(Value &&x)
virtual bool IsExpanded() const =0
#define RES_TXTATR_INPUTFIELD
Definition: hintids.hxx:145
static uno::Reference< text::XTextRange > lcl_CreateMetaPortion(uno::Reference< text::XText > const &xParent, const SwUnoCursor *const pUnoCursor, SwTextAttr &rAttr, std::unique_ptr< TextRangeList_t const > &&pPortions)
const SwFormatField * GetAnnotationFormatField() const
static void lcl_ExportSoftPageBreak(TextRangeList_t &rPortions, Reference< XText > const &xParent, const SwUnoCursor *const pUnoCursor, SwSoftPageBreakList &rBreakArr, const sal_Int32 nIndex)
SwTextNode * GetTextNode()
Inline methods from Node.hxx.
Definition: ndtxt.hxx:843
static css::uno::Reference< css::text::XFootnote > GetObject(SwDoc &rDoc, const SwFormatFootnote &rFormat)
Definition: unocoll.cxx:1826
static void lcl_CreatePortions(TextRangeList_t &i_rPortions, uno::Reference< text::XText > const &i_xParentText, SwUnoCursor *pUnoCursor, FrameClientSortList_t &i_rFrames, const sal_Int32 i_nStartPos, const sal_Int32 i_nEndPos)
const SwFormatFlyCnt & GetFlyCnt() const
Definition: txatbase.hxx:206
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 std::set< sal_Int32 > &rFramePositions, bool bOnlyFrameBookmarkStarts)