Upgrade mdds to 3.0.0 and liborcus to 0.20.0
[LibreOffice.git] / sw / source / core / tox / ToxTextGenerator.cxx
blob32d18d1c8087b70e0d7cc3d1d13a02fa14a6fbab
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 #include <chpfld.hxx>
23 #include <cntfrm.hxx>
24 #include <txtfrm.hxx>
25 #include <rootfrm.hxx>
26 #include <ndindex.hxx>
27 #include <fchrfmt.hxx>
28 #include <doc.hxx>
29 #include <IDocumentLayoutAccess.hxx>
30 #include <IDocumentStylePoolAccess.hxx>
31 #include <ndtxt.hxx>
32 #include <tox.hxx>
33 #include <txmsrt.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>
47 #include <cassert>
48 #include <memory>
49 #include <utility>
51 namespace {
53 bool sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(const SwTOXSortTabBase& sortTab)
55 if (sortTab.aTOXSources.empty()) {
56 return true;
58 if (sortTab.aTOXSources.at(0).pNd == nullptr) {
59 return true;
61 return false;
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
69 namespace sw {
71 OUString
72 ToxTextGenerator::GetNumStringOfFirstNode(const SwTOXSortTabBase& rBase,
73 bool bUsePrefix, sal_uInt8 nLevel,
74 SwRootFrame const*const pLayout, bool bAddSpace)
76 if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase)) {
77 return OUString();
80 OUString sRet;
81 if (rBase.pTextMark) { // only if it's not a Mark
82 return sRet;
85 const SwTextNode* pNd = rBase.aTOXSources[0].pNd->GetTextNode();
86 if (!pNd) {
87 return sRet;
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();
95 if (!pRule) {
96 return sRet;
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
107 return sRet;
111 ToxTextGenerator::ToxTextGenerator(const SwForm& toxForm,
112 std::shared_ptr<ToxTabStopTokenHandler> tabStopHandler)
113 : mToxForm(toxForm),
114 mLinkProcessor(std::make_shared<ToxLinkProcessor>()),
115 mTabStopTokenHandler(std::move(tabStopHandler))
118 ToxTextGenerator::~ToxTextGenerator()
121 OUString
122 ToxTextGenerator::HandleChapterToken(const SwTOXSortTabBase& rBase,
123 const SwFormToken& aToken, SwRootFrame const*const pLayout) const
125 if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase)) {
126 return OUString();
129 // A bit tricky: Find a random Frame
130 const SwContentNode* contentNode = rBase.aTOXSources.at(0).pNd->GetContentNode();
131 if (!contentNode) {
132 return OUString();
135 // #i53420#
136 const SwContentFrame* contentFrame = contentNode->getLayoutFrame(pLayout);
137 if (!contentFrame) {
138 return OUString();
141 return GenerateTextForChapterToken(aToken, contentFrame, contentNode, pLayout);
144 OUString
145 ToxTextGenerator::GenerateTextForChapterToken(const SwFormToken& chapterToken, const SwContentFrame* contentFrame,
146 const SwContentNode *contentNode,
147 SwRootFrame const*const pLayout) const
149 OUString retval;
151 SwChapterFieldType chapterFieldType;
152 SwChapterField aField = ObtainChapterField(&chapterFieldType, &chapterToken, contentFrame, contentNode);
154 //---> #i89791#
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);
165 return retval;
168 std::optional<std::pair<SwTextNode *, SvxTabStopItem>>
169 ToxTextGenerator::GenerateText(SwDoc* pDoc,
170 std::unordered_map<OUString, int> & rMarkURLs,
171 const std::vector<std::unique_ptr<SwTOXSortTabBase>> &entries,
172 sal_uInt16 indexOfEntryToProcess, sal_uInt16 numberOfEntriesToProcess,
173 SwRootFrame const*const pLayout)
175 std::optional<std::pair<SwTextNode *, SvxTabStopItem>> oRet;
176 // pTOXNd is only set at the first mark
177 SwTextNode* pTOXNd = const_cast<SwTextNode*>(entries.at(indexOfEntryToProcess)->pTOXNd);
178 // FIXME this operates directly on the node text
179 OUString & rText = const_cast<OUString&>(pTOXNd->GetText());
180 rText.clear();
181 for(sal_uInt16 nIndex = indexOfEntryToProcess; nIndex < indexOfEntryToProcess + numberOfEntriesToProcess; nIndex++)
183 if(nIndex > indexOfEntryToProcess)
184 rText += ", "; // comma separation
185 // Initialize String with the Pattern from the form
186 const SwTOXSortTabBase& rBase = *entries.at(nIndex);
187 sal_uInt16 nLvl = rBase.GetLevel();
188 OSL_ENSURE( nLvl < mToxForm.GetFormMax(), "invalid FORM_LEVEL");
190 oRet.emplace(pTOXNd, SvxTabStopItem(0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP));
191 // create an enumerator
192 // #i21237#
193 SwFormTokens aPattern = mToxForm.GetPattern(nLvl);
194 // remove text from node
195 for (size_t i = 0; i < aPattern.size(); ++i) // #i21237#
197 const auto& aToken = aPattern[i];
198 sal_Int32 nStartCharStyle = rText.getLength();
199 OUString aCharStyleName = aToken.sCharStyleName;
200 switch( aToken.eTokenType )
202 case TOKEN_ENTRY_NO:
203 // for TOC numbering
204 // Only add space when there is outline numbering, and also when the next token
205 // is the entry text: it can also be e.g. a tab, or the entry number can be used
206 // in page number area like "2-15" for chapter 2, page 15.
207 rText += GetNumStringOfFirstNode(rBase,
208 aToken.nChapterFormat == CF_NUMBER,
209 static_cast<sal_uInt8>(aToken.nOutlineLevel - 1), pLayout,
210 i < aPattern.size() - 1 && aPattern[i + 1].eTokenType == TOKEN_ENTRY_TEXT);
211 break;
213 case TOKEN_ENTRY_TEXT: {
214 HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool(), pLayout);
215 ApplyHandledTextToken(htt, *pTOXNd);
217 break;
219 case TOKEN_ENTRY:
221 // for TOC numbering
222 rText += GetNumStringOfFirstNode(rBase, true, MAXLEVEL, pLayout);
223 HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool(), pLayout);
224 ApplyHandledTextToken(htt, *pTOXNd);
226 break;
228 case TOKEN_TAB_STOP: {
229 ToxTabStopTokenHandler::HandledTabStopToken htst =
230 mTabStopTokenHandler->HandleTabStopToken(aToken, *pTOXNd);
231 rText += htst.text;
232 oRet->second.Insert(htst.tabStop);
233 break;
236 case TOKEN_TEXT:
237 rText += aToken.sText;
238 break;
240 case TOKEN_PAGE_NUMS:
241 rText += ConstructPageNumberPlaceholder(rBase.aTOXSources.size());
242 break;
244 case TOKEN_CHAPTER_INFO:
245 rText += HandleChapterToken(rBase, aToken, pLayout);
246 break;
248 case TOKEN_LINK_START:
249 mLinkProcessor->StartNewLink(rText.getLength(), aToken.sCharStyleName);
250 break;
252 case TOKEN_LINK_END:
254 auto [url, isMark] = rBase.GetURL(pLayout);
255 if (isMark)
257 auto [iter, _] = rMarkURLs.emplace(url, 0);
258 (void) _; // sigh... ignore it more explicitly
259 ++iter->second;
260 url = "#" + OUString::number(iter->second) + url;
262 mLinkProcessor->CloseLink(rText.getLength(), url, /*bRelative=*/true);
264 break;
266 case TOKEN_AUTHORITY:
268 ToxAuthorityField eField = static_cast<ToxAuthorityField>(aToken.nAuthorityField);
269 SwContentIndex aIdx( pTOXNd, rText.getLength() );
270 if (eField == ToxAuthorityField::AUTH_FIELD_URL)
272 aCharStyleName = SwResId(STR_POOLCHR_INET_NORMAL);
273 mLinkProcessor->StartNewLink(rText.getLength(), aCharStyleName);
275 rBase.FillText( *pTOXNd, aIdx, o3tl::narrowing<sal_uInt16>(eField), pLayout );
276 if (eField == ToxAuthorityField::AUTH_FIELD_URL)
278 // Get the absolute URL, the text may be a relative one.
279 const auto& rAuthority = static_cast<const SwTOXAuthority&>(rBase);
280 OUString aURL = SwTOXAuthority::GetSourceURL(
281 rAuthority.GetText(AUTH_FIELD_URL, pLayout));
283 mLinkProcessor->CloseLink(rText.getLength(), aURL, /*bRelative=*/false);
286 break;
287 case TOKEN_END: break;
290 if (!aCharStyleName.isEmpty())
292 SwCharFormat* pCharFormat;
293 if( USHRT_MAX != aToken.nPoolId )
294 pCharFormat = pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( aToken.nPoolId );
295 else
296 pCharFormat = pDoc->FindCharFormatByName(aCharStyleName);
298 if (pCharFormat)
300 SwFormatCharFormat aFormat( pCharFormat );
301 pTOXNd->InsertItem( aFormat, nStartCharStyle,
302 rText.getLength(), SetAttrMode::DONTEXPAND );
307 mLinkProcessor->InsertLinkAttributes(*pTOXNd);
308 return oRet;
311 /*static*/ std::shared_ptr<SfxItemSet>
312 ToxTextGenerator::CollectAttributesForTox(const SwTextAttr& hint, SwAttrPool& pool)
314 auto retval = std::make_shared<SfxItemSet>(pool);
315 if (hint.Which() != RES_TXTATR_AUTOFMT) {
316 return retval;
318 const SwFormatAutoFormat& afmt = hint.GetAutoFormat();
319 SfxItemIter aIter( *afmt.GetStyleHandle());
320 const SfxPoolItem* pItem = aIter.GetCurItem();
323 if (pItem->Which() == RES_CHRATR_ESCAPEMENT ||
324 pItem->Which() == RES_CHRATR_POSTURE ||
325 pItem->Which() == RES_CHRATR_CJK_POSTURE ||
326 pItem->Which() == RES_CHRATR_CTL_POSTURE)
328 retval->Put(std::unique_ptr<SfxPoolItem>(pItem->Clone()));
330 pItem = aIter.NextItem();
331 } while (pItem);
332 return retval;
335 void ToxTextGenerator::GetAttributesForNode(
336 ToxTextGenerator::HandledTextToken & rResult,
337 sal_Int32 & rOffset,
338 SwTextNode const& rNode,
339 ToxWhitespaceStripper const& rStripper,
340 SwAttrPool & rPool,
341 SwRootFrame const*const pLayout)
343 // note: this *must* use the same flags as SwTextNode::GetExpandText()
344 // or indexes will be off!
345 ExpandMode eMode = ExpandMode::ExpandFields | ExpandMode::HideFieldmarkCommands;
346 if (pLayout && pLayout->IsHideRedlines())
348 eMode |= ExpandMode::HideDeletions;
350 ModelToViewHelper aConversionMap(rNode, pLayout, eMode);
351 if (SwpHints const*const pHints = rNode.GetpSwpHints())
353 for (size_t i = 0; i < pHints->Count(); ++i)
355 const SwTextAttr* pHint = pHints->Get(i);
356 std::shared_ptr<SfxItemSet> attributesToClone =
357 CollectAttributesForTox(*pHint, rPool);
358 if (attributesToClone->Count() <= 0) {
359 continue;
362 // sw_redlinehide: due to the ... interesting ... multi-level index
363 // mapping going on here, can't use the usual merged attr iterators :(
365 sal_Int32 const nStart(aConversionMap.ConvertToViewPosition(pHint->GetStart()));
366 sal_Int32 const nEnd(aConversionMap.ConvertToViewPosition(pHint->GetAnyEnd()));
367 if (nStart != nEnd) // might be in delete redline, and useless anyway
369 std::unique_ptr<SwFormatAutoFormat> pClone(pHint->GetAutoFormat().Clone());
370 pClone->SetStyleHandle(attributesToClone);
371 rResult.autoFormats.push_back(std::move(pClone));
372 // note the rStripper is on the whole merged text, so need rOffset
373 rResult.startPositions.push_back(
374 rStripper.GetPositionInStrippedString(rOffset + nStart));
375 rResult.endPositions.push_back(
376 rStripper.GetPositionInStrippedString(rOffset + nEnd));
380 rOffset += aConversionMap.getViewText().getLength();
383 ToxTextGenerator::HandledTextToken
384 ToxTextGenerator::HandleTextToken(const SwTOXSortTabBase& source,
385 SwAttrPool& pool, SwRootFrame const*const pLayout)
387 HandledTextToken result;
388 ToxWhitespaceStripper stripper(source.GetText().sText);
389 result.text = stripper.GetStrippedString();
391 // FIXME: there is a pre-existing problem that the index mapping of the
392 // attributes only works if the paragraph is fully selected
393 if (!source.IsFullPara() || source.aTOXSources.empty())
394 return result;
396 const SwTextNode* pSrc = source.aTOXSources.front().pNd->GetTextNode();
397 if (!pSrc)
399 return result;
402 sal_Int32 nOffset(0);
403 GetAttributesForNode(result, nOffset, *pSrc, stripper, pool, pLayout);
404 if (pLayout && pLayout->HasMergedParas())
406 if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pSrc->getLayoutFrame(pLayout)))
408 if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
410 // pSrc already copied above
411 assert(pSrc == pMerged->pParaPropsNode);
412 for (SwNodeOffset i = pSrc->GetIndex() + 1;
413 i <= pMerged->pLastNode->GetIndex(); ++i)
415 SwNode *const pTmp(pSrc->GetNodes()[i]);
416 if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst)
418 GetAttributesForNode(result, nOffset,
419 *pTmp->GetTextNode(), stripper, pool, pLayout);
426 return result;
429 /*static*/ void
430 ToxTextGenerator::ApplyHandledTextToken(const HandledTextToken& htt, SwTextNode& targetNode)
432 sal_Int32 offset = targetNode.GetText().getLength();
433 SwContentIndex aIdx(&targetNode, offset);
434 targetNode.InsertText(htt.text, aIdx);
435 for (size_t i=0; i < htt.autoFormats.size(); ++i) {
436 targetNode.InsertItem(*htt.autoFormats.at(i),
437 htt.startPositions.at(i) + offset,
438 htt.endPositions.at(i) + offset);
442 /*static*/ OUString
443 ToxTextGenerator::ConstructPageNumberPlaceholder(size_t numberOfToxSources)
445 if (numberOfToxSources == 0) {
446 return OUString();
448 OUStringBuffer retval;
449 // Place holder for the PageNumber; we only respect the first one
450 retval.append(C_NUM_REPL);
451 for (size_t i = 1; i < numberOfToxSources; ++i) {
452 retval.append(SwTOXMark::S_PAGE_DELI + OUStringChar(C_NUM_REPL));
454 retval.append(C_END_PAGE_NUM);
455 return retval.makeStringAndClear();
458 /*virtual*/ SwChapterField
459 ToxTextGenerator::ObtainChapterField(SwChapterFieldType* chapterFieldType,
460 const SwFormToken* chapterToken, const SwContentFrame* contentFrame,
461 const SwContentNode* contentNode) const
463 assert(chapterToken);
464 assert(chapterToken->nOutlineLevel >= 1);
466 SwChapterField retval(chapterFieldType, chapterToken->nChapterFormat);
467 retval.SetLevel(static_cast<sal_uInt8>(chapterToken->nOutlineLevel - 1));
468 // #i53420#
469 retval.ChangeExpansion(*contentFrame, contentNode, true);
470 return retval;
472 } // end namespace sw
474 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */