Files
libreoffice/sw/source/core/tox/ToxTextGenerator.cxx
Tobias Lippert 0a1b1511fa Unittest generation of text for chapter entries in the TOC
Conflicts:
	sw/inc/ToxTextGenerator.hxx
	sw/qa/cppunit/tox/test_ToxTextGenerator.cxx
	sw/source/core/tox/ToxTextGenerator.cxx

Change-Id: I343958f85fb6718215a0caa456a825d72f168a57
Reviewed-on: https://gerrit.libreoffice.org/9612
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
2014-06-06 12:19:12 +00:00

437 lines
16 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <ToxTextGenerator.hxx>
#include "chpfld.hxx"
#include "cntfrm.hxx"
#include "pagefrm.hxx"
#include "fchrfmt.hxx"
#include "doc.hxx"
#include "fmtinfmt.hxx"
#include "ndtxt.hxx"
#include "pagedesc.hxx"
#include "tox.hxx"
#include "txmsrt.hxx"
#include "fmtautofmt.hxx"
#include "fmtfsize.hxx"
#include "fmtpdsc.hxx"
#include "DocumentSettingManager.hxx"
#include "SwStyleNameMapper.hxx"
#include "swatrset.hxx"
#include "ToxWhitespaceStripper.hxx"
#include "ToxLinkProcessor.hxx"
#include "txatbase.hxx"
#include "editeng/tstpitem.hxx"
#include "editeng/lrspitem.hxx"
#include "rtl/ustring.hxx"
#include "svl/itemiter.hxx"
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <cassert>
namespace {
bool sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(const SwTOXSortTabBase& sortTab)
{
if (sortTab.aTOXSources.empty()) {
return true;
}
if (sortTab.aTOXSources.at(0).pNd != NULL) {
return true;
}
return false;
}
} // end anonymous namespace
namespace sw {
OUString
ToxTextGenerator::GetNumStringOfFirstNode( const SwTOXSortTabBase& rBase, bool bUsePrefix, sal_uInt8 nLevel )
{
if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase)) {
return OUString();
}
OUString sRet;
if (!rBase.pTxtMark) { // only if it's not a Mark
return sRet;
}
const SwTxtNode* pNd = rBase.aTOXSources[0].pNd->GetTxtNode();
if (!pNd) {
return sRet;
}
const SwNumRule* pRule = pNd->GetNumRule();
if (!pRule) {
return sRet;
}
if (pNd->GetActualListLevel() < MAXLEVEL) {
sRet = pNd->GetNumString(bUsePrefix, nLevel);
}
return sRet;
}
ToxTextGenerator::ToxTextGenerator(const SwForm& toxForm)
:mToxForm(toxForm),
mLinkProcessor(new ToxLinkProcessor())
{;}
ToxTextGenerator::~ToxTextGenerator()
{;}
OUString
ToxTextGenerator::HandleChapterToken(const SwTOXSortTabBase& rBase, const SwFormToken& aToken,
SwDoc* pDoc) const
{
if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase)) {
return OUString();
}
// A bit tricky: Find a random Frame
const SwCntntNode* contentNode = rBase.aTOXSources.at(0).pNd->GetCntntNode();
if (!contentNode) {
return OUString();
}
// #i53420#
const SwCntntFrm* contentFrame = contentNode->getLayoutFrm(pDoc->GetCurrentLayout());
if (!contentFrame) {
return OUString();
}
return GenerateTextForChapterToken(aToken, contentFrame, contentNode);
}
OUString
ToxTextGenerator::GenerateTextForChapterToken(const SwFormToken& chapterToken, const SwCntntFrm* contentFrame,
const SwCntntNode *contentNode) const
{
OUString retval;
SwChapterFieldType chapterFieldType;
SwChapterField aFld = ObtainChapterField(&chapterFieldType, &chapterToken, contentFrame, contentNode);
//---> #i89791#
// continue to support CF_NUMBER and CF_NUM_TITLE in order to handle ODF 1.0/1.1 written by OOo 3.x
// in the same way as OOo 2.x would handle them.
if (CF_NUM_NOPREPST_TITLE == chapterToken.nChapterFormat || CF_NUMBER == chapterToken.nChapterFormat) {
retval += aFld.GetNumber(); // get the string number without pre/postfix
}
else if (CF_NUMBER_NOPREPST == chapterToken.nChapterFormat || CF_NUM_TITLE == chapterToken.nChapterFormat) {
retval += aFld.GetNumber();
retval += " ";
retval += aFld.GetTitle();
} else if (CF_TITLE == chapterToken.nChapterFormat) {
retval += aFld.GetTitle();
}
return retval;
}
// Add parameter <_TOXSectNdIdx> and <_pDefaultPageDesc> in order to control,
// which page description is used, no appropriate one is found.
void ToxTextGenerator::GenerateText(SwDoc* pDoc, const std::vector<SwTOXSortTabBase*> &entries,
sal_uInt16 indexOfEntryToProcess, sal_uInt16 numberOfEntriesToProcess, sal_uInt32 _nTOXSectNdIdx,
const SwPageDesc* _pDefaultPageDesc)
{
// pTOXNd is only set at the first mark
SwTxtNode* pTOXNd = (SwTxtNode*)entries.at(indexOfEntryToProcess)->pTOXNd;
// FIXME this operates directly on the node text
OUString & rTxt = const_cast<OUString&>(pTOXNd->GetTxt());
rTxt = "";
for(sal_uInt16 nIndex = indexOfEntryToProcess; nIndex < indexOfEntryToProcess + numberOfEntriesToProcess; nIndex++)
{
if(nIndex > indexOfEntryToProcess)
rTxt += ", "; // comma separation
// Initialize String with the Pattern from the form
const SwTOXSortTabBase& rBase = *entries.at(nIndex);
sal_uInt16 nLvl = rBase.GetLevel();
OSL_ENSURE( nLvl < mToxForm.GetFormMax(), "invalid FORM_LEVEL");
SvxTabStopItem aTStops( 0, 0, SVX_TAB_ADJUST_DEFAULT, RES_PARATR_TABSTOP );
// create an enumerator
// #i21237#
SwFormTokens aPattern = mToxForm.GetPattern(nLvl);
SwFormTokens::iterator aIt = aPattern.begin();
// remove text from node
while(aIt != aPattern.end()) // #i21237#
{
SwFormToken aToken = *aIt; // #i21237#
sal_Int32 nStartCharStyle = rTxt.getLength();
switch( aToken.eTokenType )
{
case TOKEN_ENTRY_NO:
// for TOC numbering
rTxt += GetNumStringOfFirstNode( rBase, aToken.nChapterFormat == CF_NUMBER, static_cast<sal_uInt8>(aToken.nOutlineLevel - 1) ) ;
break;
case TOKEN_ENTRY_TEXT: {
HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool());
ApplyHandledTextToken(htt, *pTOXNd);
}
break;
case TOKEN_ENTRY:
{
// for TOC numbering
rTxt += GetNumStringOfFirstNode( rBase, true, MAXLEVEL );
SwIndex aIdx( pTOXNd, rTxt.getLength() );
ToxWhitespaceStripper stripper(rBase.GetTxt().sText);
pTOXNd->InsertText(stripper.GetStrippedString(), aIdx);
}
break;
case TOKEN_TAB_STOP:
if (aToken.bWithTab) // #i21237#
rTxt += "\t";
if(SVX_TAB_ADJUST_END > aToken.eTabAlign)
{
const SvxLRSpaceItem& rLR =
(SvxLRSpaceItem&)pTOXNd->
SwCntntNode::GetAttr( RES_LR_SPACE, true );
long nTabPosition = aToken.nTabStopPosition;
if( !mToxForm.IsRelTabPos() && rLR.GetTxtLeft() )
nTabPosition -= rLR.GetTxtLeft();
aTStops.Insert( SvxTabStop( nTabPosition,
aToken.eTabAlign,
cDfltDecimalChar,
aToken.cTabFillChar ));
}
else
{
const SwPageDesc* pPageDesc = ((SwFmtPageDesc&)pTOXNd->
SwCntntNode::GetAttr( RES_PAGEDESC )).GetPageDesc();
bool bCallFindRect = true;
long nRightMargin;
if( pPageDesc )
{
const SwFrm* pFrm = pTOXNd->getLayoutFrm( pDoc->GetCurrentLayout(), 0, 0, true );
if( !pFrm || 0 == ( pFrm = pFrm->FindPageFrm() ) ||
pPageDesc != ((SwPageFrm*)pFrm)->GetPageDesc() )
// we have to go via the PageDesc here
bCallFindRect = false;
}
SwRect aNdRect;
if( bCallFindRect )
aNdRect = pTOXNd->FindLayoutRect( true );
if( aNdRect.IsEmpty() )
{
// Nothing helped so far, so we go via the PageDesc
sal_uInt32 nPgDescNdIdx = pTOXNd->GetIndex() + 1;
sal_uInt32* pPgDescNdIdx = &nPgDescNdIdx;
pPageDesc = pTOXNd->FindPageDesc( false, pPgDescNdIdx );
if ( !pPageDesc ||
*pPgDescNdIdx < _nTOXSectNdIdx )
{
// Use default page description, if none is found
// or the found one is given by a Node before the
// table-of-content section.
pPageDesc = _pDefaultPageDesc;
}
const SwFrmFmt& rPgDscFmt = pPageDesc->GetMaster();
nRightMargin = rPgDscFmt.GetFrmSize().GetWidth() -
rPgDscFmt.GetLRSpace().GetLeft() -
rPgDscFmt.GetLRSpace().GetRight();
}
else
nRightMargin = aNdRect.Width();
//#i24363# tab stops relative to indent
if( pDoc->GetDocumentSettingManager().get(IDocumentSettingAccess::TABS_RELATIVE_TO_INDENT) )
{
// left margin of paragraph style
const SvxLRSpaceItem& rLRSpace = pTOXNd->GetTxtColl()->GetLRSpace();
nRightMargin -= rLRSpace.GetLeft();
nRightMargin -= rLRSpace.GetTxtFirstLineOfst();
}
aTStops.Insert( SvxTabStop( nRightMargin, SVX_TAB_ADJUST_RIGHT,
cDfltDecimalChar,
aToken.cTabFillChar ));
}
break;
case TOKEN_TEXT:
rTxt += aToken.sText;
break;
case TOKEN_PAGE_NUMS:
rTxt += ConstructPageNumberPlaceholder(rBase.aTOXSources.size());
break;
case TOKEN_CHAPTER_INFO:
rTxt += HandleChapterToken(rBase, aToken, pDoc);
break;
case TOKEN_LINK_START:
mLinkProcessor->StartNewLink(rTxt.getLength(), aToken.sCharStyleName);
break;
case TOKEN_LINK_END:
mLinkProcessor->CloseLink(rTxt.getLength(), rBase.GetURL());
break;
case TOKEN_AUTHORITY:
{
ToxAuthorityField eField = (ToxAuthorityField)aToken.nAuthorityField;
SwIndex aIdx( pTOXNd, rTxt.getLength() );
rBase.FillText( *pTOXNd, aIdx, static_cast<sal_uInt16>(eField) );
}
break;
case TOKEN_END: break;
}
if ( !aToken.sCharStyleName.isEmpty() )
{
SwCharFmt* pCharFmt;
if( USHRT_MAX != aToken.nPoolId )
pCharFmt = pDoc->GetCharFmtFromPool( aToken.nPoolId );
else
pCharFmt = pDoc->FindCharFmtByName( aToken.sCharStyleName);
if (pCharFmt)
{
SwFmtCharFmt aFmt( pCharFmt );
pTOXNd->InsertItem( aFmt, nStartCharStyle,
rTxt.getLength(), nsSetAttrMode::SETATTR_DONTEXPAND );
}
}
++aIt; // #i21237#
}
pTOXNd->SetAttr( aTStops );
}
mLinkProcessor->InsertLinkAttributes(*pTOXNd);
}
/*static*/ boost::shared_ptr<SfxItemSet>
ToxTextGenerator::CollectAttributesForTox(const SwTxtAttr& hint, SwAttrPool& pool)
{
boost::shared_ptr<SfxItemSet> retval(new SfxItemSet(pool));
if (hint.Which() != RES_TXTATR_AUTOFMT) {
return retval;
}
const SwFmtAutoFmt& afmt = hint.GetAutoFmt();
SfxItemIter aIter( *afmt.GetStyleHandle());
const SfxPoolItem* pItem = aIter.GetCurItem();
while (true) {
if (pItem->Which() == RES_CHRATR_ESCAPEMENT ||
pItem->Which() == RES_CHRATR_POSTURE ||
pItem->Which() == RES_CHRATR_CJK_POSTURE ||
pItem->Which() == RES_CHRATR_CTL_POSTURE) {
SfxPoolItem* clonedItem = pItem->Clone(NULL);
retval->Put(*clonedItem);
}
if (aIter.IsAtEnd()) {
break;
}
pItem = aIter.NextItem();
}
return retval;
}
ToxTextGenerator::HandledTextToken
ToxTextGenerator::HandleTextToken(const SwTOXSortTabBase& source, SwAttrPool& pool)
{
HandledTextToken result;
ToxWhitespaceStripper stripper(source.GetTxt().sText);
result.text = stripper.GetStrippedString();
const SwTxtNode* pSrc = source.aTOXSources.at(0).pNd->GetTxtNode();
if (!pSrc->HasHints()) {
return result;
}
const SwpHints& hints = pSrc->GetSwpHints();
for (sal_uInt16 i = 0; i < hints.Count(); ++i) {
const SwTxtAttr* hint = hints[i];
boost::shared_ptr<SfxItemSet> attributesToClone = CollectAttributesForTox(*hint, pool);
if (attributesToClone->Count() <= 0) {
continue;
}
SwFmtAutoFmt* clone = static_cast<SwFmtAutoFmt*>(hint->GetAutoFmt().Clone());
clone->SetStyleHandle(attributesToClone);
result.autoFormats.push_back(clone);
result.startPositions.push_back(stripper.GetPositionInStrippedString(*hint->GetStart()));
result.endPositions.push_back(stripper.GetPositionInStrippedString(*hint->GetAnyEnd()));
}
return result;
}
/*static*/ void
ToxTextGenerator::ApplyHandledTextToken(const HandledTextToken& htt, SwTxtNode& targetNode)
{
sal_Int32 offset = targetNode.GetTxt().getLength();
SwIndex aIdx(&targetNode, offset);
targetNode.InsertText(htt.text, aIdx);
for (size_t i=0; i < htt.autoFormats.size(); ++i) {
targetNode.InsertItem(*htt.autoFormats.at(i),
htt.startPositions.at(i) + offset,
htt.endPositions.at(i) + offset);
}
}
/*static*/ OUString
ToxTextGenerator::ConstructPageNumberPlaceholder(size_t numberOfToxSources)
{
OUString retval;
if (numberOfToxSources == 0) {
return retval;
}
// Place holder for the PageNumber; we only respect the first one
retval += OUString(C_NUM_REPL);
for (size_t i = 1; i < numberOfToxSources; ++i) {
retval += S_PAGE_DELI;
retval += OUString(C_NUM_REPL);
}
retval += OUString(C_END_PAGE_NUM);
return retval;
}
/*virtual*/ SwChapterField
ToxTextGenerator::ObtainChapterField(SwChapterFieldType* chapterFieldType,
const SwFormToken* chapterToken, const SwCntntFrm* contentFrame,
const SwCntntNode* contentNode) const
{
assert(chapterToken);
assert(chapterToken->nOutlineLevel >= 1);
SwChapterField retval(chapterFieldType, chapterToken->nChapterFormat);
retval.SetLevel(static_cast<sal_uInt8>(chapterToken->nOutlineLevel - 1));
// #i53420#
retval.ChangeExpansion(contentFrame, contentNode, true);
return retval;
}
} // end namespace sw
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */