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
