/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: textdoc.cxx,v $
 * $Revision: 1.6 $
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svtools.hxx"

#include <textdoc.hxx>

#include <stdlib.h>

SV_IMPL_PTRARR( TextCharAttribs, TextCharAttribPtr );



// Vergleichmethode wird von QuickSort gerufen...

EXTERN_C
#if defined( PM2 ) && (!defined( CSET ) && !defined ( MTW ) && !defined( WTC ))
int _stdcall
#else
#ifdef WNT
#if _MSC_VER >= 1200
int __cdecl
#else
int _cdecl
#endif
#else
int
#endif
#endif

CompareStart( const void* pFirst, const void* pSecond )
{
	if ( (*((TextCharAttrib**)pFirst))->GetStart() < (*((TextCharAttrib**)pSecond))->GetStart() )
		return (-1);
	else if ( (*((TextCharAttrib**)pFirst))->GetStart() > (*((TextCharAttrib**)pSecond))->GetStart() )
		return (1);
	return 0;
}


// -------------------------------------------------------------------------
// (+) class TextCharAttrib
// -------------------------------------------------------------------------
TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, USHORT nStart, USHORT nEnd )
{
	mpAttr = rAttr.Clone();
	mnStart = nStart,
	mnEnd = nEnd;
}

TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
{
	mpAttr = rTextCharAttrib.GetAttr().Clone();
	mnStart = rTextCharAttrib.mnStart;
	mnEnd = rTextCharAttrib.mnEnd;
}

TextCharAttrib::~TextCharAttrib()
{
	delete mpAttr;
}

// -------------------------------------------------------------------------
// (+) class TextCharAttribList
// -------------------------------------------------------------------------

TextCharAttribList::TextCharAttribList()
{
	mbHasEmptyAttribs = FALSE;
}

TextCharAttribList::~TextCharAttribList()
{
	// PTRARR_DEL
}

void TextCharAttribList::Clear( BOOL bDestroyAttribs )
{
	if ( bDestroyAttribs )
		TextCharAttribs::DeleteAndDestroy( 0, Count() );
	else
		TextCharAttribs::Remove( 0, Count() );
}


void TextCharAttribList::InsertAttrib( TextCharAttrib* pAttrib )
{
	if ( pAttrib->IsEmpty() )
		mbHasEmptyAttribs = TRUE;

	const USHORT nCount = Count();
	const USHORT nStart = pAttrib->GetStart(); // vielleicht besser fuer Comp.Opt.
	BOOL bInserted = FALSE;
	for ( USHORT x = 0; x < nCount; x++ )
	{
		TextCharAttrib* pCurAttrib = GetObject( x );
		if ( pCurAttrib->GetStart() > nStart )
		{
			Insert( pAttrib, x );
			bInserted = TRUE;
			break;
		}
	}
	if ( !bInserted )
		Insert( pAttrib, nCount );
}

void TextCharAttribList::ResortAttribs()
{
	if ( Count() )
		qsort( (void*)GetData(), Count(), sizeof( TextCharAttrib* ), CompareStart );
}

TextCharAttrib* TextCharAttribList::FindAttrib( USHORT nWhich, USHORT nPos )
{
	// Rueckwaerts, falls eins dort endet, das naechste startet.
	// => Das startende gilt...

	for ( USHORT nAttr = Count(); nAttr; )
	{
		TextCharAttrib* pAttr = GetObject( --nAttr );

		if ( pAttr->GetEnd() < nPos )
			return 0;

		if ( ( pAttr->Which() == nWhich ) && pAttr->IsIn(nPos) )
			return pAttr;
	}
	return NULL;
}

TextCharAttrib* TextCharAttribList::FindNextAttrib( USHORT nWhich, USHORT nFromPos, USHORT nMaxPos ) const
{
	DBG_ASSERT( nWhich, "FindNextAttrib: Which?" );
	const USHORT nAttribs = Count();
	for ( USHORT nAttr = 0; nAttr < nAttribs; nAttr++ )
	{
		TextCharAttrib* pAttr = GetObject( nAttr );
		if ( ( pAttr->GetStart() >= nFromPos ) && 
			 ( pAttr->GetEnd() <= nMaxPos ) && 
			 ( pAttr->Which() == nWhich ) )
			return pAttr;
	}
	return NULL;
}

BOOL TextCharAttribList::HasAttrib( USHORT nWhich ) const
{
	for ( USHORT nAttr = Count(); nAttr; )
	{
		const TextCharAttrib* pAttr = GetObject( --nAttr );
		if ( pAttr->Which() == nWhich )
			return TRUE;
	}
	return FALSE;
}

BOOL TextCharAttribList::HasBoundingAttrib( USHORT nBound )
{
	// Rueckwaerts, falls eins dort endet, das naechste startet.
	// => Das startende gilt...
	for ( USHORT nAttr = Count(); nAttr; )
	{
		TextCharAttrib* pAttr = GetObject( --nAttr );

		if ( pAttr->GetEnd() < nBound )
			return FALSE;

		if ( ( pAttr->GetStart() == nBound ) || ( pAttr->GetEnd() == nBound ) )
			return TRUE;
	}
	return FALSE;
}

TextCharAttrib* TextCharAttribList::FindEmptyAttrib( USHORT nWhich, USHORT nPos )
{
	if ( !mbHasEmptyAttribs )
		return 0;

	const USHORT nAttribs = Count();
	for ( USHORT nAttr = 0; nAttr < nAttribs; nAttr++ )
	{
		TextCharAttrib* pAttr = GetObject( nAttr );
		if ( pAttr->GetStart() > nPos )
			return 0;

		if ( ( pAttr->GetStart() == nPos ) && ( pAttr->GetEnd() == nPos ) && ( pAttr->Which() == nWhich ) )
			return pAttr;
	}
	return 0;
}

void TextCharAttribList::DeleteEmptyAttribs()
{
	for ( USHORT nAttr = 0; nAttr < Count(); nAttr++ )
	{
		TextCharAttrib* pAttr = GetObject( nAttr );
		if ( pAttr->IsEmpty() )
		{
			Remove( nAttr );
			delete pAttr;
			nAttr--;
		}
	}
	mbHasEmptyAttribs = FALSE;
}

#ifdef  DBG_UTIL
BOOL TextCharAttribList::DbgCheckAttribs()
{
	BOOL bOK = TRUE;
	for ( USHORT nAttr = 0; nAttr < Count(); nAttr++ )
	{
		TextCharAttrib* pAttr = GetObject( nAttr );
		if ( pAttr->GetStart() > pAttr->GetEnd() )
		{
			bOK = FALSE;
			DBG_ERROR( "Attr verdreht" );
		}
	}
	return bOK;
}
#endif

// -------------------------------------------------------------------------
// (+) class TextNode
// -------------------------------------------------------------------------

TextNode::TextNode( const String& rText ) :
	maText( rText )
{
}

void TextNode::ExpandAttribs( USHORT nIndex, USHORT nNew )
{
	if ( !nNew )
		return;

	BOOL bResort = FALSE;
	USHORT nAttribs = maCharAttribs.Count();
	for ( USHORT nAttr = 0; nAttr < nAttribs; nAttr++ )
	{
		TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
		if ( pAttrib->GetEnd() >= nIndex )
		{
			// Alle Attribute hinter der Einfuegeposition verschieben...
			if ( pAttrib->GetStart() > nIndex )
			{
				pAttrib->MoveForward( nNew );
			}
			// 0: Leeres Attribut expandieren, wenn an Einfuegestelle
			else if ( pAttrib->IsEmpty() )
			{
				// Index nicht pruefen, leeres durfte nur dort liegen.
				// Wenn spaeter doch Ueberpruefung:
				//   Spezialfall: Start == 0; AbsLen == 1, nNew = 1 => Expand, weil durch Absatzumbruch!
				// Start <= nIndex, End >= nIndex => Start=End=nIndex!
//				if ( pAttrib->GetStart() == nIndex )
					pAttrib->Expand( nNew );
			}
			// 1: Attribut startet davor, geht bis Index...
			else if ( pAttrib->GetEnd() == nIndex ) // Start muss davor liegen
			{
				// Nur expandieren, wenn kein Feature,
				// und wenn nicht in ExcludeListe!
				// Sonst geht z.B. ein UL bis zum neuen ULDB, beide expandieren
				if ( !maCharAttribs.FindEmptyAttrib( pAttrib->Which(), nIndex ) )
				{
					pAttrib->Expand( nNew );
				}
				else
					bResort = TRUE;
			}
			// 2: Attribut startet davor, geht hinter Index...
			else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
			{
				pAttrib->Expand( nNew );
			}
			// 3: Attribut startet auf Index...
			else if ( pAttrib->GetStart() == nIndex )
			{
				if ( nIndex == 0 )
				{
					pAttrib->Expand( nNew );
//					bResort = TRUE;		// es gibt ja keine Features mehr...
				}
				else
					pAttrib->MoveForward( nNew );
			}
		}

		DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribut verdreht!" );
		DBG_ASSERT( ( pAttrib->GetEnd() <= maText.Len() ), "Expand: Attrib groesser als Absatz!" );
		DBG_ASSERT( !pAttrib->IsEmpty(), "Leeres Attribut nach ExpandAttribs?" );
	}

	if ( bResort )
		maCharAttribs.ResortAttribs();

#ifdef EDITDEBUG
	DBG_ASSERT( CheckOrderedList( (TextCharAttribs*)&maCharAttribs ), "Expand: Start-Liste verdreht" );
#endif
}

void TextNode::CollapsAttribs( USHORT nIndex, USHORT nDeleted )
{
	if ( !nDeleted )
		return;

	BOOL bResort = FALSE;
	USHORT nEndChanges = nIndex+nDeleted;

	for ( USHORT nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
	{
		TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
		BOOL bDelAttr = FALSE;
		if ( pAttrib->GetEnd() >= nIndex )
		{
			// Alles Attribute hinter der Einfuegeposition verschieben...
			if ( pAttrib->GetStart() >= nEndChanges )
			{
				pAttrib->MoveBackward( nDeleted );
			}
			// 1. Innenliegende Attribute loeschen...
			else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) )
			{
				// Spezialfall: Attrubt deckt genau den Bereich ab
				// => als leeres Attribut behalten.
				if ( ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) )
					pAttrib->GetEnd() = nIndex;	// leer
				else
					bDelAttr = TRUE;
			}
			// 2. Attribut beginnt davor, endet drinnen oder dahinter...
			else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
			{
				if ( pAttrib->GetEnd() <= nEndChanges )	// endet drinnen
					pAttrib->GetEnd() = nIndex;
				else
					pAttrib->Collaps( nDeleted );		// endet dahinter
			}
			// 3. Attribut beginnt drinnen, endet dahinter...
			else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) )
			{
				// Features duerfen nicht expandieren!
				pAttrib->GetStart() = nEndChanges;
				pAttrib->MoveBackward( nDeleted );
			}
		}

		DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collaps: Attribut verdreht!" );
		DBG_ASSERT( ( pAttrib->GetEnd() <= maText.Len()) || bDelAttr, "Collaps: Attrib groesser als Absatz!" );
		if ( bDelAttr /* || pAttrib->IsEmpty() */ )
		{
			bResort = TRUE;
			maCharAttribs.RemoveAttrib( nAttr );
			delete pAttrib;
			nAttr--;
		}
		else if ( pAttrib->IsEmpty() )
			maCharAttribs.HasEmptyAttribs() = TRUE;
	}

	if ( bResort )
		maCharAttribs.ResortAttribs();

#ifdef EDITDEBUG
	DBG_ASSERT( CheckOrderedList( (TextCharAttribs)&maCharAttribs ), "Collaps: Start-Liste verdreht" );
#endif
}

void TextNode::InsertText( USHORT nPos, const String& rText )
{
	maText.Insert( rText, nPos );
	ExpandAttribs( nPos, rText.Len() );
}

void TextNode::InsertText( USHORT nPos, sal_Unicode c )
{
	maText.Insert( c, nPos );
	ExpandAttribs( nPos, 1 );
}

void TextNode::RemoveText( USHORT nPos, USHORT nChars )
{
	maText.Erase( nPos, nChars );
	CollapsAttribs( nPos, nChars );
}

TextNode* TextNode::Split( USHORT nPos, BOOL bKeepEndingAttribs )
{
	String aNewText;
	if ( nPos < maText.Len() )
	{
		aNewText = maText.Copy( nPos );
		maText.Erase( nPos );
	}
	TextNode* pNew = new TextNode( aNewText );

	for ( USHORT nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
	{
		TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
		if ( pAttrib->GetEnd() < nPos )
		{
			// bleiben unveraendert....
			;
		}
		else if ( pAttrib->GetEnd() == nPos )
		{
			// muessen als leeres Attribut kopiert werden.
			// !FindAttrib nur sinnvoll, wenn Rueckwaerts durch Liste!
			if ( bKeepEndingAttribs && !pNew->maCharAttribs.FindAttrib( pAttrib->Which(), 0 ) )
			{
				TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
				pNewAttrib->GetStart() = 0;
				pNewAttrib->GetEnd() = 0;
				pNew->maCharAttribs.InsertAttrib( pNewAttrib );
			}
		}
		else if ( pAttrib->IsInside( nPos ) || ( !nPos && !pAttrib->GetStart() ) )
		{
			// Wenn ganz vorne gecuttet wird, muss das Attribut erhalten bleiben!
			// muessen kopiert und geaendert werden
			TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
			pNewAttrib->GetStart() = 0;
			pNewAttrib->GetEnd() = pAttrib->GetEnd()-nPos;
			pNew->maCharAttribs.InsertAttrib( pNewAttrib );
			// stutzen:
			pAttrib->GetEnd() = nPos;
		}
		else
		{
			DBG_ASSERT( pAttrib->GetStart() >= nPos, "Start < nPos!" );
			DBG_ASSERT( pAttrib->GetEnd() >= nPos, "End < nPos!" );
			// alle dahinter verschieben in den neuen Node (this)
			maCharAttribs.RemoveAttrib( nAttr );
			pNew->maCharAttribs.InsertAttrib( pAttrib );
			pAttrib->GetStart() = pAttrib->GetStart() - nPos;
			pAttrib->GetEnd() = pAttrib->GetEnd() - nPos;
			nAttr--;
		}
	}
	return pNew;
}

void TextNode::Append( const TextNode& rNode )
{
	USHORT nOldLen = maText.Len();

	maText += rNode.GetText();

#ifdef EDITDEBUG
	DBG_ASSERT( maCharAttribs.DbgCheckAttribs(), "Attribute VOR AppendAttribs kaputt" );
#endif

	const USHORT nAttribs = rNode.GetCharAttribs().Count();
	for ( USHORT nAttr = 0; nAttr < nAttribs; nAttr++ )
	{
		TextCharAttrib* pAttrib = rNode.GetCharAttribs().GetAttrib( nAttr );
		BOOL bMelted = FALSE;
		if ( pAttrib->GetStart() == 0 )
		{
			// Evtl koennen Attribute zusammengefasst werden:
			USHORT nTmpAttribs = maCharAttribs.Count();
			for ( USHORT nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
			{
				TextCharAttrib* pTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );

				if ( pTmpAttrib->GetEnd() == nOldLen )
				{
					if ( ( pTmpAttrib->Which() == pAttrib->Which() ) &&
						 ( pTmpAttrib->GetAttr() == pAttrib->GetAttr() ) )
					{
						pTmpAttrib->GetEnd() =
                            pTmpAttrib->GetEnd() + pAttrib->GetLen();
						bMelted = TRUE;
						break;	// es kann nur eins von der Sorte an der Stelle geben
					}
				}
			}
		}

		if ( !bMelted )
		{
			TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
			pNewAttrib->GetStart() = pNewAttrib->GetStart() + nOldLen;
			pNewAttrib->GetEnd() = pNewAttrib->GetEnd() + nOldLen;
			maCharAttribs.InsertAttrib( pNewAttrib );
		}
	}

#ifdef EDITDEBUG
	DBG_ASSERT( maCharAttribs.DbgCheckAttribs(), "Attribute NACH AppendAttribs kaputt" );
#endif
}

// -------------------------------------------------------------------------
// (+) class TextDoc
// -------------------------------------------------------------------------

TextDoc::TextDoc()
{
	mnLeftMargin = 0;
};

TextDoc::~TextDoc()
{
	DestroyTextNodes();
}

void TextDoc::Clear()
{
	DestroyTextNodes();
}

void TextDoc::DestroyTextNodes()
{
	for ( ULONG nNode = 0; nNode < maTextNodes.Count(); nNode++ )
		delete maTextNodes.GetObject( nNode );
	maTextNodes.clear();
}

String TextDoc::GetText( const sal_Unicode* pSep ) const
{
	ULONG nLen = GetTextLen( pSep );
	ULONG nNodes = maTextNodes.Count();

	if ( nLen > STRING_MAXLEN )
	{
		DBG_ERROR( "Text zu gross fuer String" );
		return String();
	}

	String aASCIIText;
	ULONG nLastNode = nNodes-1;
	for ( ULONG nNode = 0; nNode < nNodes; nNode++ )
	{
		TextNode* pNode = maTextNodes.GetObject( nNode );
		String aTmp( pNode->GetText() );
		aASCIIText += aTmp;
		if ( pSep && ( nNode != nLastNode ) )
			aASCIIText += pSep;
	}

	return aASCIIText;
}

XubString TextDoc::GetText( ULONG nPara ) const
{
	XubString aText;
	TextNode* pNode = ( nPara < maTextNodes.Count() ) ? maTextNodes.GetObject( nPara ) : 0;
	if ( pNode )
		aText = pNode->GetText();

	return aText;
}


ULONG TextDoc::GetTextLen( const xub_Unicode* pSep, const TextSelection* pSel ) const
{
	ULONG nLen = 0;
	ULONG nNodes = maTextNodes.Count();
	if ( nNodes )
	{
		ULONG nStartNode = 0;
		ULONG nEndNode = nNodes-1;
		if ( pSel )
		{
			nStartNode = pSel->GetStart().GetPara();
			nEndNode = pSel->GetEnd().GetPara();
		}

		for ( ULONG nNode = nStartNode; nNode <= nEndNode; nNode++ )
		{
			TextNode* pNode = maTextNodes.GetObject( nNode );

			USHORT nS = 0;
			ULONG nE = pNode->GetText().Len();
			if ( pSel && ( nNode == pSel->GetStart().GetPara() ) )
				nS = pSel->GetStart().GetIndex();
			if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) )
				nE = pSel->GetEnd().GetIndex();

			nLen += ( nE - nS );
		}

		if ( pSep )
			nLen += (nEndNode-nStartNode) * String( pSep ).Len();
	}

	return nLen;
}

TextPaM TextDoc::InsertText( const TextPaM& rPaM, xub_Unicode c )
{
	DBG_ASSERT( c != 0x0A, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );
	DBG_ASSERT( c != 0x0D, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );

	TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() );
	pNode->InsertText( rPaM.GetIndex(), c );

	TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
	return aPaM;
}

TextPaM TextDoc::InsertText( const TextPaM& rPaM, const XubString& rStr )
{
	DBG_ASSERT( rStr.Search( 0x0A ) == STRING_NOTFOUND, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );
	DBG_ASSERT( rStr.Search( 0x0D ) == STRING_NOTFOUND, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );

	TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() );
	pNode->InsertText( rPaM.GetIndex(), rStr );

	TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.Len() );
	return aPaM;
}

TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM, BOOL bKeepEndingAttribs )
{
	TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() );
	TextNode* pNew = pNode->Split( rPaM.GetIndex(), bKeepEndingAttribs );

	maTextNodes.Insert( pNew, rPaM.GetPara()+1 );

	TextPaM aPaM( rPaM.GetPara()+1, 0 );
	return aPaM;
}

TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, TextNode* pRight )
{
	USHORT nPrevLen = pLeft->GetText().Len();
	pLeft->Append( *pRight );

	// der rechte verschwindet.
	ULONG nRight = maTextNodes.GetPos( pRight );
	maTextNodes.Remove( nRight );
	delete pRight;

	ULONG nLeft = maTextNodes.GetPos( pLeft );
	TextPaM aPaM( nLeft, nPrevLen );
	return aPaM;
}

TextPaM TextDoc::RemoveChars( const TextPaM& rPaM, USHORT nChars )
{
	TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() );
	pNode->RemoveText( rPaM.GetIndex(), nChars );

	return rPaM;
}

BOOL TextDoc::IsValidPaM( const TextPaM& rPaM )
{
	if ( rPaM.GetPara() >= maTextNodes.Count() )
	{
		DBG_ERROR( "PaM: Para out of range" );
		return FALSE;
	}
	TextNode * pNode = maTextNodes.GetObject( rPaM.GetPara() );
	if ( rPaM.GetIndex() > pNode->GetText().Len() )
	{
		DBG_ERROR( "PaM: Index out of range" );
		return FALSE;
	}
	return TRUE;
}

/*

void TextDoc::InsertAttribInSelection( TextNode* pNode, USHORT nStart, USHORT nEnd, const SfxPoolItem& rPoolItem )
{
	DBG_ASSERT( pNode, "Wohin mit dem Attribut?" );
	DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribut zu gross!" );

	// fuer Optimierung:
	// dieses endet am Anfang der Selektion => kann erweitert werden
	TextCharAttrib* pEndingAttrib = 0;
	// dieses startet am Ende der Selektion => kann erweitert werden
	TextCharAttrib* pStartingAttrib = 0;

	DBG_ASSERT( nStart <= nEnd, "Kleiner Rechenfehler in InsertAttribInSelection" );

	RemoveAttribs( pNode, nStart, nEnd, pStartingAttrib, pEndingAttrib, rPoolItem.Which() );

	if ( pStartingAttrib && pEndingAttrib &&
		 ( *(pStartingAttrib->GetItem()) == rPoolItem ) &&
		 ( *(pEndingAttrib->GetItem()) == rPoolItem ) )
	{
		// wird ein groesses Attribut.
		pEndingAttrib->GetEnd() = pStartingAttrib->GetEnd();
		pCurPool->Remove( *(pStartingAttrib->GetItem()) );
		pNode->GetCharAttribs().GetAttribs().Remove( pNode->GetCharAttribs().GetAttribs().GetPos( pStartingAttrib ) );
		delete pStartingAttrib;
	}
	else if ( pStartingAttrib && ( *(pStartingAttrib->GetItem()) == rPoolItem ) )
		pStartingAttrib->GetStart() = nStart;
	else if ( pEndingAttrib && ( *(pEndingAttrib->GetItem()) == rPoolItem ) )
		pEndingAttrib->GetEnd() = nEnd;
	else
		InsertAttrib( rPoolItem, pNode, nStart, nEnd );

	if ( pStartingAttrib )
		pNode->GetCharAttribs().ResortAttribs();
}

BOOL TextDoc::RemoveAttribs( TextNode* pNode, USHORT nStart, USHORT nEnd, USHORT nWhich )
{
	TextCharAttrib* pStarting;
	TextCharAttrib* pEnding;
	return RemoveAttribs( pNode, nStart, nEnd, pStarting, pEnding, nWhich );
}

BOOL TextDoc::RemoveAttribs( TextNode* pNode, USHORT nStart, USHORT nEnd, TextCharAttrib*& rpStarting, TextCharAttrib*& rpEnding, USHORT nWhich )
{
	DBG_ASSERT( pNode, "Wohin mit dem Attribut?" );
	DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribut zu gross!" );

	// dieses endet am Anfang der Selektion => kann erweitert werden
	rpEnding = 0;
	// dieses startet am Ende der Selektion => kann erweitert werden
	rpStarting = 0;

	BOOL bChanged = FALSE;

	DBG_ASSERT( nStart <= nEnd, "Kleiner Rechenfehler in InsertAttribInSelection" );

	// ueber die Attribute iterieren...
	USHORT nAttr = 0;
	TextCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
	while ( pAttr )
	{
		BOOL bRemoveAttrib = FALSE;
		if ( !nWhich || ( pAttr->Which() == nWhich ) )
		{
			// Attribut beginnt in Selection
			if ( ( pAttr->GetStart() >= nStart ) && ( pAttr->GetStart() <= nEnd ) )
			{
				bChanged = TRUE;
				if ( pAttr->GetEnd() > nEnd )
				{
					pAttr->GetStart() = nEnd;	// dann faengt es dahinter an
					rpStarting = pAttr;
					break;	// es kann kein weiteres Attrib hier liegen
				}
				else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) )
				{
					// Feature nur loeschen, wenn genau an der Stelle
					bRemoveAttrib = TRUE;
				}
			}

			// Attribut endet in Selection
			else if ( ( pAttr->GetEnd() >= nStart ) && ( pAttr->GetEnd() <= nEnd ) )
			{
				bChanged = TRUE;
				if ( ( pAttr->GetStart() < nStart ) && !pAttr->IsFeature() )
				{
					pAttr->GetEnd() = nStart;	// dann hoert es hier auf
					rpEnding = pAttr;
				}
				else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) )
				{
					// Feature nur loeschen, wenn genau an der Stelle
					bRemoveAttrib = TRUE;
				}
			}
			// Attribut ueberlappt die Selektion
			else if ( ( pAttr->GetStart() <= nStart ) && ( pAttr->GetEnd() >= nEnd ) )
			{
				bChanged = TRUE;
				if ( pAttr->GetStart() == nStart )
				{
					pAttr->GetStart() = nEnd;
					rpStarting = pAttr;
					break;	// es kann weitere Attribute geben!
				}
				else if ( pAttr->GetEnd() == nEnd )
				{
					pAttr->GetEnd() = nStart;
					rpEnding = pAttr;
					break;	// es kann weitere Attribute geben!
				}
				else // Attribut muss gesplittet werden...
				{
					USHORT nOldEnd = pAttr->GetEnd();
					pAttr->GetEnd() = nStart;
					rpEnding = pAttr;
//					ULONG nSavePos = pNode->GetCharAttribs().GetStartList().GetCurPos();
					InsertAttrib( *pAttr->GetItem(), pNode, nEnd, nOldEnd );
//					pNode->GetCharAttribs().GetStartList().Seek( nSavePos );
					break;	// es kann weitere Attribute geben!
				}
			}
		}
		if ( bRemoveAttrib )
		{
			DBG_ASSERT( ( pAttr != rpStarting ) && ( pAttr != rpEnding ), "Loeschen und behalten des gleichen Attributs ?" );
			pNode->GetCharAttribs().GetAttribs().Remove(nAttr);
			pCurPool->Remove( *pAttr->GetItem() );
			delete pAttr;
			nAttr--;
		}
		nAttr++;
		pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
	}
	return bChanged;
}

#pragma SEG_FUNCDEF(editdoc_3f)

void TextDoc::InsertAttrib( const SfxPoolItem& rPoolItem, TextNode* pNode, USHORT nStart, USHORT nEnd )
{
	// Diese Methode prueft nicht mehr, ob ein entspr. Attribut
	// schon an der Stelle existiert!

	// pruefen, ob neues Attrib oder einfach nur Ende eines Attribs...
//	const SfxPoolItem& rDefItem = pNode->GetContentAttribs().GetItem( rPoolItem.Which() );
//	BOOL bCreateAttrib = ( rDefItem != rPoolItem );

	// Durch den Verlust der Exclude-Liste geht es nicht mehr, dass ich
	// kein neues Attribut benoetige und nur das alte nicht expandiere...
//	if ( !bCreateAttrib )
	{
		// => Wenn schon Default-Item, dann wenigstens nur dann einstellen,
		// wenn davor wirklich ein entsprechendes Attribut.
//		if ( pNode->GetCharAttribs().FindAttrib( rPoolItem.Which(), nStart ) )
//			bCreateAttrib = TRUE;
		// Aber kleiner Trost:
		// Die wenigsten schreiben, aendern das Attr, schreiben, und
		// stellen dann wieder das Default-Attr ein.
	}

	// 22.9.95:
	// Die Uberlegung, einfach das andere Attribut nicht zu expandieren, war
	// sowieso falsch, da das DefAttr aus einer Vorlage kommen kann,
	// die irgendwann verschwindet!
//	if ( bCreateAttrib )
//	{
		TextCharAttrib* pAttrib = MakeCharAttrib( *pCurPool, rPoolItem, nStart, nEnd );
		DBG_ASSERT( pAttrib, "MakeCharAttrib fehlgeschlagen!" );
		pNode->GetCharAttribs().InsertAttrib( pAttrib );
//	}
//	else
//	{
//		TextCharAttrib* pTmpAttrib =
//			pNode->GetCharAttribs().FindAnyAttrib( rPoolItem.Which() );
//		if ( pTmpAttrib ) 	// sonst benoetige ich es sowieso nicht....
//		{
//			aExcludeList.Insert( pTmpAttrib->GetItem() );
//		}
//	}
}

#pragma SEG_FUNCDEF(editdoc_40)

void TextDoc::InsertAttrib( TextNode* pNode, USHORT nStart, USHORT nEnd, const SfxPoolItem& rPoolItem )
{
	if ( nStart != nEnd )
	{
		InsertAttribInSelection( pNode, nStart, nEnd, rPoolItem );
	}
	else
	{
		// Pruefen, ob schon ein neues Attribut mit der WhichId an der Stelle:
		TextCharAttrib* pAttr = pNode->GetCharAttribs().FindEmptyAttrib( rPoolItem.Which(), nStart );
		if ( pAttr )
		{
			// Attribut entfernen....
			pNode->GetCharAttribs().GetAttribs().Remove(
				pNode->GetCharAttribs().GetAttribs().GetPos( pAttr ) );
		}

		// pruefen, ob ein 'gleiches' Attribut an der Stelle liegt.
		pAttr = pNode->GetCharAttribs().FindAttrib( rPoolItem.Which(), nStart );
		if ( pAttr )
		{
			if ( pAttr->IsInside( nStart ) )	// splitten
			{
				// ???????????????????????????????
				// eigentlich noch pruefen, ob wirklich splittet, oder return !
				// ???????????????????????????????
				USHORT nOldEnd = pAttr->GetEnd();
				pAttr->GetEnd() = nStart;
				pAttr = MakeCharAttrib( *pCurPool, *(pAttr->GetItem()), nStart, nOldEnd );
				pNode->GetCharAttribs().InsertAttrib( pAttr );
			}
			else if ( pAttr->GetEnd() == nStart )
			{
				DBG_ASSERT( !pAttr->IsEmpty(), "Doch noch ein leeres Attribut?" );
				// pruefen, ob genau das gleiche Attribut
				if ( *(pAttr->GetItem()) == rPoolItem )
					return;
			}
		}
		InsertAttrib( rPoolItem, pNode, nStart, nStart );
	}
}

#pragma SEG_FUNCDEF(editdoc_41)

void TextDoc::FindAttribs( TextNode* pNode, USHORT nStartPos, USHORT nEndPos, SfxItemSet& rCurSet )
{
	DBG_ASSERT( pNode, "Wo soll ich suchen ?" );
	DBG_ASSERT( nStartPos <= nEndPos, "Ungueltiger Bereich!" );

	USHORT nAttr = 0;
	TextCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
	// keine Selection...
	if ( nStartPos == nEndPos )
	{
		while ( pAttr && ( pAttr->GetStart() <= nEndPos) )
		{
			const SfxPoolItem* pItem = 0;
			// Attribut liegt dadrueber...
			if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() > nStartPos ) )
				pItem = pAttr->GetItem();
			// Attribut endet hier, ist nicht leer
			else if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() == nStartPos ) )
			{
				if ( !pNode->GetCharAttribs().FindEmptyAttrib( pAttr->GetItem()->Which(), nStartPos ) )
					pItem = pAttr->GetItem();
			}
			// Attribut endet hier, ist leer
			else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() == nStartPos ) )
			{
//				if ( aExcludeList.FindAttrib( pAttr->GetItem()->Which() ) )
					pItem = pAttr->GetItem();
//				else if ( pNode->Len() == 0 )	// Sonderfall
//					pItem = pAttr->GetItem();
			}
			// Attribut beginnt hier
			else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() > nStartPos ) )
			{
				if ( nStartPos == 0 ) 	// Sonderfall
					pItem = pAttr->GetItem();
			}

			if ( pItem )
			{
				USHORT nWhich = pItem->Which();
				if ( rCurSet.GetItemState( nWhich ) == SFX_ITEM_OFF )
				{
					rCurSet.Put( *pItem );
				}
				else if ( rCurSet.GetItemState( nWhich ) == SFX_ITEM_ON )
				{
					const SfxPoolItem& rItem = rCurSet.Get( nWhich );
					if ( rItem != *pItem )
					{
						rCurSet.InvalidateItem( nWhich );
					}
				}
			}
			nAttr++;
			pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
		}
	}
	else	// Selektion
	{
		while ( pAttr && ( pAttr->GetStart() < nEndPos) )
		{
			const SfxPoolItem* pItem = 0;
			// Attribut liegt dadrueber...
			if ( ( pAttr->GetStart() <= nStartPos ) && ( pAttr->GetEnd() >= nEndPos ) )
				pItem = pAttr->GetItem();
			// Attribut startet mitten drin...
			else if ( pAttr->GetStart() >= nStartPos )
			{
				// !!! pItem = pAttr->GetItem();
				// einfach nur pItem reicht nicht, da ich z.B. bei Shadow
				// niemals ein ungleiches Item finden wuerde, da ein solche
				// seine Anwesenheit durch Abwesenheit repraesentiert!
				// if ( ... )
				// Es muesste geprueft werden, on genau das gleiche Attribut
				// an der Bruchstelle aufsetzt, was recht aufwendig ist.
				// Da ich beim Einfuegen von Attributen aber etwas optimiere
				// tritt der Fall nicht so schnell auf...
				// Also aus Geschwindigkeitsgruenden:
				rCurSet.InvalidateItem( pAttr->GetItem()->Which() );

			}
			// Attribut endet mitten drin...
			else if ( pAttr->GetEnd() > nStartPos )
			{
				// pItem = pAttr->GetItem();
				// s.o.

				// -----------------31.05.95 16:01-------------------
				//  Ist falsch, wenn das gleiche Attribut sofort wieder
				//  eingestellt wird!
				//  => Sollte am besten nicht vorkommen, also gleich beim
				//  	Setzen von Attributen richtig machen!
				// --------------------------------------------------
				rCurSet.InvalidateItem( pAttr->GetItem()->Which() );
			}

			if ( pItem )
			{
				USHORT nWhich = pItem->Which();
				if ( rCurSet.GetItemState( nWhich ) == SFX_ITEM_OFF )
				{
					rCurSet.Put( *pItem );
				}
				else if ( rCurSet.GetItemState( nWhich ) == SFX_ITEM_ON )
				{
					const SfxPoolItem& rItem = rCurSet.Get( nWhich );
					if ( rItem != *pItem )
					{
						rCurSet.InvalidateItem( nWhich );
					}
				}
			}
			nAttr++;
			pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
		}
	}
}


*/


