/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef std::vector SwStartNodePointers; // function to determine the highest level in the given range static sal_uInt16 HighestLevel( SwNodes & rNodes, const SwNodeRange & rRange ); /** Constructor * * creates the base sections (PostIts, Inserts, AutoText, RedLines, Content) * * @param pDocument TODO: provide documentation */ SwNodes::SwNodes( SwDoc* pDocument ) : m_vIndices(nullptr), m_pMyDoc( pDocument ) { m_bInNodesDel = m_bInDelUpdOutline = false; assert(m_pMyDoc); sal_uLong nPos = 0; SwStartNode* pSttNd = new SwStartNode( *this, nPos++ ); m_pEndOfPostIts = new SwEndNode( *this, nPos++, *pSttNd ); SwStartNode* pTmp = new SwStartNode( *this, nPos++ ); m_pEndOfInserts = new SwEndNode( *this, nPos++, *pTmp ); pTmp = new SwStartNode( *this, nPos++ ); pTmp->m_pStartOfSection = pSttNd; m_pEndOfAutotext = new SwEndNode( *this, nPos++, *pTmp ); pTmp = new SwStartNode( *this, nPos++ ); pTmp->m_pStartOfSection = pSttNd; m_pEndOfRedlines = new SwEndNode( *this, nPos++, *pTmp ); pTmp = new SwStartNode( *this, nPos++ ); pTmp->m_pStartOfSection = pSttNd; m_pEndOfContent.reset(new SwEndNode( *this, nPos++, *pTmp )); m_pOutlineNodes.reset(new SwOutlineNodes); } /** Destructor * * Deletes all nodes whose pointer are in a dynamic array. This should be no * problem as nodes cannot be created outside this array and, thus, cannot be * part of multiple arrays. */ SwNodes::~SwNodes() { m_pOutlineNodes.reset(); { SwNodeIndex aNdIdx( *this ); while( true ) { SwNode *pNode = &aNdIdx.GetNode(); if( pNode == m_pEndOfContent.get() ) break; ++aNdIdx; delete pNode; } } // here, all SwNodeIndices must be unregistered m_pEndOfContent.reset(); } void SwNodes::ChgNode( SwNodeIndex const & rDelPos, sal_uLong nSz, SwNodeIndex& rInsPos, bool bNewFrames ) { // no need for frames in the UndoArea SwNodes& rNds = rInsPos.GetNodes(); const SwNode* pPrevInsNd = rNds[ rInsPos.GetIndex() -1 ]; // declare all fields as invalid, updating will happen // in the idle-handler of the doc if( GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty( true, &rDelPos.GetNode(), nSz ) && rNds.GetDoc() != GetDoc() ) rNds.GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); // NEVER include nodes from the RedLineArea sal_uLong nNd = rInsPos.GetIndex(); bool bInsOutlineIdx = !( rNds.GetEndOfRedlines().StartOfSectionNode()->GetIndex() < nNd && nNd < rNds.GetEndOfRedlines().GetIndex() ); if( &rNds == this ) // if in the same node array -> move { // Move order: from front to back, so that new entries are added at // first position, thus, deletion position stays the same const sal_uLong nDiff = rDelPos.GetIndex() < rInsPos.GetIndex() ? 0 : 1; for( sal_uLong n = rDelPos.GetIndex(); nSz; n += nDiff, --nSz ) { SwNodeIndex aDelIdx( *this, n ); SwNode& rNd = aDelIdx.GetNode(); // #i57920# - correction of refactoring done by cws swnumtree: // - state of the text node to , which // isn't correct here. if ( rNd.IsTextNode() ) { SwTextNode* pTextNode = rNd.GetTextNode(); pTextNode->RemoveFromList(); if (pTextNode->IsOutline()) { const SwNodePtr pSrch = &rNd; m_pOutlineNodes->erase( pSrch ); } } BigPtrArray::Move( aDelIdx.GetIndex(), rInsPos.GetIndex() ); if( rNd.IsTextNode() ) { SwTextNode& rTextNd = static_cast(rNd); rTextNd.AddToList(); if (bInsOutlineIdx && rTextNd.IsOutline()) { const SwNodePtr pSrch = &rNd; m_pOutlineNodes->insert( pSrch ); } rTextNd.InvalidateNumRule(); //FEATURE::CONDCOLL if( RES_CONDTXTFMTCOLL == rTextNd.GetTextColl()->Which() ) rTextNd.ChkCondColl(); //FEATURE::CONDCOLL } else if( rNd.IsContentNode() ) static_cast(rNd).InvalidateNumRule(); } } else { bool bSavePersData(GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(rNds)); bool bRestPersData(GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(*this)); SwDoc* pDestDoc = rNds.GetDoc() != GetDoc() ? rNds.GetDoc() : nullptr; OSL_ENSURE(!pDestDoc, "SwNodes::ChgNode(): " "the code to handle text fields here looks broken\n" "if the target is in a different document."); if( !bRestPersData && !bSavePersData && pDestDoc ) bSavePersData = bRestPersData = true; OUString sNumRule; for( sal_uLong n = 0; n < nSz; n++ ) { SwNode* pNd = &rDelPos.GetNode(); // NoTextNode keep their persistent data if( pNd->IsNoTextNode() ) { if( bSavePersData ) static_cast(pNd)->SavePersistentData(); } else if( pNd->IsTextNode() ) { SwTextNode* pTextNd = static_cast(pNd); // remove outline index from old nodes array if (pTextNd->IsOutline()) { m_pOutlineNodes->erase( pNd ); } // copy rules if needed if( pDestDoc ) { const SwNumRule* pNumRule = pTextNd->GetNumRule(); if( pNumRule && sNumRule != pNumRule->GetName() ) { sNumRule = pNumRule->GetName(); SwNumRule* pDestRule = pDestDoc->FindNumRulePtr( sNumRule ); if( pDestRule ) pDestRule->SetInvalidRule( true ); else pDestDoc->MakeNumRule( sNumRule, pNumRule ); } } else // if movement into the UndoNodes-array, update numbering pTextNd->InvalidateNumRule(); pTextNd->RemoveFromList(); } RemoveNode( rDelPos.GetIndex(), 1, false ); // move indices SwContentNode * pCNd = pNd->GetContentNode(); rNds.InsertNode( pNd, rInsPos ); if( pCNd ) { SwTextNode* pTextNd = pCNd->GetTextNode(); if( pTextNd ) { SwpHints * const pHts = pTextNd->GetpSwpHints(); // OutlineNodes set the new nodes in the array if (bInsOutlineIdx && pTextNd->IsOutline()) { rNds.m_pOutlineNodes->insert( pTextNd ); } pTextNd->AddToList(); // special treatment for fields if( pHts && pHts->Count() ) { bool const bToUndo = !pDestDoc && GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(rNds); for( size_t i = pHts->Count(); i; ) { SwTextAttr * const pAttr = pHts->Get( --i ); switch ( pAttr->Which() ) { case RES_TXTATR_FIELD: case RES_TXTATR_ANNOTATION: case RES_TXTATR_INPUTFIELD: { SwTextField* pTextField = static_txtattr_cast(pAttr); rNds.GetDoc()->getIDocumentFieldsAccess().InsDelFieldInFieldLst( !bToUndo, *pTextField ); const SwFieldType* pTyp = pTextField->GetFormatField().GetField()->GetTyp(); if ( SwFieldIds::Postit == pTyp->Which() ) { rNds.GetDoc()->GetDocShell()->Broadcast( SwFormatFieldHint( &pTextField->GetFormatField(), ( pTextField->GetFormatField().IsFieldInDoc() ? SwFormatFieldHintWhich::INSERTED : SwFormatFieldHintWhich::REMOVED ) ) ); } else if( SwFieldIds::Dde == pTyp->Which() ) { if( bToUndo ) const_cast(static_cast(pTyp))->DecRefCnt(); else const_cast(static_cast(pTyp))->IncRefCnt(); } static_cast(pAttr->GetAttr()) .InvalidateField(); } break; case RES_TXTATR_FTN: static_cast(pAttr->GetAttr()) .InvalidateFootnote(); break; case RES_TXTATR_TOXMARK: static_cast(pAttr->GetAttr()) .InvalidateTOXMark(); break; case RES_TXTATR_REFMARK: static_cast(pAttr->GetAttr()) .InvalidateRefMark(); break; case RES_TXTATR_META: case RES_TXTATR_METAFIELD: { SwTextMeta *const pTextMeta( static_txtattr_cast(pAttr)); // force removal of UNO object pTextMeta->ChgTextNode(nullptr); pTextMeta->ChgTextNode(pTextNd); } break; default: break; } } } //FEATURE::CONDCOLL if( RES_CONDTXTFMTCOLL == pTextNd->GetTextColl()->Which() ) pTextNd->ChkCondColl(); //FEATURE::CONDCOLL } else { // Moved into different Docs? Persist data again! if( pCNd->IsNoTextNode() && bRestPersData ) static_cast(pCNd)->RestorePersistentData(); } } } } // declare all fields as invalid, updating will happen // in the idle-handler of the doc GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); if( rNds.GetDoc() != GetDoc() ) rNds.GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); if( bNewFrames ) bNewFrames = &GetDoc()->GetNodes() == &rNds && GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); if( bNewFrames ) { // get the frames: SwNodeIndex aIdx( *pPrevInsNd, 1 ); SwNodeIndex aFrameNdIdx( aIdx ); SwNode* pFrameNd = rNds.FindPrvNxtFrameNode( aFrameNdIdx, rNds[ rInsPos.GetIndex() - 1 ] ); if( pFrameNd ) while( aIdx != rInsPos ) { SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); if( pCNd ) { if( pFrameNd->IsTableNode() ) static_cast(pFrameNd)->MakeFramesForAdjacentContentNode(aIdx); else if( pFrameNd->IsSectionNode() ) static_cast(pFrameNd)->MakeFramesForAdjacentContentNode(aIdx); else static_cast(pFrameNd)->MakeFramesForAdjacentContentNode(*pCNd); pFrameNd = pCNd; } ++aIdx; } } } // TODO: provide documentation /** move the node pointer * * Move the node pointer from "(inclusive) start position to (exclusive) end * position" to target position. * If the target is in front of the first or in the area between first and * last element to move, nothing happens. * If the area to move is empty or the end position is before the start * position, nothing happens. * * @param aRange range to move (excluding end node) * @param rNodes * @param aIndex * @param bNewFrames * @return */ bool SwNodes::MoveNodes( const SwNodeRange& aRange, SwNodes & rNodes, const SwNodeIndex& aIndex, bool bNewFrames ) { SwNode * pCurrentNode; if( aIndex == 0 || ( (pCurrentNode = &aIndex.GetNode())->GetStartNode() && !pCurrentNode->StartOfSectionIndex() )) return false; SwNodeRange aRg( aRange ); // skip "simple" start or end nodes while( SwNodeType::Start == (pCurrentNode = &aRg.aStart.GetNode())->GetNodeType() || ( pCurrentNode->IsEndNode() && !pCurrentNode->m_pStartOfSection->IsSectionNode() ) ) ++aRg.aStart; --aRg.aStart; // if aEnd-1 points to no ContentNode, search previous one --aRg.aEnd; while( ( (( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() && !pCurrentNode->IsSectionNode() ) || ( pCurrentNode->IsEndNode() && SwNodeType::Start == pCurrentNode->m_pStartOfSection->GetNodeType()) ) && aRg.aEnd > aRg.aStart ) --aRg.aEnd; // if in same array, check insertion position if( aRg.aStart >= aRg.aEnd ) return false; if( this == &rNodes ) { if( ( aIndex.GetIndex()-1 >= aRg.aStart.GetIndex() && aIndex.GetIndex()-1 < aRg.aEnd.GetIndex()) || ( aIndex.GetIndex()-1 == aRg.aEnd.GetIndex() ) ) return false; } sal_uLong nInsPos = 0; // counter for tmp array // array as a stack, storing all StartOfSelections SwStartNodePointers aSttNdStack; SwStartNodePointers::size_type nLevel = 0; // level counter // set start index SwNodeIndex aIdx( aIndex ); SwStartNode* pStartNode = aIdx.GetNode().m_pStartOfSection; aSttNdStack.insert( aSttNdStack.begin(), pStartNode ); SwNodeRange aOrigInsPos( aIdx, -1, aIdx ); // original insertion position // call DelFrames/MakeFrames for the upmost SectionNode int nSectNdCnt = 0; bool bSaveNewFrames = bNewFrames; // continue until everything has been moved while( aRg.aStart < aRg.aEnd ) { pCurrentNode = &aRg.aEnd.GetNode(); switch( pCurrentNode->GetNodeType() ) { case SwNodeType::End: { if( nInsPos ) // move everything until here { // delete and copy. CAUTION: all indices after // "aRg.aEnd+1" will be moved as well! SwNodeIndex aSwIndex( aRg.aEnd, 1 ); ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); aIdx -= nInsPos; nInsPos = 0; } SwStartNode* pSttNd = pCurrentNode->m_pStartOfSection; if( pSttNd->IsTableNode() ) { SwTableNode* pTableNd = static_cast(pSttNd); // move the whole table/range nInsPos = (aRg.aEnd.GetIndex() - pSttNd->GetIndex() )+1; aRg.aEnd -= nInsPos; // NEVER include nodes from the RedLineArea sal_uLong nNd = aIdx.GetIndex(); bool bInsOutlineIdx = !( rNodes.GetEndOfRedlines(). StartOfSectionNode()->GetIndex() < nNd && nNd < rNodes.GetEndOfRedlines().GetIndex() ); if( bNewFrames ) // delete all frames pTableNd->DelFrames(nullptr); if( &rNodes == this ) // move into self? { // move all Start/End/ContentNodes // ContentNodes: delete also the frames! pTableNd->m_pStartOfSection = aIdx.GetNode().m_pStartOfSection; for( sal_uLong n = 0; n < nInsPos; ++n ) { SwNodeIndex aMvIdx( aRg.aEnd, 1 ); SwContentNode* pCNd = nullptr; SwNode* pTmpNd = &aMvIdx.GetNode(); if( pTmpNd->IsContentNode() ) { pCNd = static_cast(pTmpNd); if( pTmpNd->IsTextNode() ) static_cast(pTmpNd)->RemoveFromList(); // remove outline index from old nodes array if (pCNd->IsTextNode() && pCNd->GetTextNode()->IsOutline()) { m_pOutlineNodes->erase( pCNd ); } else pCNd = nullptr; } BigPtrArray::Move( aMvIdx.GetIndex(), aIdx.GetIndex() ); if( bInsOutlineIdx && pCNd ) m_pOutlineNodes->insert( pCNd ); if( pTmpNd->IsTextNode() ) static_cast(pTmpNd)->AddToList(); } } else { // get StartNode // Even aIdx points to a startnode, we need the startnode // of the environment of aIdx (#i80941) SwStartNode* pSttNode = aIdx.GetNode().m_pStartOfSection; // get all boxes with content because their indices // pointing to the StartNodes need to be reset // (copying the array and deleting all found ones eases // searching) SwNodeIndex aMvIdx( aRg.aEnd, 1 ); for( sal_uLong n = 0; n < nInsPos; ++n ) { SwNode* pNd = &aMvIdx.GetNode(); const bool bOutlNd = pNd->IsTextNode() && pNd->GetTextNode()->IsOutline(); // delete outline indices from old node array if( bOutlNd ) m_pOutlineNodes->erase( pNd ); RemoveNode( aMvIdx.GetIndex(), 1, false ); pNd->m_pStartOfSection = pSttNode; rNodes.InsertNode( pNd, aIdx ); // set correct indices in Start/EndNodes if( bInsOutlineIdx && bOutlNd ) // and put them into the new node array rNodes.m_pOutlineNodes->insert( pNd ); else if( pNd->IsStartNode() ) pSttNode = static_cast(pNd); else if( pNd->IsEndNode() ) { pSttNode->m_pEndOfSection = static_cast(pNd); if( pSttNode->IsSectionNode() ) static_cast(pSttNode)->NodesArrChgd(); pSttNode = pSttNode->m_pStartOfSection; } } if( dynamic_cast(&pTableNd->GetTable()) != nullptr ) { SwDDEFieldType* pTyp = static_cast(pTableNd-> GetTable()).GetDDEFieldType(); if( pTyp ) { if( rNodes.IsDocNodes() ) pTyp->IncRefCnt(); else pTyp->DecRefCnt(); } } if (GetDoc()->GetIDocumentUndoRedo().IsUndoNodes( rNodes)) { SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); pTableFormat->GetNotifier().Broadcast(SfxHint(SfxHintId::Dying)); } } if( bNewFrames ) { SwNodeIndex aTmp( aIdx ); pTableNd->MakeOwnFrames(&aTmp); } aIdx -= nInsPos; nInsPos = 0; } else if( pSttNd->GetIndex() < aRg.aStart.GetIndex() ) { // SectionNode: not the whole section will be moved, thus, // move only the ContentNodes // StartNode: create a new section at the given position do { // middle check loop if( !pSttNd->IsSectionNode() ) { // create StartNode and EndNode at InsertPos SwStartNode* pTmp = new SwStartNode( aIdx, SwNodeType::Start, /*?? NodeType ??*/ SwNormalStartNode ); nLevel++; // put the index to StartNode on the stack aSttNdStack.insert( aSttNdStack.begin() + nLevel, pTmp ); // create EndNode new SwEndNode( aIdx, *pTmp ); } else if (GetDoc()->GetIDocumentUndoRedo().IsUndoNodes( rNodes)) { // use placeholder in UndoNodes array new SwPlaceholderNode(aIdx); } else { // JP 18.5.2001 (Bug 70454) creating new section? --aRg.aEnd; break; } --aRg.aEnd; --aIdx; } while( false ); } else { // move StartNode and EndNode in total // if Start is exactly the Start of the area, // then the Node needs to be re-visited if( &aRg.aStart.GetNode() == pSttNd ) --aRg.aStart; SwSectionNode* pSctNd = pSttNd->GetSectionNode(); if( bNewFrames && pSctNd ) { // tdf#135056 skip over code in DelFrames() that moves // SwNodeIndex around because in case of nested // sections, m_pStartOfSection will point between // undo nodes-array and doc nodes-array pSctNd->DelFrames(nullptr, true); } RemoveNode( aRg.aEnd.GetIndex(), 1, false ); // delete EndNode sal_uLong nSttPos = pSttNd->GetIndex(); // this StartNode will be removed later SwStartNode* pTmpSttNd = new SwStartNode( *this, nSttPos+1 ); pTmpSttNd->m_pStartOfSection = pSttNd->m_pStartOfSection; RemoveNode( nSttPos, 1, false ); // delete SttNode pSttNd->m_pStartOfSection = aIdx.GetNode().m_pStartOfSection; rNodes.InsertNode( pSttNd, aIdx ); rNodes.InsertNode( pCurrentNode, aIdx ); --aIdx; pSttNd->m_pEndOfSection = static_cast(pCurrentNode); --aRg.aEnd; nLevel++; // put the index pointing to the StartNode onto the stack aSttNdStack.insert( aSttNdStack.begin() + nLevel, pSttNd ); // reset remaining indices if SectionNode if( pSctNd ) { pSctNd->NodesArrChgd(); ++nSectNdCnt; // tdf#132326 do not let frames survive in undo nodes if (!GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(rNodes)) { bNewFrames = false; } } } } break; case SwNodeType::Section: if( !nLevel && GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(rNodes)) { // here, a SectionDummyNode needs to be inserted at the current position if( nInsPos ) // move everything until here { // delete and copy. CAUTION: all indices after // "aRg.aEnd+1" will be moved as well! SwNodeIndex aSwIndex( aRg.aEnd, 1 ); ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); aIdx -= nInsPos; nInsPos = 0; } new SwPlaceholderNode(aIdx); --aRg.aEnd; --aIdx; break; } [[fallthrough]]; case SwNodeType::Table: case SwNodeType::Start: { // empty section -> nothing to do // and only if it's a top level section if( !nInsPos && !nLevel ) { --aRg.aEnd; break; } if( !nLevel ) // level is decreasing { // create decrease SwNodeIndex aTmpSIdx( aOrigInsPos.aStart, 1 ); SwStartNode* pTmpStt = new SwStartNode( aTmpSIdx, SwNodeType::Start, static_cast(pCurrentNode)->GetStartNodeType() ); --aTmpSIdx; SwNodeIndex aTmpEIdx( aOrigInsPos.aEnd ); new SwEndNode( aTmpEIdx, *pTmpStt ); --aTmpEIdx; ++aTmpSIdx; // set correct StartOfSection ++aRg.aEnd; { SwNodeIndex aCntIdx( aRg.aEnd ); for( sal_uLong n = 0; n < nInsPos; n++, ++aCntIdx) aCntIdx.GetNode().m_pStartOfSection = pTmpStt; } // also set correct StartNode for all decreased nodes while( aTmpSIdx < aTmpEIdx ) if( nullptr != (( pCurrentNode = &aTmpEIdx.GetNode())->GetEndNode()) ) aTmpEIdx = pCurrentNode->StartOfSectionIndex(); else { pCurrentNode->m_pStartOfSection = pTmpStt; --aTmpEIdx; } --aIdx; // after the inserted StartNode --aRg.aEnd; // before StartNode // copy array. CAUTION: all indices after // "aRg.aEnd+1" will be moved as well! SwNodeIndex aSwIndex( aRg.aEnd, 1 ); ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); aIdx -= nInsPos+1; nInsPos = 0; } else // all nodes between StartNode and EndNode were moved { OSL_ENSURE( pCurrentNode == aSttNdStack[nLevel] || ( pCurrentNode->IsStartNode() && aSttNdStack[nLevel]->IsSectionNode()), "wrong StartNode" ); SwNodeIndex aSwIndex( aRg.aEnd, 1 ); ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); aIdx -= nInsPos+1; // before inserted StartNode nInsPos = 0; // remove pointer from node array RemoveNode( aRg.aEnd.GetIndex(), 1, true ); --aRg.aEnd; SwSectionNode* pSectNd = aSttNdStack[ nLevel ]->GetSectionNode(); if( pSectNd && !--nSectNdCnt ) { SwNodeIndex aTmp( *pSectNd ); pSectNd->MakeOwnFrames(&aTmp); bNewFrames = bSaveNewFrames; } aSttNdStack.erase( aSttNdStack.begin() + nLevel ); // remove from stack nLevel--; } // delete all resulting empty start/end node pairs SwNode* pTmpNode = (*this)[ aRg.aEnd.GetIndex()+1 ]->GetEndNode(); if( pTmpNode && SwNodeType::Start == (pCurrentNode = &aRg.aEnd.GetNode()) ->GetNodeType() && pCurrentNode->StartOfSectionIndex() && pTmpNode->StartOfSectionNode() == pCurrentNode ) { DelNodes( aRg.aEnd, 2 ); --aRg.aEnd; } } break; case SwNodeType::Text: //Add special function to text node. { if( bNewFrames && pCurrentNode->GetContentNode() ) static_cast(pCurrentNode)->DelFrames(nullptr); pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; nInsPos++; --aRg.aEnd; } break; case SwNodeType::Grf: case SwNodeType::Ole: { if( bNewFrames && pCurrentNode->GetContentNode() ) static_cast(pCurrentNode)->DelFrames(nullptr); pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; nInsPos++; --aRg.aEnd; } break; case SwNodeType::PlaceHolder: if (GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(*this)) { if( &rNodes == this ) // inside UndoNodesArray { // move everything pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; nInsPos++; } else // move into "normal" node array { // than a SectionNode (start/end) is needed at the current // InsPos; if so skip it, otherwise ignore current node if( nInsPos ) // move everything until here { // delete and copy. CAUTION: all indices after // "aRg.aEnd+1" will be moved as well! SwNodeIndex aSwIndex( aRg.aEnd, 1 ); ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); aIdx -= nInsPos; nInsPos = 0; } SwNode* pTmpNd = &aIdx.GetNode(); if( pTmpNd->IsSectionNode() || pTmpNd->StartOfSectionNode()->IsSectionNode() ) --aIdx; // skip } } else { assert(!"How can this node be in the node array?"); } --aRg.aEnd; break; default: assert(!"Unknown node type"); break; } } if( nInsPos ) // copy remaining rest { // rest should be ok SwNodeIndex aSwIndex( aRg.aEnd, 1 ); ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); } ++aRg.aEnd; // again, exclusive end // delete all resulting empty start/end node pairs if( ( pCurrentNode = &aRg.aStart.GetNode())->GetStartNode() && pCurrentNode->StartOfSectionIndex() && aRg.aEnd.GetNode().GetEndNode() ) DelNodes( aRg.aStart, 2 ); // initialize numbering update ++aOrigInsPos.aStart; // Moved in same node array? Then call update top down! if( this == &rNodes && aRg.aEnd.GetIndex() >= aOrigInsPos.aStart.GetIndex() ) { UpdateOutlineIdx( aOrigInsPos.aStart.GetNode() ); UpdateOutlineIdx( aRg.aEnd.GetNode() ); } else { UpdateOutlineIdx( aRg.aEnd.GetNode() ); rNodes.UpdateOutlineIdx( aOrigInsPos.aStart.GetNode() ); } return true; } /** create a start/end section pair * * Other nodes might be in between. * * After this method call, the start node of pRange will be pointing to the * first node after the start section node and the end node will be the index * of the end section node. If this method is called multiple times with the * same input, multiple sections containing the previous ones will be created * (no content nodes between start or end node). * * @note Start and end node of the range must be on the same level but MUST * NOT be on the top level. * * @param [IN,OUT] pRange the range (excl. end) * @param eSttNdTyp type of the start node */ void SwNodes::SectionDown(SwNodeRange *pRange, SwStartNodeType eSttNdTyp ) { if( pRange->aStart >= pRange->aEnd || pRange->aEnd >= Count() || !CheckNodesRange( pRange->aStart, pRange->aEnd )) return; // If the beginning of a range is before or at a start node position, so // delete it, otherwise empty S/E or E/S nodes would be created. // For other nodes, create a new start node. SwNode * pCurrentNode = &pRange->aStart.GetNode(); SwNodeIndex aTmpIdx( *pCurrentNode->StartOfSectionNode() ); if( pCurrentNode->GetEndNode() ) DelNodes( pRange->aStart ); // prevent empty section else { // insert a new StartNode SwNode* pSttNd = new SwStartNode( pRange->aStart, SwNodeType::Start, eSttNdTyp ); pRange->aStart = *pSttNd; aTmpIdx = pRange->aStart; } // If the end of a range is before or at a StartNode, so delete it, // otherwise empty S/E or E/S nodes would be created. // For other nodes, insert a new end node. --pRange->aEnd; if( pRange->aEnd.GetNode().GetStartNode() ) DelNodes( pRange->aEnd ); else { ++pRange->aEnd; // insert a new EndNode new SwEndNode( pRange->aEnd, *pRange->aStart.GetNode().GetStartNode() ); } --pRange->aEnd; SectionUpDown( aTmpIdx, pRange->aEnd ); } /** increase level of the given range * * The range contained in pRange will be lifted to the next higher level. * This is done by adding an end node at pRange.start and a start node at * pRange.end. Furthermore all indices for this range will be updated. * * After this method call, the start node of pRange will be pointing to the * first node inside the lifted range and the end node will be pointing to the * last position inside the lifted range. * * @param [IN,OUT] pRange the range of nodes where the level should be increased */ void SwNodes::SectionUp(SwNodeRange *pRange) { if( pRange->aStart >= pRange->aEnd || pRange->aEnd >= Count() || !CheckNodesRange( pRange->aStart, pRange->aEnd ) || ( HighestLevel( *this, *pRange ) <= 1 )) return; // If the beginning of a range is before or at a start node position, so // delete it, otherwise empty S/E or E/S nodes would be created. // For other nodes, create a new start node. SwNode * pCurrentNode = &pRange->aStart.GetNode(); SwNodeIndex aIdx( *pCurrentNode->StartOfSectionNode() ); if( pCurrentNode->IsStartNode() ) // is StartNode itself { SwEndNode* pEndNd = pRange->aEnd.GetNode().GetEndNode(); if (pEndNd && pCurrentNode == pEndNd->m_pStartOfSection) { // there was a pairwise reset, adjust only those in the range SwStartNode* pTmpSttNd = pCurrentNode->m_pStartOfSection; RemoveNode( pRange->aStart.GetIndex(), 1, true ); RemoveNode( pRange->aEnd.GetIndex(), 1, true ); SwNodeIndex aTmpIdx( pRange->aStart ); while( aTmpIdx < pRange->aEnd ) { pCurrentNode = &aTmpIdx.GetNode(); pCurrentNode->m_pStartOfSection = pTmpSttNd; if( pCurrentNode->IsStartNode() ) aTmpIdx = pCurrentNode->EndOfSectionIndex() + 1; else ++aTmpIdx; } return ; } DelNodes( pRange->aStart ); } else if( aIdx == pRange->aStart.GetIndex()-1 ) // before StartNode DelNodes( aIdx ); else new SwEndNode( pRange->aStart, *aIdx.GetNode().GetStartNode() ); // If the end of a range is before or at a StartNode, so delete it, // otherwise empty S/E or E/S nodes would be created. // For other nodes, insert a new end node. SwNodeIndex aTmpIdx( pRange->aEnd ); if( pRange->aEnd.GetNode().IsEndNode() ) DelNodes( pRange->aEnd ); else { new SwStartNode( pRange->aEnd ); /*?? which NodeType ??*/ aTmpIdx = *pRange->aEnd.GetNode().EndOfSectionNode(); --pRange->aEnd; } SectionUpDown( aIdx, aTmpIdx ); } /** correct indices after movement * * Update all indices after movement so that the levels are consistent again. * * @param aStart index of the start node * @param aEnd index of the end point * * @see SwNodes::SectionUp * @see SwNodes::SectionDown */ void SwNodes::SectionUpDown( const SwNodeIndex & aStart, const SwNodeIndex & aEnd ) { SwNodeIndex aTmpIdx( aStart, +1 ); // array forms a stack, holding all StartOfSelections SwStartNodePointers aSttNdStack; SwStartNode* pTmp = aStart.GetNode().GetStartNode(); aSttNdStack.push_back( pTmp ); // loop until the first start node that needs to be change was found // (the indices are updated from the end node backwards to the start) for( ;; ++aTmpIdx ) { SwNode * pCurrentNode = &aTmpIdx.GetNode(); pCurrentNode->m_pStartOfSection = aSttNdStack[ aSttNdStack.size()-1 ]; if( pCurrentNode->GetStartNode() ) { pTmp = static_cast(pCurrentNode); aSttNdStack.push_back( pTmp ); } else if( pCurrentNode->GetEndNode() ) { SwStartNode* pSttNd = aSttNdStack[ aSttNdStack.size() - 1 ]; pSttNd->m_pEndOfSection = static_cast(pCurrentNode); aSttNdStack.pop_back(); if( !aSttNdStack.empty() ) continue; // still enough EndNodes on the stack else if( aTmpIdx < aEnd ) // too many StartNodes // if the end is not reached, yet, get the start of the section above { aSttNdStack.insert( aSttNdStack.begin(), pSttNd->m_pStartOfSection ); } else // finished, as soon as out of the range break; } } } /** delete nodes * * This is a specific implementation of a delete function for a variable array. * It is necessary as there might be inconsistencies after deleting start or * end nodes. This method can clean those up. * * @param rIndex position to delete at (unchanged afterwards) * @param nNodes number of nodes to delete (default: 1) */ void SwNodes::Delete(const SwNodeIndex &rIndex, sal_uLong nNodes) { int nLevel = 0; // level counter SwNode * pCurrentNode; sal_uLong nCnt = Count() - rIndex.GetIndex() - 1; if( nCnt > nNodes ) nCnt = nNodes; if( nCnt == 0 ) // no count -> return return; SwNodeRange aRg( rIndex, 0, rIndex, nCnt-1 ); // check if [rIndex..rIndex + nCnt] is larger than the range if( ( !aRg.aStart.GetNode().StartOfSectionIndex() && !aRg.aStart.GetIndex() ) || ! CheckNodesRange( aRg.aStart, aRg.aEnd ) ) return; // if aEnd is not on a ContentNode, search the previous one while( ( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() || ( pCurrentNode->GetEndNode() && !pCurrentNode->m_pStartOfSection->IsTableNode() )) --aRg.aEnd; nCnt = 0; //TODO: check/improve comment // increase start so that we are able to use "<" (using "<=" might cause // problems if aEnd == aStart and aEnd is deleted, so aEnd <= aStart) --aRg.aStart; bool bSaveInNodesDel = m_bInNodesDel; m_bInNodesDel = true; bool bUpdateOutline = false; // loop until everything is deleted while( aRg.aStart < aRg.aEnd ) { pCurrentNode = &aRg.aEnd.GetNode(); if( pCurrentNode->GetEndNode() ) { // delete the whole section? if( pCurrentNode->StartOfSectionIndex() > aRg.aStart.GetIndex() ) { SwTableNode* pTableNd = pCurrentNode->m_pStartOfSection->GetTableNode(); if( pTableNd ) pTableNd->DelFrames(); SwNode *pNd, *pChkNd = pCurrentNode->m_pStartOfSection; SwOutlineNodes::size_type nIdxPos; do { pNd = &aRg.aEnd.GetNode(); if( pNd->IsTextNode() ) { SwTextNode *const pTextNode(pNd->GetTextNode()); if (pTextNode->IsOutline() && m_pOutlineNodes->Seek_Entry( pNd, &nIdxPos )) { // remove outline indices m_pOutlineNodes->erase(nIdxPos); bUpdateOutline = true; } pTextNode->InvalidateNumRule(); } else if( pNd->IsEndNode() && pNd->m_pStartOfSection->IsTableNode() ) static_cast(pNd->m_pStartOfSection)->DelFrames(); --aRg.aEnd; nCnt++; } while( pNd != pChkNd ); } else { RemoveNode( aRg.aEnd.GetIndex()+1, nCnt, true ); // delete nCnt = 0; --aRg.aEnd; // before the EndNode nLevel++; } } else if( pCurrentNode->GetStartNode() ) // found StartNode { if( nLevel == 0 ) // decrease one level { if( nCnt ) { // now delete array ++aRg.aEnd; RemoveNode( aRg.aEnd.GetIndex(), nCnt, true ); nCnt = 0; } } else // remove all nodes between start and end node (incl. both) { RemoveNode( aRg.aEnd.GetIndex(), nCnt + 2, true ); // delete array nCnt = 0; nLevel--; } // after deletion, aEnd might point to an EndNode... // delete all empty start/end node pairs SwNode* pTmpNode = aRg.aEnd.GetNode().GetEndNode(); --aRg.aEnd; while( pTmpNode && ( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() && pCurrentNode->StartOfSectionIndex() ) { // remove end and start node DelNodes( aRg.aEnd, 2 ); pTmpNode = aRg.aEnd.GetNode().GetEndNode(); --aRg.aEnd; } } else // "normal" node, so insert into TmpArray { SwTextNode* pTextNd = pCurrentNode->GetTextNode(); if( pTextNd ) { if( pTextNd->IsOutline()) { // delete outline indices m_pOutlineNodes->erase( pTextNd ); bUpdateOutline = true; } pTextNd->InvalidateNumRule(); } else if( pCurrentNode->IsContentNode() ) static_cast(pCurrentNode)->InvalidateNumRule(); --aRg.aEnd; nCnt++; } } ++aRg.aEnd; if( nCnt != 0 ) RemoveNode( aRg.aEnd.GetIndex(), nCnt, true ); // delete the rest // delete all empty start/end node pairs while( aRg.aEnd.GetNode().GetEndNode() && ( pCurrentNode = &aRg.aStart.GetNode())->GetStartNode() && pCurrentNode->StartOfSectionIndex() ) // but none of the holy 5. (???) { DelNodes( aRg.aStart, 2 ); // delete start and end node --aRg.aStart; } m_bInNodesDel = bSaveInNodesDel; if( !m_bInNodesDel ) { // update numbering if( bUpdateOutline || m_bInDelUpdOutline ) { UpdateOutlineIdx( aRg.aEnd.GetNode() ); m_bInDelUpdOutline = false; } } else { if( bUpdateOutline ) m_bInDelUpdOutline = true; } } /** get section level at the given position * * @note The first node in an array should always be a start node. * Because of this, there is a special treatment here based on the * assumption that this is true in this context as well. * * @param rIdx position of the node * @return section level at the given position */ sal_uInt16 SwNodes::GetSectionLevel(const SwNodeIndex &rIdx) { // special treatment for 1st Node if(rIdx == 0) return 1; // no recursion! This calls a SwNode::GetSectionLevel (missing "s") return rIdx.GetNode().GetSectionLevel(); } void SwNodes::GoStartOfSection(SwNodeIndex *pIdx) { // after the next start node SwNodeIndex aTmp( *pIdx->GetNode().StartOfSectionNode(), +1 ); // If index points to no ContentNode, then go to one. // If there is no further available, do not change the index' position! while( !aTmp.GetNode().IsContentNode() ) { // go from this StartNode (can only be one) to its end if( *pIdx <= aTmp ) return; // ERROR: already after the section aTmp = aTmp.GetNode().EndOfSectionIndex()+1; if( *pIdx <= aTmp ) return; // ERROR: already after the section } (*pIdx) = aTmp; // is on a ContentNode } void SwNodes::GoEndOfSection(SwNodeIndex *pIdx) { if( !pIdx->GetNode().IsEndNode() ) (*pIdx) = *pIdx->GetNode().EndOfSectionNode(); } SwContentNode* SwNodes::GoNext(SwNodeIndex *pIdx) const { if( pIdx->GetIndex() >= Count() - 1 ) return nullptr; SwNodeIndex aTmp(*pIdx, +1); SwNode* pNd = nullptr; while( aTmp < Count()-1 && !( pNd = &aTmp.GetNode())->IsContentNode() ) ++aTmp; if( aTmp == Count()-1 ) pNd = nullptr; else (*pIdx) = aTmp; return static_cast(pNd); } SwContentNode* SwNodes::GoPrevious(SwNodeIndex *pIdx) { if( !pIdx->GetIndex() ) return nullptr; SwNodeIndex aTmp( *pIdx, -1 ); SwNode* pNd = nullptr; while( aTmp.GetIndex() && !( pNd = &aTmp.GetNode())->IsContentNode() ) --aTmp; if( !aTmp.GetIndex() ) pNd = nullptr; else (*pIdx) = aTmp; return static_cast(pNd); } static bool TstIdx( sal_uLong nSttIdx, sal_uLong nEndIdx, sal_uLong nStt, sal_uLong nEnd ) { return nStt < nSttIdx && nEnd >= nSttIdx && nStt < nEndIdx && nEnd >= nEndIdx; } /** Check if the given range is inside the defined ranges * * The defined ranges are Content, AutoText, PostIts, Inserts, and Redlines. * * @param rStt start index of the range * @param rEnd end index of the range * @return if valid range */ bool SwNodes::CheckNodesRange( const SwNodeIndex& rStt, const SwNodeIndex& rEnd ) const { sal_uLong nStt = rStt.GetIndex(), nEnd = rEnd.GetIndex(); if( TstIdx( nStt, nEnd, m_pEndOfContent->StartOfSectionIndex(), m_pEndOfContent->GetIndex() )) return true; if( TstIdx( nStt, nEnd, m_pEndOfAutotext->StartOfSectionIndex(), m_pEndOfAutotext->GetIndex() )) return true; if( TstIdx( nStt, nEnd, m_pEndOfPostIts->StartOfSectionIndex(), m_pEndOfPostIts->GetIndex() )) return true; if( TstIdx( nStt, nEnd, m_pEndOfInserts->StartOfSectionIndex(), m_pEndOfInserts->GetIndex() )) return true; if( TstIdx( nStt, nEnd, m_pEndOfRedlines->StartOfSectionIndex(), m_pEndOfRedlines->GetIndex() )) return true; return false; // is somewhere in the middle, ERROR } /** Delete a number of nodes * * @param rStart starting position in this nodes array * @param nCnt number of nodes to delete */ void SwNodes::DelNodes( const SwNodeIndex & rStart, sal_uLong nCnt ) { sal_uLong nSttIdx = rStart.GetIndex(); if( !nSttIdx && nCnt == GetEndOfContent().GetIndex()+1 ) { // The whole nodes array will be destroyed, you're in the Doc's DTOR! // The initial start/end nodes should be only destroyed in the SwNodes' DTOR! SwNode* aEndNdArr[] = { m_pEndOfContent.get(), m_pEndOfPostIts, m_pEndOfInserts, m_pEndOfAutotext, m_pEndOfRedlines, nullptr }; SwNode** ppEndNdArr = aEndNdArr; while( *ppEndNdArr ) { nSttIdx = (*ppEndNdArr)->StartOfSectionIndex() + 1; sal_uLong nEndIdx = (*ppEndNdArr)->GetIndex(); if( nSttIdx != nEndIdx ) RemoveNode( nSttIdx, nEndIdx - nSttIdx, true ); ++ppEndNdArr; } } else { int bUpdateNum = 0; for( sal_uLong n = nSttIdx, nEnd = nSttIdx + nCnt; n < nEnd; ++n ) { SwNode* pNd = (*this)[ n ]; if (pNd->IsTextNode() && pNd->GetTextNode()->IsOutline()) { // remove the outline indices SwOutlineNodes::size_type nIdxPos; if( m_pOutlineNodes->Seek_Entry( pNd, &nIdxPos )) { m_pOutlineNodes->erase(nIdxPos); bUpdateNum = 1; } } if( pNd->IsContentNode() ) { static_cast(pNd)->InvalidateNumRule(); static_cast(pNd)->DelFrames(nullptr); } } RemoveNode( nSttIdx, nCnt, true ); // update numbering if( bUpdateNum ) UpdateOutlineIdx( rStart.GetNode() ); } } struct HighLevel { sal_uInt16 nLevel, nTop; explicit HighLevel( sal_uInt16 nLv ) : nLevel( nLv ), nTop( nLv ) {} }; static bool lcl_HighestLevel( const SwNodePtr& rpNode, void * pPara ) { HighLevel * pHL = static_cast(pPara); if( rpNode->GetStartNode() ) pHL->nLevel++; else if( rpNode->GetEndNode() ) pHL->nLevel--; if( pHL->nTop > pHL->nLevel ) pHL->nTop = pHL->nLevel; return true; } /** Calculate the highest level in a range * * @param rNodes the nodes array * @param rRange the range to inspect * @return the highest level */ sal_uInt16 HighestLevel( SwNodes & rNodes, const SwNodeRange & rRange ) { HighLevel aPara( SwNodes::GetSectionLevel( rRange.aStart )); rNodes.ForEach( rRange.aStart, rRange.aEnd, lcl_HighestLevel, &aPara ); return aPara.nTop; } /** move a range * * @param rPam the range to move * @param rPos to destination position in the given nodes array * @param rNodes the node array to move the range into */ void SwNodes::MoveRange( SwPaM & rPam, SwPosition & rPos, SwNodes& rNodes ) { SwPosition * const pStt = rPam.Start(); SwPosition * const pEnd = rPam.End(); if( !rPam.HasMark() || *pStt >= *pEnd ) return; if( this == &rNodes && *pStt <= rPos && rPos < *pEnd ) return; SwNodeIndex aEndIdx( pEnd->nNode ); SwNodeIndex aSttIdx( pStt->nNode ); SwTextNode *const pSrcNd = aSttIdx.GetNode().GetTextNode(); SwTextNode * pDestNd = rPos.nNode.GetNode().GetTextNode(); bool bSplitDestNd = true; bool bCopyCollFormat = pDestNd && pDestNd->GetText().isEmpty(); if( pSrcNd ) { // if the first node is a TextNode, then there must // be also a TextNode in the NodesArray to store the content if( !pDestNd ) { pDestNd = rNodes.MakeTextNode( rPos.nNode, pSrcNd->GetTextColl() ); --rPos.nNode; rPos.nContent.Assign( pDestNd, 0 ); bCopyCollFormat = true; } bSplitDestNd = pDestNd->Len() > rPos.nContent.GetIndex() || pEnd->nNode.GetNode().IsTextNode(); // move the content into the new node bool bOneNd = pStt->nNode == pEnd->nNode; const sal_Int32 nLen = ( bOneNd ? std::min(pEnd->nContent.GetIndex(), pSrcNd->Len()) : pSrcNd->Len() ) - pStt->nContent.GetIndex(); if( !pEnd->nNode.GetNode().IsContentNode() ) { bOneNd = true; sal_uLong nSttNdIdx = pStt->nNode.GetIndex() + 1; const sal_uLong nEndNdIdx = pEnd->nNode.GetIndex(); for( ; nSttNdIdx < nEndNdIdx; ++nSttNdIdx ) { if( (*this)[ nSttNdIdx ]->IsContentNode() ) { bOneNd = false; break; } } } // templates must be copied/set after a split if( !bOneNd && bSplitDestNd ) { if( !rPos.nContent.GetIndex() ) { bCopyCollFormat = true; } if( rNodes.IsDocNodes() ) { SwDoc* const pInsDoc = pDestNd->GetDoc(); ::sw::UndoGuard const ug(pInsDoc->GetIDocumentUndoRedo()); pInsDoc->getIDocumentContentOperations().SplitNode( rPos, false ); } else { pDestNd->SplitContentNode(rPos, nullptr); } if( rPos.nNode == aEndIdx ) { --aEndIdx; } bSplitDestNd = true; pDestNd = rNodes[ rPos.nNode.GetIndex() - 1 ]->GetTextNode(); if( nLen ) { pSrcNd->CutText( pDestNd, SwIndex( pDestNd, pDestNd->Len()), pStt->nContent, nLen ); } } else if ( nLen ) { pSrcNd->CutText( pDestNd, rPos.nContent, pStt->nContent, nLen ); } if( bCopyCollFormat ) { SwDoc* const pInsDoc = pDestNd->GetDoc(); ::sw::UndoGuard const undoGuard(pInsDoc->GetIDocumentUndoRedo()); pSrcNd->CopyCollFormat( *pDestNd ); bCopyCollFormat = false; } if( bOneNd ) { // Correct the PaM, because it might have happened that the move // went over the node borders (so the data might be in different nodes). // Also, a selection is invalidated. pEnd->nContent = pStt->nContent; rPam.DeleteMark(); GetDoc()->GetDocShell()->Broadcast( SwFormatFieldHint( nullptr, rNodes.IsDocNodes() ? SwFormatFieldHintWhich::INSERTED : SwFormatFieldHintWhich::REMOVED ) ); return; } ++aSttIdx; } else if( pDestNd ) { if( rPos.nContent.GetIndex() ) { if( rPos.nContent.GetIndex() == pDestNd->Len() ) { ++rPos.nNode; } else if( rPos.nContent.GetIndex() ) { // if the EndNode is split than correct the EndIdx const bool bCorrEnd = aEndIdx == rPos.nNode; // if no text is attached to the TextNode, split it if( rNodes.IsDocNodes() ) { SwDoc* const pInsDoc = pDestNd->GetDoc(); ::sw::UndoGuard const ug(pInsDoc->GetIDocumentUndoRedo()); pInsDoc->getIDocumentContentOperations().SplitNode( rPos, false ); } else { pDestNd->SplitContentNode(rPos, nullptr); } if ( bCorrEnd ) { --aEndIdx; } } } // at the end only an empty TextNode is left over bSplitDestNd = true; } SwTextNode* const pEndSrcNd = aEndIdx.GetNode().GetTextNode(); if ( pEndSrcNd ) { // at the end of this range a new TextNode will be created if( !bSplitDestNd ) { if( rPos.nNode < rNodes.GetEndOfContent().GetIndex() ) { ++rPos.nNode; } pDestNd = rNodes.MakeTextNode( rPos.nNode, pEndSrcNd->GetTextColl() ); --rPos.nNode; rPos.nContent.Assign( pDestNd, 0 ); } else { pDestNd = rPos.nNode.GetNode().GetTextNode(); } if (pDestNd && pEnd->nContent.GetIndex()) { // move the content into the new node SwIndex aIdx( pEndSrcNd, 0 ); pEndSrcNd->CutText( pDestNd, rPos.nContent, aIdx, pEnd->nContent.GetIndex()); } if (pDestNd && bCopyCollFormat) { SwDoc* const pInsDoc = pDestNd->GetDoc(); ::sw::UndoGuard const ug(pInsDoc->GetIDocumentUndoRedo()); pEndSrcNd->CopyCollFormat( *pDestNd ); } } else { if ( pSrcNd && aEndIdx.GetNode().IsContentNode() ) { ++aEndIdx; } if( !bSplitDestNd ) { ++rPos.nNode; rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), 0 ); } } if( aEndIdx != aSttIdx ) { // move the nodes into the NodesArary const sal_uLong nSttDiff = aSttIdx.GetIndex() - pStt->nNode.GetIndex(); SwNodeRange aRg( aSttIdx, aEndIdx ); MoveNodes( aRg, rNodes, rPos.nNode ); // if in the same node array, all indices are now at new positions (so correct them) if( &rNodes == this ) { pStt->nNode = aRg.aEnd.GetIndex() - nSttDiff; } } // if the StartNode was moved to whom the cursor pointed, so // the content must be registered in the current content! if ( &pStt->nNode.GetNode() == &GetEndOfContent() ) { const bool bSuccess = GoPrevious( &pStt->nNode ); OSL_ENSURE( bSuccess, "Move() - no ContentNode here" ); } pStt->nContent.Assign( pStt->nNode.GetNode().GetContentNode(), pStt->nContent.GetIndex() ); // Correct the PaM, because it might have happened that the move // went over the node borders (so the data might be in different nodes). // Also, a selection is invalidated. *pEnd = *pStt; rPam.DeleteMark(); GetDoc()->GetDocShell()->Broadcast( SwFormatFieldHint( nullptr, rNodes.IsDocNodes() ? SwFormatFieldHintWhich::INSERTED : SwFormatFieldHintWhich::REMOVED ) ); } ///@see SwNodes::MoveNodes (TODO: seems to be C&P programming here) void SwNodes::CopyNodes( const SwNodeRange& rRange, const SwNodeIndex& rIndex, bool bNewFrames, bool bTableInsDummyNode ) const { SwDoc* pDoc = rIndex.GetNode().GetDoc(); SwNode * pCurrentNode; if( rIndex == 0 || ( (pCurrentNode = &rIndex.GetNode())->GetStartNode() && !pCurrentNode->StartOfSectionIndex() )) return; SwNodeRange aRg( rRange ); // skip "simple" StartNodes or EndNodes while( SwNodeType::Start == (pCurrentNode = & aRg.aStart.GetNode())->GetNodeType() || ( pCurrentNode->IsEndNode() && !pCurrentNode->m_pStartOfSection->IsSectionNode() ) ) ++aRg.aStart; const SwNode *aEndNode = &aRg.aEnd.GetNode(); int nIsEndOfContent = (aEndNode == &aEndNode->GetNodes().GetEndOfContent()) ? 1 : 0; if (0 == nIsEndOfContent) { // if aEnd-1 points to no ContentNode, search previous one --aRg.aEnd; // #i107142#: if aEnd is start node of a special section, do nothing. // Otherwise this could lead to crash: going through all previous // special section nodes and then one before the first. if (aRg.aEnd.GetNode().StartOfSectionIndex() != 0) { while( ((pCurrentNode = & aRg.aEnd.GetNode())->GetStartNode() && !pCurrentNode->IsSectionNode() ) || ( pCurrentNode->IsEndNode() && SwNodeType::Start == pCurrentNode->m_pStartOfSection->GetNodeType()) ) { --aRg.aEnd; } } ++aRg.aEnd; } // is there anything left to copy? if( aRg.aStart >= aRg.aEnd ) return; // when inserting into the source range, nothing need to be done OSL_ENSURE( &aRg.aStart.GetNodes() == this, "aRg should use this node array" ); OSL_ENSURE( &aRg.aStart.GetNodes() == &aRg.aEnd.GetNodes(), "Range across different nodes arrays? You deserve punishment!"); if( &rIndex.GetNodes() == &aRg.aStart.GetNodes() && rIndex.GetIndex() >= aRg.aStart.GetIndex() && rIndex.GetIndex() < aRg.aEnd.GetIndex() ) return; SwNodeIndex aInsPos( rIndex ); SwNodeIndex aOrigInsPos( rIndex, -1 ); // original insertion position int nLevel = 0; // level counter for( long nNodeCnt = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex(); nNodeCnt > 0; --nNodeCnt ) { pCurrentNode = &aRg.aStart.GetNode(); switch( pCurrentNode->GetNodeType() ) { case SwNodeType::Table: // Does it copy a table in(to) a footnote? if( aInsPos < pDoc->GetNodes().GetEndOfInserts().GetIndex() && pDoc->GetNodes().GetEndOfInserts().StartOfSectionIndex() < aInsPos.GetIndex() ) { const long nDistance = pCurrentNode->EndOfSectionIndex() - aRg.aStart.GetIndex(); if (nDistance < nNodeCnt) nNodeCnt -= nDistance; else nNodeCnt = 1; // insert a DummyNode for a TableNode if( bTableInsDummyNode ) new SwPlaceholderNode(aInsPos); // copy all of the table's nodes into the current cell for( ++aRg.aStart; aRg.aStart.GetIndex() < pCurrentNode->EndOfSectionIndex(); ++aRg.aStart ) { // insert a DummyNode for the box-StartNode? if( bTableInsDummyNode ) new SwPlaceholderNode(aInsPos); SwStartNode* pSttNd = aRg.aStart.GetNode().GetStartNode(); CopyNodes( SwNodeRange( *pSttNd, + 1, *pSttNd->EndOfSectionNode() ), aInsPos, bNewFrames ); // insert a DummyNode for the box-EndNode? if( bTableInsDummyNode ) new SwPlaceholderNode(aInsPos); aRg.aStart = *pSttNd->EndOfSectionNode(); } // insert a DummyNode for the table-EndNode if( bTableInsDummyNode ) new SwPlaceholderNode(aInsPos); aRg.aStart = *pCurrentNode->EndOfSectionNode(); } else { SwNodeIndex nStt( aInsPos, -1 ); SwTableNode* pTableNd = static_cast(pCurrentNode)-> MakeCopy( pDoc, aInsPos ); const long nDistance = aInsPos.GetIndex() - nStt.GetIndex() - 2; if (nDistance < nNodeCnt) nNodeCnt -= nDistance; else nNodeCnt = 1 - nIsEndOfContent; aRg.aStart = pCurrentNode->EndOfSectionIndex(); if( bNewFrames && pTableNd ) { nStt = aInsPos; pTableNd->MakeOwnFrames(&nStt); } } break; case SwNodeType::Section: // If the end of the section is outside the copy range, // the section node will skipped, not copied! // If someone want to change this behaviour, he has to adjust the function // lcl_NonCopyCount(..) in ndcopy.cxx which relies on it. if( pCurrentNode->EndOfSectionIndex() < aRg.aEnd.GetIndex() ) { // copy of the whole section, so create a new SectionNode SwNodeIndex nStt( aInsPos, -1 ); SwSectionNode* pSectNd = static_cast(pCurrentNode)-> MakeCopy( pDoc, aInsPos ); const long nDistance = aInsPos.GetIndex() - nStt.GetIndex() - 2; if (nDistance < nNodeCnt) nNodeCnt -= nDistance; else nNodeCnt = 1 - nIsEndOfContent; aRg.aStart = pCurrentNode->EndOfSectionIndex(); if( bNewFrames && pSectNd && !pSectNd->GetSection().IsHidden() ) pSectNd->MakeOwnFrames(&nStt); } break; case SwNodeType::Start: { SwStartNode* pTmp = new SwStartNode( aInsPos, SwNodeType::Start, static_cast(pCurrentNode)->GetStartNodeType() ); new SwEndNode( aInsPos, *pTmp ); --aInsPos; nLevel++; } break; case SwNodeType::End: if( nLevel ) // complete section { --nLevel; ++aInsPos; // EndNode already exists } else if( 1 == nNodeCnt && 1 == nIsEndOfContent ) // we have reached the EndOfContent node - nothing to do! continue; else if( !pCurrentNode->m_pStartOfSection->IsSectionNode() ) { // create a section at the original InsertPosition SwNodeRange aTmpRg( aOrigInsPos, 1, aInsPos ); pDoc->GetNodes().SectionDown( &aTmpRg, pCurrentNode->m_pStartOfSection->GetStartNodeType() ); } break; case SwNodeType::Text: case SwNodeType::Grf: case SwNodeType::Ole: { static_cast(pCurrentNode)->MakeCopy( pDoc, aInsPos, bNewFrames); } break; case SwNodeType::PlaceHolder: if (GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(*this)) { // than a SectionNode (start/end) is needed at the current // InsPos; if so skip it, otherwise ignore current node SwNode *const pTmpNd = & aInsPos.GetNode(); if( pTmpNd->IsSectionNode() || pTmpNd->StartOfSectionNode()->IsSectionNode() ) ++aInsPos; // skip } else { assert(!"How can this node be in the node array?"); } break; default: assert(false); } ++aRg.aStart; } } void SwNodes::DelDummyNodes( const SwNodeRange& rRg ) { SwNodeIndex aIdx( rRg.aStart ); while( aIdx.GetIndex() < rRg.aEnd.GetIndex() ) { if (SwNodeType::PlaceHolder == aIdx.GetNode().GetNodeType()) RemoveNode( aIdx.GetIndex(), 1, true ); else ++aIdx; } } SwStartNode* SwNodes::MakeEmptySection( const SwNodeIndex& rIdx, SwStartNodeType eSttNdTyp ) { SwStartNode* pSttNd = new SwStartNode( rIdx, SwNodeType::Start, eSttNdTyp ); new SwEndNode( rIdx, *pSttNd ); return pSttNd; } SwStartNode* SwNodes::MakeTextSection( const SwNodeIndex & rWhere, SwStartNodeType eSttNdTyp, SwTextFormatColl *pColl ) { SwStartNode* pSttNd = new SwStartNode( rWhere, SwNodeType::Start, eSttNdTyp ); new SwEndNode( rWhere, *pSttNd ); MakeTextNode( SwNodeIndex( rWhere, - 1 ), pColl ); return pSttNd; } //TODO: provide better documentation /** go to next section that is not protected nor hidden * * @note if !bSkipHidden and !bSkipProtect, use GoNext/GoPrevious * * @param pIdx * @param bSkipHidden * @param bSkipProtect * @return * @see SwNodes::GoNext * @see SwNodes::GoPrevious * @see SwNodes::GoNextSection (TODO: seems to be C&P programming here) */ SwContentNode* SwNodes::GoNextSection( SwNodeIndex * pIdx, bool bSkipHidden, bool bSkipProtect ) const { bool bFirst = true; SwNodeIndex aTmp( *pIdx ); const SwNode* pNd; while( aTmp < Count() - 1 ) { pNd = & aTmp.GetNode(); if (SwNodeType::Section == pNd->GetNodeType()) { const SwSection& rSect = static_cast(pNd)->GetSection(); if( (bSkipHidden && rSect.IsHiddenFlag()) || (bSkipProtect && rSect.IsProtectFlag()) ) // than skip the section aTmp = *pNd->EndOfSectionNode(); } else if( bFirst ) { if( pNd->m_pStartOfSection->IsSectionNode() ) { const SwSection& rSect = static_cast(pNd-> m_pStartOfSection)->GetSection(); if( (bSkipHidden && rSect.IsHiddenFlag()) || (bSkipProtect && rSect.IsProtectFlag()) ) // than skip the section aTmp = *pNd->EndOfSectionNode(); } } else if( SwNodeType::ContentMask & pNd->GetNodeType() ) { const SwSectionNode* pSectNd; if( ( bSkipHidden || bSkipProtect ) && nullptr != (pSectNd = pNd->FindSectionNode() ) && ( ( bSkipHidden && pSectNd->GetSection().IsHiddenFlag() ) || ( bSkipProtect && pSectNd->GetSection().IsProtectFlag() )) ) { aTmp = *pSectNd->EndOfSectionNode(); } else { (*pIdx) = aTmp; return const_cast(static_cast(pNd)); } } ++aTmp; bFirst = false; } return nullptr; } ///@see SwNodes::GoNextSection (TODO: seems to be C&P programming here) SwContentNode* SwNodes::GoPrevSection( SwNodeIndex * pIdx, bool bSkipHidden, bool bSkipProtect ) { bool bFirst = true; SwNodeIndex aTmp( *pIdx ); const SwNode* pNd; while( aTmp > 0 ) { pNd = & aTmp.GetNode(); if (SwNodeType::End == pNd->GetNodeType()) { if( pNd->m_pStartOfSection->IsSectionNode() ) { const SwSection& rSect = static_cast(pNd-> m_pStartOfSection)->GetSection(); if( (bSkipHidden && rSect.IsHiddenFlag()) || (bSkipProtect && rSect.IsProtectFlag()) ) // than skip section aTmp = *pNd->StartOfSectionNode(); } bFirst = false; } else if( bFirst ) { bFirst = false; if( pNd->m_pStartOfSection->IsSectionNode() ) { const SwSection& rSect = static_cast(pNd-> m_pStartOfSection)->GetSection(); if( (bSkipHidden && rSect.IsHiddenFlag()) || (bSkipProtect && rSect.IsProtectFlag()) ) // than skip section aTmp = *pNd->StartOfSectionNode(); } } else if( SwNodeType::ContentMask & pNd->GetNodeType() ) { const SwSectionNode* pSectNd; if( ( bSkipHidden || bSkipProtect ) && nullptr != (pSectNd = pNd->FindSectionNode() ) && ( ( bSkipHidden && pSectNd->GetSection().IsHiddenFlag() ) || ( bSkipProtect && pSectNd->GetSection().IsProtectFlag() )) ) { aTmp = *pSectNd; } else { (*pIdx) = aTmp; return const_cast(static_cast(pNd)); } } --aTmp; } return nullptr; } //TODO: improve documentation //TODO: The inventor of the "single responsibility principle" will be crying if you ever show this code to him! /** find the next/previous ContentNode or a table node with frames * * If no pEnd is given, search is started with FrameIndex; otherwise * search is started with the one before rFrameIdx and after pEnd. * * @param rFrameIdx node with frames to search in * @param pEnd ??? * @return result node; 0 (!!!) if not found */ SwNode* SwNodes::FindPrvNxtFrameNode( SwNodeIndex& rFrameIdx, const SwNode* pEnd ) const { SwNode* pFrameNd = nullptr; // no layout -> skip if( GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell() ) { SwNode* pSttNd = &rFrameIdx.GetNode(); // move of a hidden section? SwSectionNode* pSectNd = pSttNd->IsSectionNode() ? pSttNd->StartOfSectionNode()->FindSectionNode() : pSttNd->FindSectionNode(); if( !( pSectNd && pSectNd->GetSection().CalcHiddenFlag() ) ) { // in a table in table situation we have to assure that we don't leave the // outer table cell when the inner table is looking for a PrvNxt... SwTableNode* pTableNd = pSttNd->IsTableNode() ? pSttNd->StartOfSectionNode()->FindTableNode() : pSttNd->FindTableNode(); SwNodeIndex aIdx( rFrameIdx ); SwNode* pNd; if( pEnd ) { --aIdx; pNd = &aIdx.GetNode(); } else pNd = pSttNd; if( ( pFrameNd = pNd )->IsContentNode() ) rFrameIdx = aIdx; // search forward or backward for a content node else if( nullptr != ( pFrameNd = GoPrevSection( &aIdx, true, false )) && ::CheckNodesRange( aIdx, rFrameIdx, true ) && // Never out of the table at the start pFrameNd->FindTableNode() == pTableNd && // Bug 37652: Never out of the table at the end (!pFrameNd->FindTableNode() || pFrameNd->FindTableBoxStartNode() == pSttNd->FindTableBoxStartNode() ) && (!pSectNd || pSttNd->IsSectionNode() || pSectNd->GetIndex() < pFrameNd->GetIndex()) ) { rFrameIdx = aIdx; } else { if( pEnd ) aIdx = pEnd->GetIndex() + 1; else aIdx = rFrameIdx; // NEVER leave the section when doing this! if( ( pEnd && ( pFrameNd = &aIdx.GetNode())->IsContentNode() ) || ( nullptr != ( pFrameNd = GoNextSection( &aIdx, true, false )) && ::CheckNodesRange( aIdx, rFrameIdx, true ) && ( pFrameNd->FindTableNode() == pTableNd && // NEVER go out of the table cell at the end (!pFrameNd->FindTableNode() || pFrameNd->FindTableBoxStartNode() == pSttNd->FindTableBoxStartNode() ) ) && (!pSectNd || pSttNd->IsSectionNode() || pSectNd->EndOfSectionIndex() > pFrameNd->GetIndex()) )) { // Undo when merging a table with one before, if there is also one after it. // However, if the node is in a table, it needs to be returned if the // SttNode is a section or a table! SwTableNode* pTableNode; if (pSttNd->IsTableNode() && nullptr != (pTableNode = pFrameNd->FindTableNode()) && // TABLE IN TABLE: pTableNode != pSttNd->StartOfSectionNode()->FindTableNode()) { pFrameNd = pTableNode; rFrameIdx = *pFrameNd; } else rFrameIdx = aIdx; } else if( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() ) { pFrameNd = pNd->StartOfSectionNode(); rFrameIdx = *pFrameNd; } else { if( pEnd ) aIdx = pEnd->GetIndex() + 1; else aIdx = rFrameIdx.GetIndex() + 1; if( (pFrameNd = &aIdx.GetNode())->IsTableNode() ) rFrameIdx = aIdx; else { pFrameNd = nullptr; // is there some sectionnodes before a tablenode? while( aIdx.GetNode().IsSectionNode() ) { const SwSection& rSect = aIdx.GetNode(). GetSectionNode()->GetSection(); if( rSect.IsHiddenFlag() ) aIdx = aIdx.GetNode().EndOfSectionIndex()+1; else ++aIdx; } if( aIdx.GetNode().IsTableNode() ) { rFrameIdx = aIdx; pFrameNd = &aIdx.GetNode(); } } } } } } return pFrameNd; } void SwNodes::ForEach( sal_uLong nStart, sal_uLong nEnd, FnForEach_SwNodes fn, void* pArgs ) { if( nEnd > m_nSize ) nEnd = m_nSize; if( nStart < nEnd ) { sal_uInt16 cur = Index2Block( nStart ); BlockInfo** pp = m_ppInf.get() + cur; BlockInfo* p = *pp; sal_uInt16 nElem = sal_uInt16( nStart - p->nStart ); auto pElem = p->mvData.begin() + nElem; nElem = p->nElem - nElem; for(;;) { if( !(*fn)( static_cast(*pElem++), pArgs ) || ++nStart >= nEnd ) break; // next element if( !--nElem ) { // new block p = *++pp; pElem = p->mvData.begin(); nElem = p->nElem; } } } } void SwNodes::ForEach( const SwNodeIndex& rStart, const SwNodeIndex& rEnd, FnForEach_SwNodes fnForEach, void* pArgs ) { ForEach( rStart.GetIndex(), rEnd.GetIndex(), fnForEach, pArgs ); } void SwNodes::RemoveNode( sal_uLong nDelPos, sal_uLong nSz, bool bDel ) { #ifndef NDEBUG SwNode *const pFirst((*this)[nDelPos]); #endif for (sal_uLong nCnt = 0; nCnt < nSz; nCnt++) { SwNode* pNode = (*this)[ nDelPos + nCnt ]; SwTextNode * pTextNd = pNode->GetTextNode(); if (pTextNd) { pTextNd->RemoveFromList(); // remove RndStdIds::FLY_AS_CHAR *before* adjusting SwNodeIndex // so their anchor still points to correct node when deleted! // NOTE: this will call RemoveNode() recursively! // so adjust our indexes to account for removed nodes sal_uLong const nPos = pTextNd->GetIndex(); SwpHints *const pHints(pTextNd->GetpSwpHints()); if (pHints) { std::vector flys; for (size_t i = 0; i < pHints->Count(); ++i) { SwTextAttr *const pHint(pHints->Get(i)); if (RES_TXTATR_FLYCNT == pHint->Which()) { flys.push_back(pHint); } } for (SwTextAttr * pHint : flys) { pTextNd->DeleteAttribute(pHint); } // pHints may be dead now sal_uLong const nDiff = nPos - pTextNd->GetIndex(); if (nDiff) { nDelPos -= nDiff; } assert(pTextNd == (*this)[nDelPos + nCnt]); assert(pFirst == (*this)[nDelPos]); } } SwTableNode* pTableNode = pNode->GetTableNode(); if (pTableNode) { // The node that is deleted is a table node. // Need to make sure that all the redlines that are // related to this table are removed from the // 'Extra Redlines' array pTableNode->RemoveRedlines(); } } sal_uLong nEnd = nDelPos + nSz; SwNode* pNew = (*this)[ nEnd ]; for (SwNodeIndex& rIndex : m_vIndices->GetRingContainer()) { sal_uLong const nIdx = rIndex.GetIndex(); if (nDelPos <= nIdx && nIdx < nEnd) rIndex = *pNew; } std::vector aTempEntries; if( bDel ) { sal_uLong nCnt = nSz; BigPtrEntry *pDel = (*this)[ nDelPos+nCnt-1 ], *pPrev = (*this)[ nDelPos+nCnt-2 ]; // set temporary object // JP 24.08.98: this should actually be removed because one could // call Remove recursively, e.g. for character bound frames. However, // since there happens way too much here, this temporary object was // inserted that will be deleted in Remove again (see Bug 55406) aTempEntries.resize(nCnt); while( nCnt-- ) { delete pDel; pDel = pPrev; sal_uLong nPrevNdIdx = pPrev->GetPos(); BigPtrEntry* pTempEntry = &aTempEntries[nCnt]; BigPtrArray::Replace( nPrevNdIdx+1, pTempEntry ); if( nCnt ) pPrev = BigPtrArray::operator []( nPrevNdIdx - 1 ); // the accessed element can be a naked BigPtrEntry from // aTempEntries, so the downcast to SwNode* in // SwNodes::operator[] would be illegal (and unnecessary) } nDelPos = pDel->GetPos() + 1; } BigPtrArray::Remove( nDelPos, nSz ); } void SwNodes::InsertNode( const SwNodePtr pNode, const SwNodeIndex& rPos ) { BigPtrEntry* pIns = pNode; BigPtrArray::Insert( pIns, rPos.GetIndex() ); } void SwNodes::InsertNode( const SwNodePtr pNode, sal_uLong nPos ) { BigPtrEntry* pIns = pNode; BigPtrArray::Insert( pIns, nPos ); } // ->#112139# SwNode * SwNodes::DocumentSectionStartNode(SwNode * pNode) const { if (nullptr != pNode) { SwNodeIndex aIdx(*pNode); if (aIdx <= (*this)[0]->EndOfSectionIndex()) pNode = (*this)[0]; else { while ((*this)[0] != pNode->StartOfSectionNode()) pNode = pNode->StartOfSectionNode(); } } return pNode; } SwNode * SwNodes::DocumentSectionEndNode(SwNode * pNode) const { return DocumentSectionStartNode(pNode)->EndOfSectionNode(); } bool SwNodes::IsDocNodes() const { return this == &m_pMyDoc->GetNodes(); } void SwNodes::dumpAsXml(xmlTextWriterPtr pWriter) const { xmlTextWriterStartElement(pWriter, BAD_CAST("SwNodes")); for (sal_uLong i = 0; i < Count(); ++i) (*this)[i]->dumpAsXml(pWriter); xmlTextWriterEndElement(pWriter); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */