1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <ToxTextGenerator.hxx>
25 #include <rootfrm.hxx>
26 #include <ndindex.hxx>
27 #include <fchrfmt.hxx>
29 #include <IDocumentLayoutAccess.hxx>
30 #include <IDocumentStylePoolAccess.hxx>
34 #include <fmtautofmt.hxx>
35 #include <swatrset.hxx>
36 #include <ToxWhitespaceStripper.hxx>
37 #include <ToxLinkProcessor.hxx>
38 #include <ToxTabStopTokenHandler.hxx>
39 #include <txatbase.hxx>
40 #include <modeltoviewhelper.hxx>
41 #include <strings.hrc>
43 #include <osl/diagnose.h>
44 #include <rtl/ustrbuf.hxx>
45 #include <svl/itemiter.hxx>
53 bool sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(const SwTOXSortTabBase
& sortTab
)
55 if (sortTab
.aTOXSources
.empty()) {
58 if (sortTab
.aTOXSources
.at(0).pNd
== nullptr) {
64 // Similar to rtl::isAsciiWhiteSpace, but applicable to ToC entry number
65 bool isWhiteSpace(sal_Unicode ch
) { return ch
== ' ' || ch
== '\t'; }
67 } // end anonymous namespace
72 ToxTextGenerator::GetNumStringOfFirstNode(const SwTOXSortTabBase
& rBase
,
73 bool bUsePrefix
, sal_uInt8 nLevel
,
74 SwRootFrame
const*const pLayout
, bool bAddSpace
)
76 if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase
)) {
81 if (rBase
.pTextMark
) { // only if it's not a Mark
85 const SwTextNode
* pNd
= rBase
.aTOXSources
[0].pNd
->GetTextNode();
89 if (pLayout
&& pLayout
->HasMergedParas())
90 { // note: pNd could be any node, since it could be Sequence etc.
91 pNd
= sw::GetParaPropsNode(*pLayout
, *pNd
);
94 const SwNumRule
* pRule
= pNd
->GetNumRule();
99 if (pNd
->GetActualListLevel() < MAXLEVEL
) {
100 sRet
= pNd
->GetNumString(bUsePrefix
, nLevel
, pLayout
);
103 if (bAddSpace
&& !sRet
.isEmpty() && !isWhiteSpace(sRet
[sRet
.getLength() - 1])) {
104 sRet
+= " ";// Makes sure spacing is done only when there is outline numbering
111 ToxTextGenerator::ToxTextGenerator(const SwForm
& toxForm
,
112 std::shared_ptr
<ToxTabStopTokenHandler
> tabStopHandler
)
114 mLinkProcessor(std::make_shared
<ToxLinkProcessor
>()),
115 mTabStopTokenHandler(std::move(tabStopHandler
))
118 ToxTextGenerator::~ToxTextGenerator()
122 ToxTextGenerator::HandleChapterToken(const SwTOXSortTabBase
& rBase
,
123 const SwFormToken
& aToken
, SwRootFrame
const*const pLayout
) const
125 if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase
)) {
129 // A bit tricky: Find a random Frame
130 const SwContentNode
* contentNode
= rBase
.aTOXSources
.at(0).pNd
->GetContentNode();
136 const SwContentFrame
* contentFrame
= contentNode
->getLayoutFrame(pLayout
);
141 return GenerateTextForChapterToken(aToken
, contentFrame
, contentNode
, pLayout
);
145 ToxTextGenerator::GenerateTextForChapterToken(const SwFormToken
& chapterToken
, const SwContentFrame
* contentFrame
,
146 const SwContentNode
*contentNode
,
147 SwRootFrame
const*const pLayout
) const
151 SwChapterFieldType chapterFieldType
;
152 SwChapterField aField
= ObtainChapterField(&chapterFieldType
, &chapterToken
, contentFrame
, contentNode
);
155 // continue to support CF_NUMBER and CF_NUM_TITLE in order to handle ODF 1.0/1.1 written by OOo 3.x
156 // in the same way as OOo 2.x would handle them.
157 if (CF_NUM_NOPREPST_TITLE
== chapterToken
.nChapterFormat
|| CF_NUMBER
== chapterToken
.nChapterFormat
) {
158 retval
+= aField
.GetNumber(pLayout
); // get the string number without pre/postfix
160 else if (CF_NUMBER_NOPREPST
== chapterToken
.nChapterFormat
|| CF_NUM_TITLE
== chapterToken
.nChapterFormat
) {
161 retval
+= aField
.GetNumber(pLayout
) + " " + aField
.GetTitle(pLayout
);
162 } else if (CF_TITLE
== chapterToken
.nChapterFormat
) {
163 retval
+= aField
.GetTitle(pLayout
);
168 // Add parameter <_TOXSectNdIdx> and <_pDefaultPageDesc> in order to control,
169 // which page description is used, no appropriate one is found.
171 ToxTextGenerator::GenerateText(SwDoc
* pDoc
,
172 std::unordered_map
<OUString
, int> & rMarkURLs
,
173 const std::vector
<std::unique_ptr
<SwTOXSortTabBase
>> &entries
,
174 sal_uInt16 indexOfEntryToProcess
, sal_uInt16 numberOfEntriesToProcess
,
175 SwRootFrame
const*const pLayout
)
177 // pTOXNd is only set at the first mark
178 SwTextNode
* pTOXNd
= const_cast<SwTextNode
*>(entries
.at(indexOfEntryToProcess
)->pTOXNd
);
179 // FIXME this operates directly on the node text
180 OUString
& rText
= const_cast<OUString
&>(pTOXNd
->GetText());
182 for(sal_uInt16 nIndex
= indexOfEntryToProcess
; nIndex
< indexOfEntryToProcess
+ numberOfEntriesToProcess
; nIndex
++)
184 if(nIndex
> indexOfEntryToProcess
)
185 rText
+= ", "; // comma separation
186 // Initialize String with the Pattern from the form
187 const SwTOXSortTabBase
& rBase
= *entries
.at(nIndex
);
188 sal_uInt16 nLvl
= rBase
.GetLevel();
189 OSL_ENSURE( nLvl
< mToxForm
.GetFormMax(), "invalid FORM_LEVEL");
191 SvxTabStopItem
aTStops( 0, 0, SvxTabAdjust::Default
, RES_PARATR_TABSTOP
);
192 // create an enumerator
194 SwFormTokens aPattern
= mToxForm
.GetPattern(nLvl
);
195 // remove text from node
196 for (size_t i
= 0; i
< aPattern
.size(); ++i
) // #i21237#
198 const auto& aToken
= aPattern
[i
];
199 sal_Int32 nStartCharStyle
= rText
.getLength();
200 OUString aCharStyleName
= aToken
.sCharStyleName
;
201 switch( aToken
.eTokenType
)
205 // Only add space when there is outline numbering, and also when the next token
206 // is the entry text: it can also be e.g. a tab, or the entry number can be used
207 // in page number area like "2-15" for chapter 2, page 15.
208 rText
+= GetNumStringOfFirstNode(rBase
,
209 aToken
.nChapterFormat
== CF_NUMBER
,
210 static_cast<sal_uInt8
>(aToken
.nOutlineLevel
- 1), pLayout
,
211 i
< aPattern
.size() - 1 && aPattern
[i
+ 1].eTokenType
== TOKEN_ENTRY_TEXT
);
214 case TOKEN_ENTRY_TEXT
: {
215 HandledTextToken htt
= HandleTextToken(rBase
, pDoc
->GetAttrPool(), pLayout
);
216 ApplyHandledTextToken(htt
, *pTOXNd
);
223 rText
+= GetNumStringOfFirstNode(rBase
, true, MAXLEVEL
, pLayout
);
224 HandledTextToken htt
= HandleTextToken(rBase
, pDoc
->GetAttrPool(), pLayout
);
225 ApplyHandledTextToken(htt
, *pTOXNd
);
229 case TOKEN_TAB_STOP
: {
230 ToxTabStopTokenHandler::HandledTabStopToken htst
=
231 mTabStopTokenHandler
->HandleTabStopToken(aToken
, *pTOXNd
, pDoc
->getIDocumentLayoutAccess().GetCurrentLayout());
233 aTStops
.Insert(htst
.tabStop
);
238 rText
+= aToken
.sText
;
241 case TOKEN_PAGE_NUMS
:
242 rText
+= ConstructPageNumberPlaceholder(rBase
.aTOXSources
.size());
245 case TOKEN_CHAPTER_INFO
:
246 rText
+= HandleChapterToken(rBase
, aToken
, pLayout
);
249 case TOKEN_LINK_START
:
250 mLinkProcessor
->StartNewLink(rText
.getLength(), aToken
.sCharStyleName
);
255 auto [url
, isMark
] = rBase
.GetURL(pLayout
);
258 auto [iter
, _
] = rMarkURLs
.emplace(url
, 0);
259 (void) _
; // sigh... ignore it more explicitly
261 url
= "#" + OUString::number(iter
->second
) + url
;
263 mLinkProcessor
->CloseLink(rText
.getLength(), url
, /*bRelative=*/true);
267 case TOKEN_AUTHORITY
:
269 ToxAuthorityField eField
= static_cast<ToxAuthorityField
>(aToken
.nAuthorityField
);
270 SwContentIndex
aIdx( pTOXNd
, rText
.getLength() );
271 if (eField
== ToxAuthorityField::AUTH_FIELD_URL
)
273 aCharStyleName
= SwResId(STR_POOLCHR_INET_NORMAL
);
274 mLinkProcessor
->StartNewLink(rText
.getLength(), aCharStyleName
);
276 rBase
.FillText( *pTOXNd
, aIdx
, o3tl::narrowing
<sal_uInt16
>(eField
), pLayout
);
277 if (eField
== ToxAuthorityField::AUTH_FIELD_URL
)
279 // Get the absolute URL, the text may be a relative one.
280 const auto& rAuthority
= static_cast<const SwTOXAuthority
&>(rBase
);
281 OUString aURL
= SwTOXAuthority::GetSourceURL(
282 rAuthority
.GetText(AUTH_FIELD_URL
, pLayout
));
284 mLinkProcessor
->CloseLink(rText
.getLength(), aURL
, /*bRelative=*/false);
288 case TOKEN_END
: break;
291 if (!aCharStyleName
.isEmpty())
293 SwCharFormat
* pCharFormat
;
294 if( USHRT_MAX
!= aToken
.nPoolId
)
295 pCharFormat
= pDoc
->getIDocumentStylePoolAccess().GetCharFormatFromPool( aToken
.nPoolId
);
297 pCharFormat
= pDoc
->FindCharFormatByName(aCharStyleName
);
301 SwFormatCharFormat
aFormat( pCharFormat
);
302 pTOXNd
->InsertItem( aFormat
, nStartCharStyle
,
303 rText
.getLength(), SetAttrMode::DONTEXPAND
);
308 pTOXNd
->SetAttr( aTStops
);
310 mLinkProcessor
->InsertLinkAttributes(*pTOXNd
);
313 /*static*/ std::shared_ptr
<SfxItemSet
>
314 ToxTextGenerator::CollectAttributesForTox(const SwTextAttr
& hint
, SwAttrPool
& pool
)
316 auto retval
= std::make_shared
<SfxItemSet
>(pool
);
317 if (hint
.Which() != RES_TXTATR_AUTOFMT
) {
320 const SwFormatAutoFormat
& afmt
= hint
.GetAutoFormat();
321 SfxItemIter
aIter( *afmt
.GetStyleHandle());
322 const SfxPoolItem
* pItem
= aIter
.GetCurItem();
325 if (pItem
->Which() == RES_CHRATR_ESCAPEMENT
||
326 pItem
->Which() == RES_CHRATR_POSTURE
||
327 pItem
->Which() == RES_CHRATR_CJK_POSTURE
||
328 pItem
->Which() == RES_CHRATR_CTL_POSTURE
)
330 retval
->Put(std::unique_ptr
<SfxPoolItem
>(pItem
->Clone()));
332 pItem
= aIter
.NextItem();
337 void ToxTextGenerator::GetAttributesForNode(
338 ToxTextGenerator::HandledTextToken
& rResult
,
340 SwTextNode
const& rNode
,
341 ToxWhitespaceStripper
const& rStripper
,
343 SwRootFrame
const*const pLayout
)
345 // note: this *must* use the same flags as SwTextNode::GetExpandText()
346 // or indexes will be off!
347 ExpandMode eMode
= ExpandMode::ExpandFields
| ExpandMode::HideFieldmarkCommands
;
348 if (pLayout
&& pLayout
->IsHideRedlines())
350 eMode
|= ExpandMode::HideDeletions
;
352 ModelToViewHelper
aConversionMap(rNode
, pLayout
, eMode
);
353 if (SwpHints
const*const pHints
= rNode
.GetpSwpHints())
355 for (size_t i
= 0; i
< pHints
->Count(); ++i
)
357 const SwTextAttr
* pHint
= pHints
->Get(i
);
358 std::shared_ptr
<SfxItemSet
> attributesToClone
=
359 CollectAttributesForTox(*pHint
, rPool
);
360 if (attributesToClone
->Count() <= 0) {
364 // sw_redlinehide: due to the ... interesting ... multi-level index
365 // mapping going on here, can't use the usual merged attr iterators :(
367 sal_Int32
const nStart(aConversionMap
.ConvertToViewPosition(pHint
->GetStart()));
368 sal_Int32
const nEnd(aConversionMap
.ConvertToViewPosition(pHint
->GetAnyEnd()));
369 if (nStart
!= nEnd
) // might be in delete redline, and useless anyway
371 std::unique_ptr
<SwFormatAutoFormat
> pClone(pHint
->GetAutoFormat().Clone());
372 pClone
->SetStyleHandle(attributesToClone
);
373 rResult
.autoFormats
.push_back(std::move(pClone
));
374 // note the rStripper is on the whole merged text, so need rOffset
375 rResult
.startPositions
.push_back(
376 rStripper
.GetPositionInStrippedString(rOffset
+ nStart
));
377 rResult
.endPositions
.push_back(
378 rStripper
.GetPositionInStrippedString(rOffset
+ nEnd
));
382 rOffset
+= aConversionMap
.getViewText().getLength();
385 ToxTextGenerator::HandledTextToken
386 ToxTextGenerator::HandleTextToken(const SwTOXSortTabBase
& source
,
387 SwAttrPool
& pool
, SwRootFrame
const*const pLayout
)
389 HandledTextToken result
;
390 ToxWhitespaceStripper
stripper(source
.GetText().sText
);
391 result
.text
= stripper
.GetStrippedString();
393 // FIXME: there is a pre-existing problem that the index mapping of the
394 // attributes only works if the paragraph is fully selected
395 if (!source
.IsFullPara() || source
.aTOXSources
.empty())
398 const SwTextNode
* pSrc
= source
.aTOXSources
.front().pNd
->GetTextNode();
404 sal_Int32
nOffset(0);
405 GetAttributesForNode(result
, nOffset
, *pSrc
, stripper
, pool
, pLayout
);
406 if (pLayout
&& pLayout
->HasMergedParas())
408 if (SwTextFrame
const*const pFrame
= static_cast<SwTextFrame
*>(pSrc
->getLayoutFrame(pLayout
)))
410 if (sw::MergedPara
const*const pMerged
= pFrame
->GetMergedPara())
412 // pSrc already copied above
413 assert(pSrc
== pMerged
->pParaPropsNode
);
414 for (SwNodeOffset i
= pSrc
->GetIndex() + 1;
415 i
<= pMerged
->pLastNode
->GetIndex(); ++i
)
417 SwNode
*const pTmp(pSrc
->GetNodes()[i
]);
418 if (pTmp
->GetRedlineMergeFlag() == SwNode::Merge::NonFirst
)
420 GetAttributesForNode(result
, nOffset
,
421 *pTmp
->GetTextNode(), stripper
, pool
, pLayout
);
432 ToxTextGenerator::ApplyHandledTextToken(const HandledTextToken
& htt
, SwTextNode
& targetNode
)
434 sal_Int32 offset
= targetNode
.GetText().getLength();
435 SwContentIndex
aIdx(&targetNode
, offset
);
436 targetNode
.InsertText(htt
.text
, aIdx
);
437 for (size_t i
=0; i
< htt
.autoFormats
.size(); ++i
) {
438 targetNode
.InsertItem(*htt
.autoFormats
.at(i
),
439 htt
.startPositions
.at(i
) + offset
,
440 htt
.endPositions
.at(i
) + offset
);
445 ToxTextGenerator::ConstructPageNumberPlaceholder(size_t numberOfToxSources
)
447 if (numberOfToxSources
== 0) {
450 OUStringBuffer retval
;
451 // Place holder for the PageNumber; we only respect the first one
452 retval
.append(C_NUM_REPL
);
453 for (size_t i
= 1; i
< numberOfToxSources
; ++i
) {
454 retval
.append(SwTOXMark::S_PAGE_DELI
+ OUStringChar(C_NUM_REPL
));
456 retval
.append(C_END_PAGE_NUM
);
457 return retval
.makeStringAndClear();
460 /*virtual*/ SwChapterField
461 ToxTextGenerator::ObtainChapterField(SwChapterFieldType
* chapterFieldType
,
462 const SwFormToken
* chapterToken
, const SwContentFrame
* contentFrame
,
463 const SwContentNode
* contentNode
) const
465 assert(chapterToken
);
466 assert(chapterToken
->nOutlineLevel
>= 1);
468 SwChapterField
retval(chapterFieldType
, chapterToken
->nChapterFormat
);
469 retval
.SetLevel(static_cast<sal_uInt8
>(chapterToken
->nOutlineLevel
- 1));
471 retval
.ChangeExpansion(*contentFrame
, contentNode
, true);
474 } // end namespace sw
476 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */