xref: /core/sw/source/core/tox/ToxTextGenerator.cxx (revision 80cedb5d)
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 <ToxTextGenerator.hxx>
21 
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 <fmtinfmt.hxx>
32 #include <ndtxt.hxx>
33 #include <pagedesc.hxx>
34 #include <tox.hxx>
35 #include <txmsrt.hxx>
36 #include <fmtautofmt.hxx>
37 #include <DocumentSettingManager.hxx>
38 #include <SwStyleNameMapper.hxx>
39 #include <swatrset.hxx>
40 #include <ToxWhitespaceStripper.hxx>
41 #include <ToxLinkProcessor.hxx>
42 #include <ToxTabStopTokenHandler.hxx>
43 #include <txatbase.hxx>
44 #include <modeltoviewhelper.hxx>
45 
46 #include <svl/itemiter.hxx>
47 
48 #include <cassert>
49 #include <memory>
50 
51 namespace {
52 
53 bool sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(const SwTOXSortTabBase& sortTab)
54 {
55     if (sortTab.aTOXSources.empty()) {
56         return true;
57     }
58     if (sortTab.aTOXSources.at(0).pNd == nullptr) {
59         return true;
60     }
61     return false;
62 }
63 
64 } // end anonymous namespace
65 
66 namespace sw {
67 
68 OUString
69 ToxTextGenerator::GetNumStringOfFirstNode(const SwTOXSortTabBase& rBase,
70         bool bUsePrefix, sal_uInt8 nLevel,
71         SwRootFrame const*const pLayout)
72 {
73     if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase)) {
74         return OUString();
75     }
76 
77     OUString sRet;
78     if (rBase.pTextMark) { // only if it's not a Mark
79         return sRet;
80     }
81 
82     const SwTextNode* pNd = rBase.aTOXSources[0].pNd->GetTextNode();
83     if (!pNd) {
84         return sRet;
85     }
86     if (pLayout && pLayout->IsHideRedlines())
87     {   // note: pNd could be any node, since it could be Sequence etc.
88         pNd = sw::GetParaPropsNode(*pLayout, SwNodeIndex(*pNd));
89     }
90 
91     const SwNumRule* pRule = pNd->GetNumRule();
92     if (!pRule) {
93         return sRet;
94     }
95 
96     if (pNd->GetActualListLevel() < MAXLEVEL) {
97         sRet = pNd->GetNumString(bUsePrefix, nLevel, pLayout);
98     }
99 
100     if (!sRet.isEmpty()) {
101         sRet += " ";// Makes sure spacing is done only when there is outline numbering
102     }
103 
104     return sRet;
105 }
106 
107 
108 ToxTextGenerator::ToxTextGenerator(const SwForm& toxForm,
109         std::shared_ptr<ToxTabStopTokenHandler> const & tabStopHandler)
110 : mToxForm(toxForm),
111   mLinkProcessor(new ToxLinkProcessor()),
112   mTabStopTokenHandler(tabStopHandler)
113 {}
114 
115 ToxTextGenerator::~ToxTextGenerator()
116 {}
117 
118 OUString
119 ToxTextGenerator::HandleChapterToken(const SwTOXSortTabBase& rBase,
120         const SwFormToken& aToken, SwRootFrame const*const pLayout) const
121 {
122     if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase)) {
123         return OUString();
124     }
125 
126     // A bit tricky: Find a random Frame
127     const SwContentNode* contentNode = rBase.aTOXSources.at(0).pNd->GetContentNode();
128     if (!contentNode) {
129         return OUString();
130     }
131 
132     // #i53420#
133     const SwContentFrame* contentFrame = contentNode->getLayoutFrame(pLayout);
134     if (!contentFrame) {
135         return OUString();
136     }
137 
138     return GenerateTextForChapterToken(aToken, contentFrame, contentNode, pLayout);
139 }
140 
141 OUString
142 ToxTextGenerator::GenerateTextForChapterToken(const SwFormToken& chapterToken, const SwContentFrame* contentFrame,
143         const SwContentNode *contentNode,
144         SwRootFrame const*const pLayout) const
145 {
146     OUString retval;
147 
148     SwChapterFieldType chapterFieldType;
149     SwChapterField aField = ObtainChapterField(&chapterFieldType, &chapterToken, contentFrame, contentNode);
150 
151     //---> #i89791#
152     // continue to support CF_NUMBER and CF_NUM_TITLE in order to handle ODF 1.0/1.1 written by OOo 3.x
153     // in the same way as OOo 2.x would handle them.
154     if (CF_NUM_NOPREPST_TITLE == chapterToken.nChapterFormat || CF_NUMBER == chapterToken.nChapterFormat) {
155         retval += aField.GetNumber(pLayout); // get the string number without pre/postfix
156     }
157     else if (CF_NUMBER_NOPREPST == chapterToken.nChapterFormat || CF_NUM_TITLE == chapterToken.nChapterFormat) {
158         retval += aField.GetNumber(pLayout);
159         retval += " ";
160         retval += aField.GetTitle(pLayout);
161     } else if (CF_TITLE == chapterToken.nChapterFormat) {
162         retval += aField.GetTitle(pLayout);
163     }
164     return retval;
165 }
166 
167 // Add parameter <_TOXSectNdIdx> and <_pDefaultPageDesc> in order to control,
168 // which page description is used, no appropriate one is found.
169 void
170 ToxTextGenerator::GenerateText(SwDoc* pDoc, const std::vector<std::unique_ptr<SwTOXSortTabBase>> &entries,
171         sal_uInt16 indexOfEntryToProcess, sal_uInt16 numberOfEntriesToProcess,
172         SwRootFrame const*const pLayout)
173 {
174     // pTOXNd is only set at the first mark
175     SwTextNode* pTOXNd = const_cast<SwTextNode*>(entries.at(indexOfEntryToProcess)->pTOXNd);
176     // FIXME this operates directly on the node text
177     OUString & rText = const_cast<OUString&>(pTOXNd->GetText());
178     rText.clear();
179     for(sal_uInt16 nIndex = indexOfEntryToProcess; nIndex < indexOfEntryToProcess + numberOfEntriesToProcess; nIndex++)
180     {
181         if(nIndex > indexOfEntryToProcess)
182             rText += ", "; // comma separation
183         // Initialize String with the Pattern from the form
184         const SwTOXSortTabBase& rBase = *entries.at(nIndex);
185         sal_uInt16 nLvl = rBase.GetLevel();
186         OSL_ENSURE( nLvl < mToxForm.GetFormMax(), "invalid FORM_LEVEL");
187 
188         SvxTabStopItem aTStops( 0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP );
189         // create an enumerator
190         // #i21237#
191         SwFormTokens aPattern = mToxForm.GetPattern(nLvl);
192         // remove text from node
193         for(const auto& aToken : aPattern) // #i21237#
194         {
195             sal_Int32 nStartCharStyle = rText.getLength();
196             switch( aToken.eTokenType )
197             {
198             case TOKEN_ENTRY_NO:
199                 // for TOC numbering
200                 rText += GetNumStringOfFirstNode(rBase,
201                     aToken.nChapterFormat == CF_NUMBER,
202                     static_cast<sal_uInt8>(aToken.nOutlineLevel - 1), pLayout);
203                 break;
204 
205             case TOKEN_ENTRY_TEXT: {
206                 HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool(), pLayout);
207                 ApplyHandledTextToken(htt, *pTOXNd);
208             }
209                 break;
210 
211             case TOKEN_ENTRY:
212                 {
213                     // for TOC numbering
214                     rText += GetNumStringOfFirstNode(rBase, true, MAXLEVEL, pLayout);
215                     HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool(), pLayout);
216                     ApplyHandledTextToken(htt, *pTOXNd);
217                 }
218                 break;
219 
220             case TOKEN_TAB_STOP: {
221                 ToxTabStopTokenHandler::HandledTabStopToken htst =
222                         mTabStopTokenHandler->HandleTabStopToken(aToken, *pTOXNd, pDoc->getIDocumentLayoutAccess().GetCurrentLayout());
223                 rText += htst.text;
224                 aTStops.Insert(htst.tabStop);
225                 break;
226             }
227 
228             case TOKEN_TEXT:
229                 rText += aToken.sText;
230                 break;
231 
232             case TOKEN_PAGE_NUMS:
233                 rText += ConstructPageNumberPlaceholder(rBase.aTOXSources.size());
234                 break;
235 
236             case TOKEN_CHAPTER_INFO:
237                 rText += HandleChapterToken(rBase, aToken, pLayout);
238                 break;
239 
240             case TOKEN_LINK_START:
241                 mLinkProcessor->StartNewLink(rText.getLength(), aToken.sCharStyleName);
242                 break;
243 
244             case TOKEN_LINK_END:
245                 mLinkProcessor->CloseLink(rText.getLength(), rBase.GetURL());
246                 break;
247 
248             case TOKEN_AUTHORITY:
249                 {
250                     ToxAuthorityField eField = static_cast<ToxAuthorityField>(aToken.nAuthorityField);
251                     SwIndex aIdx( pTOXNd, rText.getLength() );
252                     rBase.FillText( *pTOXNd, aIdx, static_cast<sal_uInt16>(eField), pLayout );
253                 }
254                 break;
255             case TOKEN_END: break;
256             }
257 
258             if ( !aToken.sCharStyleName.isEmpty() )
259             {
260                 SwCharFormat* pCharFormat;
261                 if( USHRT_MAX != aToken.nPoolId )
262                     pCharFormat = pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( aToken.nPoolId );
263                 else
264                     pCharFormat = pDoc->FindCharFormatByName( aToken.sCharStyleName);
265 
266                 if (pCharFormat)
267                 {
268                     SwFormatCharFormat aFormat( pCharFormat );
269                     pTOXNd->InsertItem( aFormat, nStartCharStyle,
270                         rText.getLength(), SetAttrMode::DONTEXPAND );
271                 }
272             }
273         }
274 
275         pTOXNd->SetAttr( aTStops );
276     }
277     mLinkProcessor->InsertLinkAttributes(*pTOXNd);
278 }
279 
280 /*static*/ std::shared_ptr<SfxItemSet>
281 ToxTextGenerator::CollectAttributesForTox(const SwTextAttr& hint, SwAttrPool& pool)
282 {
283     std::shared_ptr<SfxItemSet> retval(new SfxItemSet(pool));
284     if (hint.Which() != RES_TXTATR_AUTOFMT) {
285         return retval;
286     }
287     const SwFormatAutoFormat& afmt = hint.GetAutoFormat();
288     SfxItemIter aIter( *afmt.GetStyleHandle());
289     const SfxPoolItem* pItem = aIter.GetCurItem();
290     while (true) {
291         if (pItem->Which() == RES_CHRATR_ESCAPEMENT ||
292             pItem->Which() == RES_CHRATR_POSTURE ||
293             pItem->Which() == RES_CHRATR_CJK_POSTURE ||
294             pItem->Which() == RES_CHRATR_CTL_POSTURE) {
295             std::unique_ptr<SfxPoolItem> clonedItem(pItem->Clone());
296             retval->Put(*clonedItem);
297         }
298         if (aIter.IsAtEnd()) {
299             break;
300         }
301         pItem = aIter.NextItem();
302     }
303     return retval;
304 }
305 
306 void ToxTextGenerator::GetAttributesForNode(
307     ToxTextGenerator::HandledTextToken & rResult,
308     sal_Int32 & rOffset,
309     SwTextNode const& rNode,
310     ToxWhitespaceStripper const& rStripper,
311     SwAttrPool & rPool,
312     SwRootFrame const*const pLayout)
313 {
314     // note: this *must* use the same flags as SwTextNode::GetExpandText()
315     // or indexes will be off!
316     ExpandMode eMode = ExpandMode::ExpandFields;
317     if (pLayout && pLayout->IsHideRedlines())
318     {
319         eMode |= ExpandMode::HideDeletions;
320     }
321     ModelToViewHelper aConversionMap(rNode, pLayout, eMode);
322     if (SwpHints const*const pHints = rNode.GetpSwpHints())
323     {
324         for (size_t i = 0; i < pHints->Count(); ++i)
325         {
326             const SwTextAttr* pHint = pHints->Get(i);
327             std::shared_ptr<SfxItemSet> attributesToClone =
328                 CollectAttributesForTox(*pHint, rPool);
329             if (attributesToClone->Count() <= 0) {
330                 continue;
331             }
332 
333             // sw_redlinehide: due to the ... interesting ... multi-level index
334             // mapping going on here, can't use the usual merged attr iterators :(
335 
336             sal_Int32 const nStart(aConversionMap.ConvertToViewPosition(pHint->GetStart()));
337             sal_Int32 const nEnd(aConversionMap.ConvertToViewPosition(*pHint->GetAnyEnd()));
338             if (nStart != nEnd) // might be in delete redline, and useless anyway
339             {
340                 std::unique_ptr<SwFormatAutoFormat> pClone(
341                     static_cast<SwFormatAutoFormat*>(pHint->GetAutoFormat().Clone()));
342                 pClone->SetStyleHandle(attributesToClone);
343                 rResult.autoFormats.push_back(std::move(pClone));
344                 // note the rStripper is on the whole merged text, so need rOffset
345                 rResult.startPositions.push_back(
346                     rStripper.GetPositionInStrippedString(rOffset + nStart));
347                 rResult.endPositions.push_back(
348                     rStripper.GetPositionInStrippedString(rOffset + nEnd));
349             }
350         }
351     }
352     rOffset += aConversionMap.getViewText().getLength();
353 }
354 
355 ToxTextGenerator::HandledTextToken
356 ToxTextGenerator::HandleTextToken(const SwTOXSortTabBase& source,
357         SwAttrPool& pool, SwRootFrame const*const pLayout)
358 {
359     HandledTextToken result;
360     ToxWhitespaceStripper stripper(source.GetText().sText);
361     result.text = stripper.GetStrippedString();
362 
363     // FIXME: there is a pre-existing problem that the index mapping of the
364     // attributes only works if the paragraph is fully selected
365     if (!source.IsFullPara() || source.aTOXSources.empty())
366         return result;
367 
368     const SwTextNode* pSrc = source.aTOXSources.front().pNd->GetTextNode();
369     if (!pSrc)
370     {
371         return result;
372     }
373 
374     sal_Int32 nOffset(0);
375     GetAttributesForNode(result, nOffset, *pSrc, stripper, pool, pLayout);
376     if (pLayout && pLayout->IsHideRedlines())
377     {
378         if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pSrc->getLayoutFrame(pLayout)))
379         {
380             if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
381             {
382                 // pSrc already copied above
383                 assert(pSrc == pMerged->pParaPropsNode);
384                 for (sal_uLong i = pSrc->GetIndex() + 1;
385                      i <= pMerged->pLastNode->GetIndex(); ++i)
386                 {
387                     SwNode *const pTmp(pSrc->GetNodes()[i]);
388                     if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst)
389                     {
390                         GetAttributesForNode(result, nOffset,
391                                 *pTmp->GetTextNode(), stripper, pool, pLayout);
392                     }
393                 }
394             }
395         }
396     }
397 
398     return result;
399 }
400 
401 /*static*/ void
402 ToxTextGenerator::ApplyHandledTextToken(const HandledTextToken& htt, SwTextNode& targetNode)
403 {
404     sal_Int32 offset = targetNode.GetText().getLength();
405     SwIndex aIdx(&targetNode, offset);
406     targetNode.InsertText(htt.text, aIdx);
407     for (size_t i=0; i < htt.autoFormats.size(); ++i) {
408         targetNode.InsertItem(*htt.autoFormats.at(i),
409                 htt.startPositions.at(i) + offset,
410                 htt.endPositions.at(i) + offset);
411     }
412 }
413 
414 /*static*/ OUString
415 ToxTextGenerator::ConstructPageNumberPlaceholder(size_t numberOfToxSources)
416 {
417     if (numberOfToxSources == 0) {
418         return OUString();
419     }
420     OUStringBuffer retval;
421     // Place holder for the PageNumber; we only respect the first one
422     retval.append(C_NUM_REPL);
423     for (size_t i = 1; i < numberOfToxSources; ++i) {
424         retval.append(S_PAGE_DELI);
425         retval.append(C_NUM_REPL);
426     }
427     retval.append(C_END_PAGE_NUM);
428     return retval.makeStringAndClear();
429 }
430 
431 /*virtual*/ SwChapterField
432 ToxTextGenerator::ObtainChapterField(SwChapterFieldType* chapterFieldType,
433         const SwFormToken* chapterToken, const SwContentFrame* contentFrame,
434         const SwContentNode* contentNode) const
435 {
436     assert(chapterToken);
437     assert(chapterToken->nOutlineLevel >= 1);
438 
439     SwChapterField retval(chapterFieldType, chapterToken->nChapterFormat);
440     retval.SetLevel(static_cast<sal_uInt8>(chapterToken->nOutlineLevel - 1));
441     // #i53420#
442     retval.ChangeExpansion(*contentFrame, contentNode, true);
443     return retval;
444 }
445 } // end namespace sw
446 
447 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
448