/* -*- 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 .
*/
// a: |-----| // b: // |---| => valid: b before a // |-----| => valid: start == end; b before a // |---------| => invalid: overlap (1) // |-----------| => valid: same end; b around a // |-----------------| => valid: b around a // |---| => valid; same start; b within a // |-----| => valid; same start and end; b around or within a? // |-----------| => valid: same start: b around a // |-| => valid: b within a // |---| => valid: same end; b within a // |---------| => invalid: overlap (2) // |-----| => valid: end == start; b after a // |---| => valid: b after a // ===> 2 invalid overlap cases static bool isOverlap(const sal_Int32 nStart1, const sal_Int32 nEnd1, const sal_Int32 nStart2, const sal_Int32 nEnd2)
{ return
((nStart1 > nStart2) && (nStart1 < nEnd2) && (nEnd1 > nEnd2)) // (1)
|| ((nStart1 < nStart2) && (nStart2 < nEnd1) && (nEnd1 < nEnd2)); // (2)
}
/// #i106930#: now asymmetric: empty hint1 is _not_ nested, but empty hint2 is static bool isNestedAny(const sal_Int32 nStart1, const sal_Int32 nEnd1, const sal_Int32 nStart2, const sal_Int32 nEnd2)
{ return ((nStart1 == nStart2) || (nEnd1 == nEnd2)) // same start/end: nested except if hint1 empty and hint2 not empty
? (nStart1 != nEnd1) || (nStart2 == nEnd2)
: ((nStart1 < nStart2) ? (nEnd1 >= nEnd2) : (nEnd1 <= nEnd2));
}
/** Calculate splitting policy for overlapping hints, based on what kind of hint is inserted, and what kind of existing hint overlaps.
*/ static Split_t
splitPolicy(const sal_uInt16 nWhichNew, const sal_uInt16 nWhichOther)
{ if (!isSplittable(nWhichOther))
{ if (!isSplittable(nWhichNew)) return FAIL; else return SPLIT_NEW;
} else
{ if ( RES_TXTATR_INPUTFIELD == nWhichNew ) return FAIL; elseif ( (RES_TXTATR_INETFMT == nWhichNew) &&
(RES_TXTATR_CJK_RUBY == nWhichOther) ) return SPLIT_NEW; else return SPLIT_OTHER;
}
}
/** Insert nesting hint into the hints array. Also calls NoteInHistory. @param rNewHint the hint to be inserted (must not overlap existing!)
*/ void SwpHints::InsertNesting(SwTextAttrNesting & rNewHint)
{
Insert(& rNewHint);
NoteInHistory( & rNewHint, true );
}
/**
The following hints correspond to well-formed XML elements in ODF: RES_TXTATR_INETFMT, RES_TXTATR_CJK_RUBY, RES_TXTATR_META, RES_TXTATR_METAFIELD, RES_TXTATR_CONTENTCONTROL
The writer core must ensure that these do not overlap; if they did, the document would not be storable as ODF.
Also, a Hyperlink must not be nested within another Hyperlink, and a Ruby must not be nested within another Ruby.
The ODF export in xmloff will only put a hyperlink into a ruby, never a ruby into a hyperlink.
Unfortunately the UNO API for Hyperlink and Ruby consists of the properties Hyperlink* and Ruby* of the css.text.CharacterProperties service. In other words, they are treated as formatting attributes, not as content entities. Furthermore, for API users it is not possible to easily test whether a certain range would be overlapping with other nested attributes, and most importantly, <em>which ones</em>, so we can hardly refuse to insert these in cases of overlap.
It is possible to split Hyperlink and Ruby into multiple portions, such that the result is properly nested.
meta and meta-field must not be split, because they have xml:id.
content controls should not split, either.
These constraints result in the following design:
RES_TXTATR_INETFMT: always succeeds inserts n attributes split at RES_TXTATR_CJK_RUBY, RES_TXTATR_META, RES_TXTATR_METAFIELD may replace existing RES_TXTATR_INETFMT at overlap RES_TXTATR_CJK_RUBY: always succeeds inserts n attributes split at RES_TXTATR_META, RES_TXTATR_METAFIELD may replace existing RES_TXTATR_CJK_RUBY at overlap may split existing overlapping RES_TXTATR_INETFMT RES_TXTATR_META: may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY inserts 1 attribute RES_TXTATR_METAFIELD: may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY inserts 1 attribute
The nesting is expressed by the position of the hints. RES_TXTATR_META and RES_TXTATR_METAFIELD have a CH_TXTATR, and there can only be one such hint starting and ending at a given position. Only RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY lack a CH_TXTATR. The interpretation given is that RES_TXTATR_CJK_RUBY is always around a RES_TXTATR_INETFMT at the same start and end position (which corresponds with the UNO API). Both of these are always around a nesting hint with CH_TXTATR at the same start and end position (if they should be inside, then the start should be after the CH_TXTATR). It would probably be a bad idea to add another nesting hint without CH_TXTATR; on the other hand, it would be difficult adding a CH_TXTATR to RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY, due to the overwriting and splitting of existing hints that is necessary for backward compatibility.
@param rNode the text node @param rHint the hint to be inserted @returns true iff hint was successfully inserted
*/ bool
SwpHints::TryInsertNesting( SwTextNode & rNode, SwTextAttrNesting & rNewHint )
{ // INVARIANT: the nestable hints in the array are properly nested const sal_uInt16 nNewWhich( rNewHint.Which() ); const sal_Int32 nNewStart( rNewHint.GetStart() ); const sal_Int32 nNewEnd ( *rNewHint.GetEnd() ); constbool bNewSelfNestable( isSelfNestable(nNewWhich) );
NestList_t OverlappingExisting; // existing hints to be split
NestList_t OverwrittenExisting; // existing hints to be replaced
NestList_t SplitNew; // new hints to be inserted
SplitNew.push_back(& rNewHint);
// pass 1: split the inserted hint into fragments if necessary for ( size_t i = 0; i < Count(); ++i )
{
SwTextAttr * const pOther = GetSortedByEnd(i);
if (pOther->IsNesting())
{ const sal_uInt16 nOtherWhich( pOther->Which() ); const sal_Int32 nOtherStart( pOther->GetStart() ); const sal_Int32 nOtherEnd ( *pOther->GetEnd() ); if (isOverlap(nNewStart, nNewEnd, nOtherStart, nOtherEnd ))
{ switch (splitPolicy(nNewWhich, nOtherWhich))
{ case FAIL:
SAL_INFO("sw.core", "cannot insert hint: overlap"); for (constauto& aSplit : SplitNew)
TextAttrDelete(aSplit); returnfalse; case SPLIT_NEW:
lcl_DoSplitNew(SplitNew, rNode, nNewStart,
nOtherStart, nOtherEnd, pOther->HasDummyChar()); break; case SPLIT_OTHER:
OverlappingExisting.push_back(
static_txtattr_cast<SwTextAttrNesting*>(pOther)); break; default:
assert(!"bad code monkey"); break;
}
} elseif (isNestedAny(nNewStart, nNewEnd, nOtherStart, nOtherEnd))
{ if (!bNewSelfNestable && (nNewWhich == nOtherWhich))
{ // ruby and hyperlink: if there is nesting, _overwrite_
OverwrittenExisting.push_back(
static_txtattr_cast<SwTextAttrNesting*>(pOther));
} elseif ((nNewStart == nOtherStart) && pOther->HasDummyChar())
{ if (rNewHint.HasDummyChar())
{
assert(!"ERROR: inserting duplicate CH_TXTATR hint"); returnfalse;
} elseif (nNewEnd < nOtherEnd) { // other has dummy char, new is inside other, but // new contains the other's dummy char? // should be corrected because it may lead to problems // in SwXMeta::createEnumeration // SplitNew is sorted, so this is the first split
assert(SplitNew.front()->GetStart() == nNewStart);
SplitNew.front()->SetStart(nNewStart + 1);
}
}
}
}
}
// pass 1b: tragically need to check for fieldmarks here too for (auto iter = SplitNew.begin(); iter != SplitNew.end(); ++iter)
{
SwPaM const temp(rNode, (*iter)->GetStart(), rNode, *(*iter)->GetEnd());
std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
sw::CalcBreaks(Breaks, temp, true); if (!Breaks.empty())
{ if (!isSplittable(nNewWhich))
{
SAL_INFO("sw.core", "cannot insert hint: fieldmark overlap");
assert(SplitNew.size() == 1);
TextAttrDelete(&rNewHint); returnfalse;
} else
{ for (autoconst& rPos : Breaks)
{
assert(rPos.first == rNode.GetIndex());
iter = lcl_DoSplitImpl(SplitNew, rNode, iter,
rPos.second, true, true);
}
}
}
}
assert((isSplittable(nNewWhich) || SplitNew.size() == 1) && "splitting the unsplittable ???");
// pass 2: split existing hints that overlap/nest with new hint // do not iterate over hints array, but over remembered set of overlapping // hints, to keep things simple w.r.t. insertion/removal // N.B: if there is a hint that splits the inserted hint, then // that hint would also have already split any hint in OverlappingExisting // so any hint in OverlappingExisting can be split at most by one hint // in SplitNew, or even not at all (this is not true for existing hints // that go _around_ new hint, which is the reason d'^etre for pass 4) for (auto& rpOther : OverlappingExisting)
{ const sal_Int32 nOtherStart( rpOther->GetStart() ); const sal_Int32 nOtherEnd ( *rpOther->GetEnd() );
switch (ComparePosition(nSplitNewStart, nSplitNewEnd,
nOtherStart, nOtherEnd))
{ case SwComparePosition::Inside:
{
assert(!bRemoveOverlap && "this one should be in OverwrittenExisting?");
} break; case SwComparePosition::Outside: case SwComparePosition::Equal:
{
assert(!"existing hint inside new hint: why?");
} break; case SwComparePosition::OverlapBefore:
{ Delete( rpOther ); // this also does NoteInHistory!
rpOther->SetStart(nSplitNewEnd);
InsertNesting( *rpOther ); if (!bRemoveOverlap)
{ if ( MAX_HINTS <= Count() )
{
SAL_INFO("sw.core", "hints array full :-("); returnfalse;
}
SwTextAttrNesting * const pOtherLeft(
MakeTextAttrNesting( rNode, *rpOther,
nOtherStart, nSplitNewEnd ) );
InsertNesting( *pOtherLeft );
}
} break; case SwComparePosition::OverlapBehind:
{ Delete( rpOther ); // this also does NoteInHistory!
rpOther->SetEnd(nSplitNewStart);
InsertNesting( *rpOther ); if (!bRemoveOverlap)
{ if ( MAX_HINTS <= Count() )
{
SAL_INFO("sw.core", "hints array full :-("); returnfalse;
}
SwTextAttrNesting * const pOtherRight(
MakeTextAttrNesting( rNode, *rpOther,
nSplitNewStart, nOtherEnd ) );
InsertNesting( *pOtherRight );
}
} break; default: break; // overlap resolved by splitting new: nothing to do
}
}
}
if ( MAX_HINTS <= Count() || MAX_HINTS - Count() <= SplitNew.size() )
{
SAL_INFO("sw.core", "hints array full :-("); returnfalse;
}
// pass 3: insert new hints for (constauto& rpHint : SplitNew)
{
InsertNesting(*rpHint);
}
// pass 4: handle overwritten hints // RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY should displace attributes // of the same kind. for (auto& rpOther : OverwrittenExisting)
{ const sal_Int32 nOtherStart( rpOther->GetStart() ); const sal_Int32 nOtherEnd ( *rpOther->GetEnd() );
// overwritten portion is given by start/end of inserted hint if ((nNewStart <= nOtherStart) && (nOtherEnd <= nNewEnd))
{ Delete(rpOther);
rNode.DestroyAttr( rpOther );
} else
{
assert((nOtherStart < nNewStart) || (nNewEnd < nOtherEnd)); // scenario: there is a RUBY, and contained within that a META; // now a RUBY is inserted within the META => the existing RUBY is split: // here it is not possible to simply insert the left/right fragment // of the existing RUBY because they <em>overlap</em> with the META! Delete( rpOther ); // this also does NoteInHistory! if (nNewEnd < nOtherEnd)
{
SwTextAttrNesting * const pOtherRight(
MakeTextAttrNesting(
rNode, *rpOther, nNewEnd, nOtherEnd ) ); boolconst bSuccess( TryInsertNesting(rNode, *pOtherRight) );
SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 1 failed?");
} if (nOtherStart < nNewStart)
{
rpOther->SetEnd(nNewStart); boolconst bSuccess( TryInsertNesting(rNode, *rpOther) );
SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 2 failed?");
} else
{
rNode.DestroyAttr(rpOther);
}
}
}
returntrue;
}
// This function takes care for the following text attribute: // RES_TXTATR_CHARFMT, RES_TXTATR_AUTOFMT // These attributes have to be handled in a special way (Portion building).
// The new attribute will be split by any existing RES_TXTATR_AUTOFMT or // RES_TXTATR_CHARFMT. The new attribute itself will // split any existing RES_TXTATR_AUTOFMT or RES_TXTATR_CHARFMT.
// 2. Find the hints which cover the start and end position // of the new hint. These hints have to be split into two portions:
if ( !bNoLengthAttribute ) // nothing to do for no length attributes
{ for ( size_t i = 0, nCnt = m_HintsByStart.size(); i < nCnt; ++i )
{ // we're modifying stuff here which affects the sorting, and we // don't want it changing underneath us
SwTextAttr* pOther = m_HintsByStart[i];
if ( !bNoLengthAttribute ) // nothing to do for no length attributes
{ for ( size_t i = 0, nCnt = m_HintsByStart.size(); i < nCnt; ++i )
{ const SwTextAttr* pOther = m_HintsByStart[i];
// Get all hints that are in [nPorStart, nPorEnd[: for ( size_t i = 0, nCnt = m_HintsByStart.size(); i < nCnt; ++i )
{ // we get called from TryInsertHint, which changes ordering
SwTextAttr *pOther = m_HintsByStart[i];
SwTextAttr* pNewAttr = nullptr; if ( RES_TXTATR_CHARFMT == nWhich )
{ // pNewHint can be inserted after calculating the sort value. // This should ensure, that pNewHint comes behind the already present // character style
sal_uInt16 nCharStyleCount = 0; for ( constauto& rpHint : aInsDelHints )
{ if ( RES_TXTATR_CHARFMT == rpHint->Which() )
{ // #i74589# const SwFormatCharFormat& rOtherCharFormat = rpHint->GetCharFormat(); const SwFormatCharFormat& rThisCharFormat = rNewHint.GetCharFormat(); constbool bSameCharFormat = rOtherCharFormat.GetCharFormat() == rThisCharFormat.GetCharFormat();
// #i90311# // Do not remove existing character format hint during XML import if ( !rNode.GetDoc().IsInXMLImport() &&
( !( SetAttrMode::DONTREPLACE & nMode ) ||
bNoLengthAttribute ||
bSameCharFormat ) )
{ // Remove old hint Delete( rpHint );
rNode.DestroyAttr( rpHint );
} else
++nCharStyleCount;
} else
{ // remove all attributes from auto styles, which are explicitly set in // the new character format:
OSL_ENSURE( RES_TXTATR_AUTOFMT == rpHint->Which(), "AUTOSTYLES - Misc trouble" );
SwTextAttr* pOther = rpHint; const std::shared_ptr<SfxItemSet> & pOldStyle = static_cast<const SwFormatAutoFormat&>(pOther->GetAttr()).GetStyleHandle();
// For each attribute in the automatic style check if it // is also set the new character style:
SfxItemSet aNewSet( *pOldStyle->GetPool(),
aCharAutoFormatSetRange);
SfxItemIter aItemIter( *pOldStyle ); const SfxPoolItem* pItem = aItemIter.GetCurItem(); do
{ if ( !CharFormat::IsItemIncluded( pItem->Which(), &rNewHint ) )
{
aNewSet.Put( *pItem );
}
pItem = aItemIter.NextItem();
} while (pItem);
// Remove old hint Delete( pOther );
rNode.DestroyAttr( pOther );
// If there is no current hint and start and end of rNewHint // is ok, we do not need to create a new txtattr. if ( nPorStart == nThisStart &&
nPorEnd == nThisEnd &&
!nCharStyleCount )
{
pNewAttr = &rNewHint;
bDestroyHint = false;
} else
{
pNewAttr = MakeTextAttr( rNode.GetDoc(), rNewHint.GetAttr(),
nPorStart, nPorEnd );
static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(nCharStyleCount);
}
} else
{ // Find the current autostyle. Mix attributes if necessary.
SwTextAttr* pCurrentAutoStyle = nullptr;
SwTextAttr* pCurrentCharFormat = nullptr; for ( constauto& rpHint : aInsDelHints )
{ if ( RES_TXTATR_AUTOFMT == rpHint->Which() )
pCurrentAutoStyle = rpHint; elseif ( RES_TXTATR_CHARFMT == rpHint->Which() )
pCurrentCharFormat = rpHint;
}
// #i75750# Remove attributes already set at whole paragraph // #i81764# This should not be applied for no length attributes!!! <-- if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && aNewSet.Count() )
{ const SfxItemSet& rWholeParaAttrSet(rNode.GetSwAttrSet());
std::vector<sal_uInt16> aDeleteWhichIDs;
for (SfxItemIter aIter(aNewSet); !aIter.IsAtEnd(); aIter.NextItem())
{ const SfxPoolItem* pGet(nullptr); if (SfxItemState::SET == rWholeParaAttrSet.GetItemState(aIter.GetCurWhich(), false, &pGet) &&
SfxPoolItem::areSame(pGet, aIter.GetCurItem()))
{ // Do not clear item if the attribute is set in a character format: if (!pCurrentCharFormat || nullptr == CharFormat::GetItem(*pCurrentCharFormat, aIter.GetCurWhich()))
aDeleteWhichIDs.push_back(aIter.GetCurWhich());
}
}
for (auto nDelWhich : aDeleteWhichIDs)
aNewSet.ClearItem(nDelWhich);
}
// Remove old hint Delete( pCurrentAutoStyle );
rNode.DestroyAttr( pCurrentAutoStyle );
// Create new AutoStyle if ( aNewSet.Count() )
pNewAttr = MakeTextAttr( rNode.GetDoc(), aNewSet,
nPorStart, nPorEnd );
} else
{ // Remove any attributes which are already set at the whole paragraph: bool bOptimizeAllowed = true;
// #i75750# Remove attributes already set at whole paragraph // #i81764# This should not be applied for no length attributes!!! <-- if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && pNewStyle->Count() )
{
std::unique_ptr<SfxItemSet> pNewSet;
do
{ const SfxPoolItem* pTmpItem = nullptr; // here direct SfxPoolItem ptr comp was wrong, found using SfxPoolItem::areSame if ( SfxItemState::SET == rWholeParaAttrSet.GetItemState( pItem->Which(), false, &pTmpItem ) &&
SfxPoolItem::areSame(pTmpItem, pItem) )
{ // Do not clear item if the attribute is set in a character format: if ( !pCurrentCharFormat || nullptr == CharFormat::GetItem( *pCurrentCharFormat, pItem->Which() ) )
{ if ( !pNewSet )
pNewSet = pNewStyle->Clone();
pNewSet->ClearItem( pItem->Which() );
}
}
} while ((pItem = aIter2.NextItem()));
// Create new AutoStyle // If there is no current hint and start and end of rNewHint // is ok, we do not need to create a new txtattr. if ( bOptimizeAllowed &&
nPorStart == nThisStart &&
nPorEnd == nThisEnd )
{
pNewAttr = &rNewHint;
bDestroyHint = false;
} elseif ( pNewStyle )
{
pNewAttr = MakeTextAttr( rNode.GetDoc(), *pNewStyle,
nPorStart, nPorEnd );
}
}
}
if ( pNewAttr )
{
Insert( pNewAttr ); // if ( bDestroyHint )
NoteInHistory( pNewAttr, true );
}
if ( bDestroyHint )
rNode.DestroyAttr( &rNewHint );
}
SwTextAttr* MakeRedlineTextAttr( SwDoc & rDoc, SfxPoolItem const & rAttr )
{ // this is intended _only_ for special-purpose redline attributes! switch (rAttr.Which())
{ case RES_CHRATR_COLOR: case RES_CHRATR_WEIGHT: case RES_CHRATR_CJK_WEIGHT: case RES_CHRATR_CTL_WEIGHT: case RES_CHRATR_POSTURE: case RES_CHRATR_CJK_POSTURE: case RES_CHRATR_CTL_POSTURE: case RES_CHRATR_UNDERLINE: case RES_CHRATR_CROSSEDOUT: case RES_CHRATR_CASEMAP: case RES_CHRATR_BACKGROUND: break; default:
assert(!"unsupported redline attribute"); break;
}
// create a SfxPoolItemHolder and return it (will move Item to referenced mode) returnnew SwTextAttrEnd(SfxPoolItemHolder(rDoc.GetAttrPool(), &rAttr), 0, 0);
}
// create new text attribute
SwTextAttr* MakeTextAttr(
SwDoc & rDoc,
SfxPoolItem& rAttr,
sal_Int32 const nStt,
sal_Int32 const nEnd,
CopyOrNewType const bIsCopy,
SwTextNode *const pTextNode )
{ if ( isCHRATR(rAttr.Which()) )
{ // Somebody wants to build a SwTextAttr for a character attribute. // Sorry, this is not allowed any longer. // You'll get a brand new autostyle attribute:
SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aItemSet( rDoc.GetAttrPool() );
aItemSet.Put( rAttr ); return MakeTextAttr( rDoc, aItemSet, nStt, nEnd );
} elseif ( RES_TXTATR_AUTOFMT == rAttr.Which() && static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle()->
GetPool() != &rDoc.GetAttrPool() )
{ // If the attribute is an auto-style which refers to a pool that is // different from rDoc's pool, we have to correct this: const std::shared_ptr<SfxItemSet> & pAutoStyle = static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle();
std::unique_ptr<const SfxItemSet> pNewSet(
pAutoStyle->SfxItemSet::Clone( true, &rDoc.GetAttrPool() ));
SwTextAttr* pNew = MakeTextAttr( rDoc, *pNewSet, nStt, nEnd ); return pNew;
}
// create a SfxPoolItemHolder and use it (will move Item to referenced mode) const SfxPoolItemHolder aHolder(rDoc.GetAttrPool(), &rAttr);
SfxPoolItem& rNew(const_cast<SfxPoolItem&>(*aHolder.getItem()));
pNew = new SwTextCharFormat( aHolder, nStt, nEnd );
} break; case RES_TXTATR_INETFMT:
pNew = new SwTextINetFormat( aHolder, nStt, nEnd ); break;
case RES_TXTATR_FIELD:
pNew = new SwTextField( aHolder, nStt,
rDoc.IsClipBoard() ); break;
case RES_TXTATR_ANNOTATION:
{
pNew = new SwTextAnnotationField( aHolder, nStt, rDoc.IsClipBoard() ); if (bIsCopy == CopyOrNewType::Copy)
{ // On copy of the annotation field do not keep the annotated text range by removing // the relation to its annotation mark (relation established via annotation field's name). // If the annotation mark is also copied, the relation and thus the annotated text range will be reestablished, // when the annotation mark is created and inserted into the document. auto& pField = const_cast<SwPostItField&>(dynamic_cast<const SwPostItField&>(*(pNew->GetFormatField().GetField())));
if (!rDoc.IsInWriterfilterImport())
{ // We set the name here to make the object referencable.
pField.SetName(sw::mark::MarkBase::GenerateNewName(u"__Annotation__"));
} else// Keep the previous behaviour while loading the file.
pField.SetName(SwMarkName());
pField.SetPostItId();
}
} break;
case RES_TXTATR_INPUTFIELD:
pNew = new SwTextInputField( aHolder, nStt, nEnd,
rDoc.IsClipBoard() ); break;
case RES_TXTATR_FLYCNT:
{ // finally, copy the frame format (with content)
pNew = new SwTextFlyCnt( aHolder, nStt ); if ( static_cast<const SwFormatFlyCnt &>(rAttr).GetTextFlyCnt() )
{ // if it has an existing attr then the format must be copied static_cast<SwTextFlyCnt *>(pNew)->CopyFlyFormat( rDoc );
}
} break; case RES_TXTATR_FTN:
pNew = new SwTextFootnote( aHolder, nStt ); // copy note's SeqNo if( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote() ) static_cast<SwTextFootnote*>(pNew)->SetSeqNo( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote()->GetSeqRefNo() ); break; case RES_TXTATR_REFMARK:
pNew = nStt == nEnd
? new SwTextRefMark( aHolder, nStt )
: new SwTextRefMark( aHolder, nStt, &nEnd ); break; case RES_TXTATR_TOXMARK:
{
SwTOXMark& rMark = static_cast<SwTOXMark&>(rNew);
// tdf#98868 if the SwTOXType is from a different document that the // target, re-register the TOXMark against a matching SwTOXType from // the target document instead const SwTOXType* pTOXType = rMark.GetTOXType(); if (pTOXType && &pTOXType->GetDoc() != &rDoc)
{
SwTOXType* pToxType = SwHistorySetTOXMark::GetSwTOXType(rDoc, pTOXType->GetType(),
pTOXType->GetTypeName());
rMark.RegisterToTOXType(*pToxType);
}
pNew = new SwTextTOXMark(aHolder, nStt, &nEnd); break;
} case RES_TXTATR_CJK_RUBY:
pNew = new SwTextRuby( aHolder, nStt, nEnd ); break; case RES_TXTATR_META: case RES_TXTATR_METAFIELD:
pNew = SwTextMeta::CreateTextMeta( rDoc.GetMetaFieldManager(), pTextNode,
aHolder,
nStt, nEnd, bIsCopy == CopyOrNewType::Copy ); break; case RES_TXTATR_LINEBREAK:
pNew = new SwTextLineBreak(aHolder, nStt); break; case RES_TXTATR_CONTENTCONTROL:
pNew = SwTextContentControl::CreateTextContentControl(
rDoc, pTextNode,
aHolder,
nStt, nEnd,
bIsCopy == CopyOrNewType::Copy); break; default:
assert(RES_TXTATR_AUTOFMT == rNew.Which());
pNew = new SwTextAttrEnd( aHolder, nStt, nEnd ); break;
}
// delete the text attribute and unregister its item at the pool void SwTextNode::DestroyAttr( SwTextAttr* pAttr )
{ if( !pAttr ) return;
// some things need to be done before deleting the formatting attribute
SwDoc& rDoc = GetDoc(); switch( pAttr->Which() )
{ case RES_TXTATR_FLYCNT:
{
SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat(); if( pFormat ) // set to 0 by Undo?
rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
} break;
case RES_CHRATR_HIDDEN:
SetCalcHiddenCharFlags(); break;
case RES_TXTATR_FTN: static_cast<SwTextFootnote*>(pAttr)->SetStartNode( nullptr ); static_cast<SwFormatFootnote&>(pAttr->GetAttr()).InvalidateFootnote(); break;
case RES_TXTATR_FIELD: case RES_TXTATR_ANNOTATION: case RES_TXTATR_INPUTFIELD: if( !rDoc.IsInDtor() )
{
SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pAttr));
SwFieldType* pFieldType = pAttr->GetFormatField().GetField()->GetTyp();
if (SwFieldIds::Dde != pFieldType->Which()
&& !pTextField->GetpTextNode())
{ break; // was not yet inserted
}
//JP 06-08-95: DDE-fields are an exception
assert(SwFieldIds::Dde == pFieldType->Which() || this == pTextField->GetpTextNode());
// certain fields must update the SwDoc's calculation flags
// Certain fields (like HiddenParaField) must trigger recalculation of visible flag if (GetDoc().FieldCanHideParaWeight(pFieldType->Which()))
SetCalcHiddenParaField();
switch( pFieldType->Which() )
{ case SwFieldIds::HiddenPara: case SwFieldIds::DbSetNumber: case SwFieldIds::GetExp: case SwFieldIds::Database: case SwFieldIds::SetExp: case SwFieldIds::HiddenText: case SwFieldIds::DbNumSet: case SwFieldIds::DbNextSet: if( !rDoc.getIDocumentFieldsAccess().IsNewFieldLst() && GetNodes().IsDocNodes() )
rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField); break; case SwFieldIds::Dde: if (GetNodes().IsDocNodes() && pTextField->GetpTextNode()) static_cast<SwDDEFieldType*>(pFieldType)->DecRefCnt(); break; case SwFieldIds::Postit:
{ const_cast<SwFormatField&>(pAttr->GetFormatField()).Broadcast(
SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED)); break;
} default: break;
}
} static_cast<SwFormatField&>(pAttr->GetAttr()).InvalidateField(); break;
case RES_TXTATR_TOXMARK: static_cast<SwTOXMark&>(pAttr->GetAttr()).InvalidateTOXMark(); break;
case RES_TXTATR_REFMARK: static_cast<SwFormatRefMark&>(pAttr->GetAttr()).InvalidateRefMark(); break;
case RES_TXTATR_META: case RES_TXTATR_METAFIELD:
{ auto pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(pTextMeta->GetAttr()) ); if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
{ if (SwDocShell* pDocSh = rDoc.GetDocShell())
{ static constexpr OUStringLiteral metaNS(u"urn:bails"); const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject();
rtl::Reference<SwXTextDocument> xModel = pDocSh->GetBaseModel();
SwRDFHelper::clearStatements(xModel, metaNS, xSubject);
}
}
static_txtattr_cast<SwTextMeta*>(pAttr)->ChgTextNode(nullptr);
} break; case RES_TXTATR_CONTENTCONTROL:
{
static_txtattr_cast<SwTextContentControl*>(pAttr)->ChgTextNode(nullptr); break;
}
default: break;
}
SwTextAttr::Destroy( pAttr );
}
SwTextAttr* SwTextNode::InsertItem(
SfxPoolItem& rAttr, const sal_Int32 nStart, const sal_Int32 nEnd, const SetAttrMode nMode )
{ // character attributes will be inserted as automatic styles:
assert( !isCHRATR(rAttr.Which()) && "AUTOSTYLES - " "SwTextNode::InsertItem should not be called with character attributes");
if ( pNew )
{ constbool bSuccess( InsertHint( pNew, nMode ) ); // N.B.: also check that the hint is actually in the hints array, // because hints of certain types may be merged after successful // insertion, and thus destroyed! if (!bSuccess || !m_pSwpHints->Contains( pNew ))
{ return nullptr;
}
}
return pNew;
}
// take ownership of pAttr; if insertion fails, delete pAttr bool SwTextNode::InsertHint( SwTextAttr * const pAttr, const SetAttrMode nMode )
{ bool bHiddenPara = false;
// translate from SetAttrMode to InsertMode (for hints with CH_TXTATR) const SwInsertFlags nInsertFlags =
(nMode & SetAttrMode::NOHINTEXPAND)
? SwInsertFlags::NOHINTEXPAND
: (nMode & SetAttrMode::FORCEHINTEXPAND)
? (SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND)
: SwInsertFlags::EMPTYEXPAND;
// need this after TryInsertHint, when pAttr may be deleted const sal_Int32 nStart( pAttr->GetStart() ); constbool bDummyChar( pAttr->HasDummyChar() ); if (bDummyChar)
{
SetAttrMode nInsMode = nMode; switch( pAttr->Which() )
{ case RES_TXTATR_FLYCNT:
{
SwTextFlyCnt *pFly = static_cast<SwTextFlyCnt *>(pAttr);
SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat(); if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
{ // Need to insert char first, because SetAnchor() reads // GetStart(). //JP 11.05.98: if the anchor is already set correctly, // fix it after inserting the char, so that clients don't // have to worry about it. const SwFormatAnchor* pAnchor = pFormat->GetItemIfSet( RES_ANCHOR, false );
SwContentIndex aIdx( this, pAttr->GetStart() ); const OUString c(GetCharOfTextAttr(*pAttr));
OUString const ins( InsertText(c, aIdx, nInsertFlags) ); if (ins.isEmpty())
{ // do not record deletion of Format!
::sw::UndoGuard const ug(
pFormat->GetDoc().GetIDocumentUndoRedo());
DestroyAttr(pAttr); returnfalse; // text node full :(
}
nInsMode |= SetAttrMode::NOTXTATRCHR;
// format pointer could have changed in SetAnchor, // when copying to other docs!
pFormat = pAttr->GetFlyCnt().GetFrameFormat();
SwDoc& rDoc = pFormat->GetDoc();
// OD 26.06.2003 - allow drawing objects in header/footer. // But don't allow control objects in header/footer if( RES_DRAWFRMFMT == pFormat->Which() &&
rDoc.IsInHeaderFooter( *pFormat->GetAnchor().GetAnchorNode() ) )
{ bool bCheckControlLayer = false;
pFormat->CallSwClientNotify(sw::CheckDrawFrameFormatLayerHint(&bCheckControlLayer)); if( bCheckControlLayer )
{ // This should not be allowed, prevent it here. // The dtor of the SwTextAttr does not delete the // char, so delete it explicitly here. if( SetAttrMode::NOTXTATRCHR & nInsMode )
{ // delete the char from the string
assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()]
|| CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]);
m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, u""); // Update SwContentIndexes
SwContentIndex aTmpIdx( this, pAttr->GetStart() );
Update(aTmpIdx, 1, UpdateMode::Negative);
} // do not record deletion of Format!
::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
DestroyAttr( pAttr ); returnfalse;
}
} break;
}
case RES_TXTATR_FTN :
{ // Footnotes: create text node and put it into Inserts-section
SwDoc& rDoc = GetDoc();
SwNodes &rNodes = rDoc.GetNodes();
// check that footnote is inserted into body or redline section bool bSplitFly = false; if (StartOfSectionIndex() < rNodes.GetEndOfAutotext().GetIndex()
&& StartOfSectionIndex() >= rNodes.GetEndOfInserts().GetIndex())
{ // This is a frame, header or footer. Check if it's a split frame.
SwFrameFormat* pFlyFormat = StartOfSectionNode()->GetFlyFormat();
bSplitFly = pFlyFormat && pFlyFormat->GetFlySplit().GetValue();
}
if (StartOfSectionIndex() < rNodes.GetEndOfAutotext().GetIndex() && !bSplitFly)
{ // This should not be allowed, prevent it here. // The dtor of the SwTextAttr does not delete the // char, so delete it explicitly here. if( SetAttrMode::NOTXTATRCHR & nInsMode )
{ // delete the char from the string
assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()]
|| CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]);
m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, u""); // Update SwContentIndexes
SwContentIndex aTmpIdx( this, pAttr->GetStart() );
Update(aTmpIdx, 1, UpdateMode::Negative);
}
DestroyAttr( pAttr ); returnfalse;
}
if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
{ // must insert first, to prevent identical indexes // that could later prevent insertion into SwDoc's // footnote array
SwContentIndex aNdIdx( this, pAttr->GetStart() ); const OUString c(GetCharOfTextAttr(*pAttr));
OUString const ins( InsertText(c, aNdIdx, nInsertFlags) ); if (ins.isEmpty())
{
DestroyAttr(pAttr); returnfalse; // text node full :(
}
nInsMode |= SetAttrMode::NOTXTATRCHR;
}
// insert into SwDoc's footnote index array
SwTextFootnote* pTextFootnote = nullptr; if( !bNewFootnote )
{ // moving an existing footnote (e.g. SplitNode) for( size_t n = 0; n < rDoc.GetFootnoteIdxs().size(); ++n ) if( pAttr == rDoc.GetFootnoteIdxs()[n] )
{ // assign new index by removing and re-inserting
pTextFootnote = rDoc.GetFootnoteIdxs()[n];
rDoc.GetFootnoteIdxs().erase( rDoc.GetFootnoteIdxs().begin() + n ); break;
} // if the Undo set the StartNode, the Index isn't // in the doc's array yet!
} if( !pTextFootnote )
pTextFootnote = static_cast<SwTextFootnote*>(pAttr);
// to update the numbers and for sorting, the Node must be set static_cast<SwTextFootnote*>(pAttr)->ChgTextNode( this );
// do not insert footnote in redline section into footnote array if (StartOfSectionIndex() > rNodes.GetEndOfRedlines().GetIndex() || bSplitFly)
{ constbool bSuccess = rDoc.GetFootnoteIdxs().insert(pTextFootnote).second;
OSL_ENSURE( bSuccess, "FootnoteIdx not inserted." );
}
rDoc.GetFootnoteIdxs().UpdateFootnote( *this ); static_cast<SwTextFootnote*>(pAttr)->SetSeqRefNo();
} break;
case RES_TXTATR_FIELD:
{ // trigger notification for relevant fields, like HiddenParaFields if (GetDoc().FieldCanHideParaWeight(
pAttr->GetFormatField().GetField()->GetTyp()->Which()))
{
bHiddenPara = true;
}
} break; case RES_TXTATR_LINEBREAK :
{ static_cast<SwTextLineBreak*>(pAttr)->SetTextNode(this);
} break;
} // CH_TXTATR_* are inserted for SwTextHints without EndIndex // If the caller is SwTextNode::Copy, the char has already been copied, // and SETATTR_NOTXTATRCHR prevents inserting it again here. if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
{
SwContentIndex aIdx( this, pAttr->GetStart() );
OUString const ins( InsertText(OUString(GetCharOfTextAttr(*pAttr)),
aIdx, nInsertFlags) ); if (ins.isEmpty())
{
DestroyAttr(pAttr); returnfalse; // text node full :(
}
// adjust end of hint to account for inserted CH_TXTATR const sal_Int32* pEnd(pAttr->GetEnd()); if (pEnd)
{
pAttr->SetEnd(*pEnd + 1);
}
if (pAttr->Which() == RES_TXTATR_CONTENTCONTROL)
{ // Content controls have a dummy character at their end as well.
SwContentIndex aEndIdx(this, *pAttr->GetEnd());
OUString aEnd
= InsertText(OUString(GetCharOfTextAttr(*pAttr)), aEndIdx, nInsertFlags); if (aEnd.isEmpty())
{
DestroyAttr(pAttr); returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.