Milán Major

netscape-revival

1 branch
Code

lib/layout/edtele.cpp

/* -*- Mode: C++; tab-width: 4; tabs-indent-mode: nil -*- */

//
// Public interface and shared subsystem data.
//

#ifdef EDITOR

#include "editor.h"

#define  EDT_IS_SCRIPT(tf) (0 != (tf & (TF_SERVER | TF_SCRIPT)))

//-----------------------------------------------------------------------------
// CEditElement
//-----------------------------------------------------------------------------

//
// This version of the constructor is used to create a child element.
//
CEditElement::CEditElement(CEditElement *pParent, TagType tagType, char* pData): 
        m_tagType(tagType), 
        m_pParent(pParent), 
        m_pNext(0), 
        m_pChild(0),
        m_pTagData(0)
{
    if ( pData ) {
        SetTagData(pData);
    }
    CommonConstructor();
}

CEditElement::CEditElement(CEditElement *pParent, PA_Tag *pTag) :
        m_tagType(pTag->type), 
        m_pParent(pParent), 
        m_pNext(0), 
        m_pChild(0),
        m_pTagData(0)
{
    char *locked_buff;

    PA_LOCK(locked_buff, char *, pTag->data);
    if( locked_buff && *locked_buff != '>'){
        SetTagData( locked_buff );
    }
    PA_UNLOCK(pTag->data);
    CommonConstructor();
}

void CEditElement::CommonConstructor(){
    if( m_pParent ){
        CEditElement* pE = m_pParent->GetChild();

        //
        // there already is a child, add this child to the end of the list.
        //
        if( pE ){
            while( pE->m_pNext != 0 ){
                pE = pE->m_pNext;
            }
            pE->RawSetNextSibling(this);
        }
        else {
            // make this the first child.
            m_pParent->RawSetChild(this);
        }
    }
}


CEditElement::CEditElement( IStreamIn *pIn, CEditBuffer* /* pBuffer */ ):
        m_pParent(0), 
        m_pNext(0), 
        m_pChild(0),
        m_pTagData(0)
{
    m_tagType = (TagType) pIn->ReadInt();
    m_pTagData = pIn->ReadZString();
}

CEditElement::~CEditElement(){
    Finalize();
}

void CEditElement::Finalize(){
    Unlink();
    DeleteChildren();
    if( m_pTagData ) {
        XP_FREE(m_pTagData);
        m_pTagData = NULL;
    }
}

void
CEditElement::SetTagData(char* tagData)
{
    if( m_pTagData ) {
        XP_FREE(m_pTagData);
    }
    if( tagData ){
        m_pTagData = XP_STRDUP(tagData);
    }
    else {
        m_pTagData = tagData;
    }
}

void CEditElement::StreamOut( IStreamOut *pOut ){
    pOut->WriteInt( GetElementType() );
    pOut->WriteInt( m_tagType );
    pOut->WriteZString( m_pTagData );
}

XP_Bool CEditElement::ShouldStreamSelf( CEditSelection& local, CEditSelection& selection)
{
    return ( local.EqualRange( selection ) || ! local.Contains(selection));
}

void CEditElement::PartialStreamOut( IStreamOut* pOut, CEditSelection& selection) {
    // partially stream out each child.
    CEditSelection local;
    GetAll(local);
    if ( local.Intersects(selection) ) {
        XP_Bool bWriteSelf = ShouldStreamSelf(local, selection);
        if ( bWriteSelf ) {
            StreamOut(pOut);
        }
        CEditElement* pChild;
        for ( pChild = GetChild(); pChild; pChild = pChild->GetNextSibling() ) {
            pChild->PartialStreamOut(pOut, selection);
        }
        if ( bWriteSelf ) {
            pOut->WriteInt((int32)eElementNone);
        }
    }
}

XP_Bool CEditElement::ClipToMe(CEditSelection& selection, CEditSelection& local) {
    // Returns TRUE if selection intersects with "this".
    GetAll(local);
    return local.ClipTo(selection);
}

void CEditElement::GetAll(CEditSelection& selection) {
    CEditLeafElement* pFirstMostChild = CEditLeafElement::Cast(GetFirstMostChild());
    if ( ! pFirstMostChild ) {
        XP_ASSERT(FALSE);
        return;
    }
    selection.m_start.m_pElement = GetFirstMostChild()->Leaf();
    selection.m_start.m_iPos = 0;
    CEditLeafElement* pLast = GetLastMostChild()->Leaf();
    CEditLeafElement* pNext = pLast->NextLeaf();
    if ( pNext ) {
        selection.m_end.m_pElement = pNext;
        selection.m_end.m_iPos = 0;
    }
    else {
     // edge of document. Can't select any further.
        selection.m_end.m_pElement = pLast;
        selection.m_end.m_iPos = pLast->GetLen();
    }
    selection.m_bFromStart = FALSE;
}

EEditElementType CEditElement::GetElementType()
{
    return eElement;
}

// 
// static function calls the appropriate stream constructor
//

CEditElement* CEditElement::StreamCtor( IStreamIn *pIn, CEditBuffer *pBuffer ){
    CEditElement* pResult = StreamCtorNoChildren(pIn, pBuffer);
    if ( pResult ) {
        pResult->StreamInChildren(pIn, pBuffer);
    }
    return pResult;
}

void CEditElement::StreamInChildren(IStreamIn* pIn, CEditBuffer* pBuffer){
    CEditElement* pChild;
    while ( (pChild = CEditElement::StreamCtor(pIn, pBuffer)) != NULL ) {
        pChild->InsertAsLastChild(this);
    }
}

CEditElement* CEditElement::StreamCtorNoChildren( IStreamIn *pIn, CEditBuffer *pBuffer ){

    EEditElementType eType = (EEditElementType) pIn->ReadInt();
    switch( eType ){
        case eElementNone:
            return 0;

        case eElement:
            return new CEditElement( pIn, pBuffer );

        case eTextElement:
            return new CEditTextElement( pIn, pBuffer  );

        case eImageElement:
            return new CEditImageElement( pIn, pBuffer  );

        case eHorizRuleElement:
            return new CEditHorizRuleElement( pIn, pBuffer  );

        case eBreakElement:
            return new CEditBreakElement( pIn, pBuffer  );

        case eContainerElement:
            return new CEditContainerElement( pIn, pBuffer  );

        case eListElement:
            return new CEditListElement( pIn, pBuffer  );

        case eIconElement:
            return new CEditIconElement( pIn, pBuffer  );

        case eTargetElement:
            return new CEditTargetElement( pIn, pBuffer  );

        case eTableElement:
             return new CEditTableElement( pIn, pBuffer  );

        case eCaptionElement:
             return new CEditCaptionElement( pIn, pBuffer  );

        case eTableRowElement:
             return new CEditTableRowElement( pIn, pBuffer  );

        case eTableCellElement:
             return new CEditTableCellElement( pIn, pBuffer  );

        case eMultiColumnElement:
             return new CEditMultiColumnElement( pIn, pBuffer  );

       default:
            XP_ASSERT(0);
    }
    return 0;
}


//
// Scan up the tree looking to see if we are within 'tagType'.  If we stop and
//  we are not at the top of the tree, we found the tag we are looking for.
//
XP_Bool CEditElement::Within( int tagType ){
    CEditElement* pParent = GetParent();
    while( pParent && pParent->GetType() != tagType ){
        pParent = pParent->GetParent();
    }
    return (pParent != 0);
}

CEditBuffer* CEditElement::GetEditBuffer(){
    CEditRootDocElement *pE = GetRootDoc();
    if( pE ){
        return pE->GetBuffer();
    }
    else {
        return 0;
    }
}

XP_Bool CEditElement::InFormattedText(){
    CEditElement* pParent = GetParent();

    if( IsA( P_TEXT) && (Text()->m_tf & (TF_SERVER|TF_SCRIPT)) != 0 ){
        return TRUE;
    }

    while( pParent && BitSet( edt_setFormattedText, pParent->GetType() ) == 0 ){
        pParent = pParent->GetParent();
    }
    return (pParent != 0);
}


//
// Fills in the data member of the tag, as well as the type informaiton.
//
void CEditElement::SetTagData( PA_Tag* pTag, char* pTagData){
    PA_Block buff;
    char *locked_buff;
    int iLen;

    pTag->type = m_tagType;
    pTag->edit_element = this;

    iLen = XP_STRLEN(pTagData);
    buff = (PA_Block)PA_ALLOC((iLen+1) * sizeof(char));
    if (buff != NULL)
    {
        PA_LOCK(locked_buff, char *, buff);
        XP_BCOPY(pTagData, locked_buff, iLen);
        locked_buff[iLen] = '\0';
        PA_UNLOCK(buff);
    }
    else { 
        // LTNOTE: out of memory, should throw an exception
        return;
    }

    pTag->data = buff;
    pTag->data_len = iLen;
    pTag->next = NULL;
    return;
}

PA_Tag* CEditElement::TagOpen( int /* iEditOffset */ ){
    PA_Tag *pTag = XP_NEW( PA_Tag );
    XP_BZERO( pTag, sizeof( PA_Tag ) );
    if( GetTagData() ){
        SetTagData( pTag, GetTagData() );
    }
    else {
        SetTagData( pTag, ">" );
    }
    return pTag;
}

PA_Tag* CEditElement::TagEnd( ){
    if( TagHasClose( m_tagType ) || BitSet( edt_setWriteEndTag, m_tagType ) ){
        PA_Tag *pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        pTag->type = m_tagType;
        pTag->is_end = TRUE;
        pTag->edit_element = this;
        return pTag;
    }
    return 0;
}


XP_Bool CEditElement::Reduce( CEditBuffer* /* pBuffer */ ){
    if( !BitSet( edt_setSoloTags,  GetType()  ) &&  GetChild() == 0 ){
        return TRUE;
    }
    else if( BitSet( edt_setCharFormat,  GetType()  ) ){
        CEditElement *pNext = GetNextSibling();
        if( pNext && pNext->GetType() == GetType() ){
            // FONTs and Anchors need better compares than this.
            Merge(pNext);

            // make sure it stays in the tree so it dies a natural death (because
            //  it has no children)
            pNext->InsertAfter(this);
            return FALSE;
        }
    }
    return FALSE;
}

int CEditElement::GetDefaultFontSize(){
    TagType t = GetType();
    if( !BitSet( edt_setTextContainer,  t  ) ){
        CEditElement* pCont = FindContainer();
        if( pCont ){
            t = pCont->GetType();
        }
        else {
            return 0;       // no default font size
        }
    }

    switch( t ){
        case P_HEADER_1:
            return 6;
        case P_HEADER_2:
            return 5;
        case P_HEADER_3:
            return 4;
        case P_HEADER_4:
            return 3;
        case P_HEADER_5:
            return 2; 
        case P_HEADER_6:
            return 1;
        default:
            return 3;
    }
}

CEditInsertPoint CEditElement::IndexToInsertPoint(ElementIndex index, XP_Bool bStickyAfter) {
    if ( index < 0 ) {
        XP_ASSERT(FALSE);
        index = 0;
    }
    CEditElement* pChild;
    CEditElement* pLastChild = NULL;
    ElementIndex childCount = 0;
    // XP_TRACE(("IndexToInsertPoint: 0x%08x (%d) index = %d", this, GetElementIndex(), index));
    for ( pChild = GetChild();
        pChild;
        pChild = pChild->GetNextSibling()) {
        pLastChild = pChild;
        childCount = pChild->GetPersistentCount();
        if ( index < childCount
            || (index == childCount && ! bStickyAfter) ){
            return pChild->IndexToInsertPoint(index, bStickyAfter);
        }
            
        index -= childCount;
    }
    if ( ! pLastChild ) { // No children at all
        childCount = GetPersistentCount();
        if ( index > childCount ){
            XP_ASSERT(FALSE);
            index = childCount;
        }
        return CEditInsertPoint(this, index);
    }
    // Ran off end of children
    return pLastChild->IndexToInsertPoint(childCount, bStickyAfter);
}

CPersistentEditInsertPoint CEditElement::GetPersistentInsertPoint(ElementOffset offset){
    XP_ASSERT(FALSE);   // This method should never be called
    return CPersistentEditInsertPoint(GetElementIndex() + offset);
}

ElementIndex CEditElement::GetPersistentCount()
{
    ElementIndex count = 0;
    for ( CEditElement* c = GetChild();
        c;
        c = c->GetNextSibling() ) {
        count += c->GetPersistentCount();
    }
    return count;
}

ElementIndex CEditElement::GetElementIndex()
{
    CEditElement* parent = GetParent();
    if ( parent )
        return parent->GetElementIndexOf(this);
    else
        return 0;
}

ElementIndex CEditElement::GetElementIndexOf(CEditElement* child)
{
    ElementIndex index = GetElementIndex();
    for ( CEditElement* c = GetChild();
        c;
        c = c->GetNextSibling() ) {
        if ( child == c )
        {
            return index;
        }
        index += c->GetPersistentCount();
    }
    XP_ASSERT(FALSE); // Couldn't find this child.
    return index;
}

void CEditElement::FinishedLoad( CEditBuffer* pBuffer ){
    CEditElement* pNext;
    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pNext ) {
        pNext = pChild->GetNextSibling();
        if ( IsAcceptableChild(*pChild) ){
            pChild->FinishedLoad(pBuffer);
        }
        else {
#ifdef DEBUG
            XP_TRACE(("Removing an unacceptable child. Parent type %d child type %d.\n",
                GetElementType(), pChild->GetElementType()));
#endif
            delete pChild;
        }
    }
}

void CEditElement::EnsureSelectableSiblings(CEditBuffer* pBuffer)
{
    // To ensure that we can edit before or after the table,
    // make sure that there is a container before and after the table.

    CEditElement* pParent = GetParent();
    if ( ! pParent ) {
        return;
    }
    {
        // Make sure the previous sibling exists and is a container
        CEditElement* pPrevious = GetPreviousSibling();
        if ( ! pPrevious || !pPrevious->IsContainer() ) {
            pPrevious = CEditContainerElement::NewDefaultContainer( NULL, 
                            pParent->GetDefaultAlignment() );
            pPrevious->InsertBefore(this);
			pPrevious->FinishedLoad(pBuffer);
        }
    }
    {
        // Make sure the next sibling exists and is container
        CEditElement* pNext = GetNextSibling();
        if ( ! pNext || pNext->IsEndContainer() || !pNext->IsContainer() ) {
            pNext = CEditContainerElement::NewDefaultContainer( NULL, 
                            pParent->GetDefaultAlignment() );
            pNext->InsertAfter(this);
			pNext->FinishedLoad(pBuffer);
        }
    }
}

//-----------------------------------------------------------------------------
//  Reverse navagation (left)
//-----------------------------------------------------------------------------

// these routines are a little expensive.  If we need to, we can make the linked
//  lists of elements, doubly linked.
//
CEditElement* CEditElement::GetPreviousSibling(){
    if( GetParent() == 0 ){
        return 0;
    }

    // point to our first sibling.
    CEditElement *pSibling = GetParent()->GetChild();

    // if we are the first sibling, then there is no previous sibling.
    if ( pSibling == this ){
        return 0;
    }

    // if we get an Exception in this loop, the tree is fucked up!
    while( pSibling->GetNextSibling() != this ){
        pSibling = pSibling->GetNextSibling();
    }
    return pSibling;
    
}

void CEditElement::SetChild(CEditElement *pChild){
#if 0
#ifdef DEBUG
    if ( pChild ){
        XP_ASSERT(IsAcceptableChild(*pChild));
    }
#endif
#endif
    RawSetChild(pChild);
}

void CEditElement::SetNextSibling(CEditElement* pNext){
#if 0
#ifdef DEBUG
    if ( m_pParent && pNext) {
        XP_ASSERT(m_pParent->IsAcceptableChild(*pNext));
    }
#endif
#endif
    RawSetNextSibling(pNext);
}

CEditElement* CEditElement::GetLastChild(){
    CEditElement* pChild;
    if( (pChild = GetChild()) == 0 ){
        return 0;
    }
    while( pChild->GetNextSibling() ){
        pChild = pChild->GetNextSibling();
    }
    return pChild;
}

CEditElement* CEditElement::GetFirstMostChild(){
    CEditElement* pChild = this;
    CEditElement* pPrev = pChild;
    while( pPrev ){
        pChild = pPrev;
        pPrev = pPrev->GetChild();
    }
    return pChild;
}

CEditElement* CEditElement::GetLastMostChild(){
    CEditElement* pChild = this;
    CEditElement* pNext = pChild;
    while( pNext ){
        pChild = pNext;
        pNext = pNext->GetLastChild();
    }
    return pChild;
}

CEditTableCellElement* CEditElement::GetTableCell() {
    // Returns containing cell, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsTableCell() ) {
            break;
        }
        if ( pElement->IsSubDoc() && pElement != this ) {
            return NULL;
        }
        pElement = pElement->GetParent();
    }
    return (CEditTableCellElement*) pElement;
}

CEditTableCellElement* CEditElement::GetTableCellIgnoreSubdoc() {
    // Returns containing cell, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsTableCell() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return (CEditTableCellElement*) pElement;
}

CEditTableRowElement* CEditElement::GetTableRow() {
    // Returns containing cell, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsTableRow() ) {
            break;
        }
        if ( pElement->IsSubDoc() && pElement != this ) {
            return NULL;
        }
        pElement = pElement->GetParent();
    }
    return (CEditTableRowElement*) pElement;
}

CEditTableRowElement* CEditElement::GetTableRowIgnoreSubdoc() {
    // Returns containing cell, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsTableRow() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return (CEditTableRowElement*) pElement;
}

CEditCaptionElement* CEditElement::GetCaption() {
    // Returns containing tavle, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsCaption() ) {
            break;
        }
        if ( pElement->IsSubDoc() && pElement != this ) {
            return NULL;
        }
        pElement = pElement->GetParent();
    }
    return (CEditCaptionElement*) pElement;
}

CEditCaptionElement* CEditElement::GetCaptionIgnoreSubdoc() {
    // Returns containing table, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsCaption() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return (CEditCaptionElement*) pElement;
}

CEditTableElement* CEditElement::GetTable() {
    // Returns containing table, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsTable() ) {
            break;
        }
        if ( pElement->IsSubDoc() && pElement != this ) {
            return NULL;
        }
        pElement = pElement->GetParent();
    }
    return (CEditTableElement*) pElement;
}

CEditTableElement* CEditElement::GetTableIgnoreSubdoc() {
    // Returns containing table, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsTable() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return (CEditTableElement*) pElement;
}

CEditElement* CEditElement::GetTopmostTableOrMultiColumn() {
    // Returns containing table, or NULL if none.
    CEditElement* pResult = NULL;
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsTable() || pElement->IsMultiColumn()) {
            pResult = pElement;
        }
        pElement = pElement->GetParent();
    }
    return pResult;
}

CEditElement* CEditElement::GetTableOrMultiColumnIgnoreSubdoc() {
    // Returns containing table, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsTable() || pElement->IsMultiColumn() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return pElement;
}

CEditElement* CEditElement::GetSubDocOrMultiColumnSkipRoot() {
    // Returns containing sub-doc, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsMultiColumn() ||
            (pElement->IsSubDoc() && !pElement->IsRoot() ) ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return pElement;
}

CEditMultiColumnElement* CEditElement::GetMultiColumn() {
    // Returns containing MultiColumn, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsMultiColumn() ) {
            break;
        }
        if ( pElement->IsSubDoc() && pElement != this ) {
            return NULL;
        }
        pElement = pElement->GetParent();
    }
    return (CEditMultiColumnElement*) pElement;
}

CEditMultiColumnElement* CEditElement::GetMultiColumnIgnoreSubdoc() {
    // Returns containing MultiColumn, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsMultiColumn() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return (CEditMultiColumnElement*) pElement;
}

CEditSubDocElement* CEditElement::GetSubDoc() {
    // Returns containing sub-doc, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsSubDoc() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return (CEditSubDocElement*) pElement;
}

CEditSubDocElement* CEditElement::GetSubDocSkipRoot() {
    // Returns containing sub-doc, or NULL if none.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsSubDoc() && !pElement->IsRoot() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return (CEditSubDocElement*) pElement;
}


CEditRootDocElement* CEditElement::GetRootDoc(){
    // Returns containing root.
    CEditElement* pElement = this;
    while ( pElement ) {
        if ( pElement->IsRoot() ) {
            break;
        }
        pElement = pElement->GetParent();
    }
    return (CEditRootDocElement*) pElement;
}

ED_Alignment CEditElement::GetDefaultAlignment(){
    if ( m_pParent )
        return m_pParent->GetDefaultAlignment();
/*
    return ED_ALIGN_LEFT;
*/
    return ED_ALIGN_DEFAULT;
}

ED_Alignment CEditElement::GetDefaultVAlignment(){
    if ( m_pParent )
        return m_pParent->GetDefaultVAlignment();
    return ED_ALIGN_TOP;
}

CEditElement* CEditElement::UpLeft( PMF_EditElementTest pmf, void *pTestData ){
    CEditElement *pPrev = this;
    CEditElement* pRet;

    while( (pPrev = pPrev->GetPreviousSibling()) != NULL ){
        pRet = pPrev->DownLeft( pmf, pTestData );
        if( pRet ){
            return pRet;
        }
    }
    if( GetParent() ){
        return GetParent()->UpLeft( pmf, pTestData );
    }
    else{
        return 0;
    }
}

//
// All the children come before the node.
//
CEditElement* CEditElement::DownLeft( PMF_EditElementTest pmf, void *pTestData, 
            XP_Bool /* bIgnoreThis */ ){
    CEditElement *pChild;
    CEditElement *pRet;

    pChild = GetLastChild();
    while( pChild != NULL ){
        if( (pRet = pChild->DownLeft( pmf, pTestData )) != NULL ){
            return pRet;
        }
        pChild = pChild->GetPreviousSibling();
    }
    if( TestIsTrue( pmf, pTestData ) ){ 
        return this;
    }
    return 0;
}


CEditElement* CEditElement::FindPreviousElement( PMF_EditElementTest pmf, 
        void *pTestData ){
    return UpLeft( pmf, pTestData );
}

//-----------------------------------------------------------------------------
// forward navagation (right)
//-----------------------------------------------------------------------------
CEditElement* CEditElement::UpRight( PMF_EditElementTest pmf, void *pTestData ){
    CEditElement *pNext = this;
    CEditElement* pRet;

    while( (pNext = pNext->GetNextSibling()) != NULL ){
        pRet = pNext->DownRight( pmf, pTestData );
        if( pRet ){
            return pRet;
        }
    }
    if( GetParent() ){
        return GetParent()->UpRight( pmf, pTestData );
    }
    else{
        return 0;
    }
}


CEditElement* CEditElement::DownRight( PMF_EditElementTest pmf, void *pTestData, 
            XP_Bool bIgnoreThis ){

    CEditElement *pChild;
    CEditElement *pRet;

    if( !bIgnoreThis && TestIsTrue( pmf, pTestData ) ){ 
        return this;
    }
    pChild = GetChild();
    while( pChild != NULL ){
        if( (pRet = pChild->DownRight( pmf, pTestData )) != NULL ){
            return pRet;
        }
        pChild = pChild->GetNextSibling();
    }
    return 0;
}

CEditElement* CEditElement::FindNextElement( PMF_EditElementTest pmf, 
        void *pTestData ){
    CEditElement *pRet;

    pRet = DownRight( pmf, pTestData, TRUE );
    if( pRet ){
        return pRet;
    }
    return UpRight( pmf, pTestData );
}


// 
// routine looks for a valid text block for positioning during editing.
//
XP_Bool CEditElement::FindText( void* /*pVoid*/ ){ 
    CEditElement *pPrev;

    //
    // if this is a text block and the layout element actually points to 
    //  something, return it.
    //
    if( GetType() == P_TEXT ){
        CEditTextElement *pText = Text();
        if( pText->GetLen() == 0 ){
            //
            // Find only empty text blocks that occur at the beginning of
            //  a paragraph (as a paragraph place holder)
            //
            pPrev = FindPreviousElement( &CEditElement::FindTextAll, 0 );
            if( pPrev && pPrev->FindContainer() == FindContainer() ){
                return FALSE;
            }
        }
        return TRUE;
    }
    return FALSE;
}

XP_Bool CEditElement::FindImage( void* /*pVoid*/ ){ 
    return IsImage() ;
}

XP_Bool CEditElement::FindTarget( void* /*pVoid*/ ){ 
    return GetElementType() == eTargetElement ;
}

// 
// routine looks for a valid text block for positioning during editing.
//
XP_Bool CEditElement::FindLeaf( void* pVoid ){ 
    if( !IsLeaf() ){
        return FALSE;
    }
    if( IsA(P_TEXT) ){
        return FindText( pVoid );
    }
    else {
        return TRUE;
    }
}


XP_Bool CEditElement::FindTextAll( void* /*pVoid*/ ){ 

    //
    // if this is a text block and the layout element actually points to 
    //  something, return it.
    //
    if( GetType() == P_TEXT ){
        return TRUE;
    }
    return FALSE;
}

XP_Bool CEditElement::FindLeafAll( void* /*pVoid*/ ){ 

    //
    // if this is a text block and the layout element actually points to 
    //  something, return it.
    //
    if( IsLeaf() ){
        return TRUE;
    }
    return FALSE;
}

XP_Bool CEditElement::SplitContainerTest( void* /*pVoid*/ ){ 
    return BitSet( edt_setTextContainer,  GetType()  );
}

XP_Bool CEditElement::SplitFormattingTest( void* pVoid ){ 
    return (void*)GetType() == pVoid;
}

XP_Bool CEditElement::FindContainer( void* /*pVoid*/ ){
    return IsContainer();
}


//-----------------------------------------------------------------------------
//  Default printing routines.
//-----------------------------------------------------------------------------

void CEditElement::PrintOpen( CPrintState *pPrintState ){
    InternalPrintOpen(pPrintState, m_pTagData);
}

void CEditElement::InternalPrintOpen( CPrintState *pPrintState, char* pTagData ){
    if( !(BitSet( edt_setCharFormat, GetType())
        || BitSet( edt_setSuppressNewlineBefore, GetType())) ){
        pPrintState->m_pOut->Write( "\n", 1 );
        pPrintState->m_iCharPos = 0;
    }

    if( pTagData && *pTagData != '>' ){
        char *pStr = pTagData;
        while( *pStr == ' ' ) pStr++;
        // Trim trailing white-space in-place
        {
            intn len = XP_STRLEN(pStr);
            while ( len > 1 && pStr[len-2] == ' ') {
                len--;
            }
            if ( len > 1 ) {
                pStr[len-1] = '>';
                pStr[len] = '\0';
            }
        }
            
        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s %s", 
                EDT_TagString(GetType()),pStr);
    }
    else {
        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s>", 
                EDT_TagString(GetType()) );
    }

    if ( BitSet( edt_setRequireNewlineAfter, GetType()) ){
        pPrintState->m_pOut->Write( "\n", 1 );
        pPrintState->m_iCharPos = 0;
    }
}

void CEditElement::PrintEnd( CPrintState *pPrintState ){
    if( TagHasClose( GetType() ) || BitSet( edt_setWriteEndTag,  GetType()  ) ){
        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "</%s>", 
                EDT_TagString(GetType()) );
        if( !BitSet( edt_setCharFormat, GetType() ) ){
            pPrintState->m_pOut->Write( "\n", 1 );
            pPrintState->m_iCharPos = 0;
        }
    }
}


//-----------------------------------------------------------------------------
// Insertion routines
//-----------------------------------------------------------------------------
CEditElement* CEditElement::InsertAfter( CEditElement *pPrev ){
    XP_ASSERT(m_pParent == NULL);
    m_pParent = pPrev->GetParent();
    SetNextSibling(pPrev->GetNextSibling());
    pPrev->SetNextSibling(this);
    return m_pParent;
}

CEditElement* CEditElement::InsertBefore( CEditElement *pNext ){
    XP_ASSERT(m_pParent == NULL);
    XP_ASSERT(pNext != NULL);
    CEditElement *pPrev = pNext->GetPreviousSibling();
    if( pPrev == 0 ){
        InsertAsFirstChild( pNext->GetParent() );
    }
    else {
        m_pParent = pNext->GetParent();
        SetNextSibling(pPrev->m_pNext);
        pPrev->SetNextSibling(this);
    }
    return m_pParent;
}


void CEditElement::InsertAsFirstChild( CEditElement *pParent ){
    XP_ASSERT(m_pParent == NULL);
    m_pParent = pParent;
    SetNextSibling(pParent->GetChild());
    pParent->SetChild(this);
}

void CEditElement::InsertAsLastChild( CEditElement *pParent ){
    XP_ASSERT(m_pParent == NULL);
    m_pParent = pParent;
    SetNextSibling( 0 );
    CEditElement *pPrev = pParent->GetLastChild();
    if( pPrev == 0 ){
        pParent->SetChild(this);
    }
    else {
        pPrev->SetNextSibling(this);
    }
}



CEditElement* CEditElement::Split( CEditElement *pLastChild, 
            CEditElement* pCloneTree,
            PMF_EditElementTest pmf,
            void *pData ){

    CEditElement *pClone = Clone();
    pClone->SetChild(pCloneTree);

    if( pLastChild->m_pNext ){
        if( pCloneTree != 0 ){
            pCloneTree->SetNextSibling(pLastChild->m_pNext);
        }
        else {
            pClone->SetChild(pLastChild->m_pNext);
        }
        pLastChild->SetNextSibling( 0 );    
    }

    //
    // Reparent all the children that have been moved to the clone.
    //
    CEditElement* pNext = pClone->m_pChild;
    while( pNext ){
        pNext->m_pParent = pClone;
        pNext = pNext->GetNextSibling();
    }


    //
    // If we are at the container point
    //
    if( TestIsTrue( pmf, pData ) ){
        return pClone->InsertAfter( this );
    }
    else {
        return GetParent()->Split( this, pClone, pmf, pData );
    }

}

CEditElement* CEditElement::Clone( CEditElement *pParent ){
    return new CEditElement(pParent, GetType(), GetTagData());
}


//
// Copied formatting, returns the bottom of the chain (Child most formatting
//  element)
//
CEditElement* CEditElement::CloneFormatting( TagType endType ){
    if( GetType() == endType ){
        return 0;
    }
    else {
        return Clone( GetParent()->CloneFormatting(endType) );
    }
}

XP_Bool CEditElement::IsFirstInContainer(){
    CEditElement *pPrevious, *pContainer, *pPreviousContainer;

    pPrevious = PreviousLeaf();
    if( pPrevious ){
        pContainer = FindContainer();
        pPreviousContainer = pPrevious->FindContainer();
        if( pContainer != pPreviousContainer ){
            return TRUE;       
        }
        else {
            return FALSE;
        }
    }
    else {
        return TRUE;
    }
}

CEditTextElement* CEditElement::PreviousTextInContainer(){
    CEditElement *pPrevious, *pContainer, *pPreviousContainer;
    pPrevious = FindPreviousElement( &CEditElement::FindText, 0 );
    if( pPrevious ){
        pContainer = FindContainer();
        pPreviousContainer = pPrevious->FindContainer();
        if( pContainer != pPreviousContainer ){
            return 0;
        }
        else {
            return pPrevious->Text();
        }
    }
    else {
        return 0;
    }
}


CEditTextElement* CEditElement::TextInContainerAfter(){
    CEditElement *pNext, *pContainer, *pNextContainer;

    pNext = FindNextElement( &CEditElement::FindText, 0 );
    if( pNext ){
        pContainer = FindContainer();
        pNextContainer = pNext->FindContainer();
        if( pContainer != pNextContainer ){
            return 0;       
        }
        else {
            return pNext->Text();
        }
    }
    else {
        return 0;
    }
}

CEditLeafElement* CEditElement::PreviousLeafInContainer(){
    CEditElement *pPrevious, *pContainer, *pPreviousContainer;
    pPrevious = PreviousLeaf();
    if( pPrevious ){
        pContainer = FindContainer();
        pPreviousContainer = pPrevious->FindContainer();
        if( pContainer != pPreviousContainer ){
            return 0;
        }
        else {
            return pPrevious->Leaf();
        }
    }
    else {
        return 0;
    }
}


CEditLeafElement* CEditElement::LeafInContainerAfter(){
    CEditElement *pNext, *pContainer, *pNextContainer;

    pNext = NextLeaf();
    if( pNext ){
        pContainer = FindContainer();
        pNextContainer = pNext->FindContainer();
        if( pContainer != pNextContainer ){
            return 0;       
        }
        else {
            return pNext->Leaf();
        }
    }
    else {
        return 0;
    }
}

CEditElement* CEditElement::GetRoot(){
    CEditElement* pRoot = this;
    while ( pRoot ) {
        CEditElement* pParent = pRoot->GetParent();
        if ( ! pParent ) break;
        pRoot = pParent;
    }
    return pRoot;
}

CEditElement* CEditElement::GetCommonAncestor(CEditElement* pOther){
    if ( this == pOther ) return this;

    // Start at root, and trickle down to find where they diverge

    CEditElement* pRoot = GetRoot();
    CEditElement* pRootOther = pOther->GetRoot();
    if ( pRoot != pRootOther ) return NULL;

    CEditElement* pCommon = pRoot;

    while ( pCommon ){
        CEditElement* pAncestor = pCommon->GetChildContaining(this);
        CEditElement* pAncestorOther = pCommon->GetChildContaining(pOther);
        if ( pAncestor != pAncestorOther ) break;
        pCommon = pAncestor;
    }
    return pCommon;
}

CEditElement* CEditElement::GetChildContaining(CEditElement* pDescendant){
    CEditElement* pParent = pDescendant;
    while ( pParent ) {
        CEditElement* pTemp = pParent->GetParent();
        if ( pTemp == this ) break; // Our direct child
        pParent = pTemp;
    }
    return pParent;
}

//
// Unlink from parent, but keep children
//
void CEditElement::Unlink(){
    CEditElement* pParent = GetParent();
    if( pParent ){
        CEditElement *pPrev = GetPreviousSibling();
        if( pPrev ){
            pPrev->SetNextSibling( GetNextSibling() );
        }
        else {
            pParent->SetChild(GetNextSibling());
        }
        m_pParent = 0;
        SetNextSibling( 0 );
    }

}

// LTNOTE: 01/01/96
//  We take take the characteristics of the thing that follows this paragraph
//  instead of keeping this paragraphs characteristics.
//
// jhp -- paste takes characteristic of second, but backspace takes
// characteristic of first, so we need a flag to control it.
// bCopyAppendAttributes == TRUE for the second (cut/paste)
// bCopyAppendAttributes == FALSE for the first (backspace/delete)

void CEditElement::Merge( CEditElement *pAppendBlock, XP_Bool bCopyAppendAttributes ){
    CEditElement* pChild = GetLastChild();
    CEditElement* pAppendChild = pAppendBlock->GetChild();

    // LTNOTE: 01/01/96 - I don't think this should be happening anymore.
    //   The way we deal with leaves and containers should keep this from 
    //   occuring...
    //
    // Check to see if pAppendBlock is a child of this.
    //    
    // LTNOTE: this is a case where we have
    // UL: 
    //   text: some text 
    //   LI: 
    //      text: XXX some More text
    //   LI:
    //      text: and some more.
    //
    // Merge occurs before xxx
    //  
    CEditElement *pAppendParent = pAppendBlock->GetParent();
    CEditElement *pAppendBlock2 = pAppendBlock;
    XP_Bool bDone = FALSE;
    while( !bDone && pAppendParent ){
        if( pAppendParent == this ){
            pChild = pAppendBlock2->GetPreviousSibling();            
            bDone = TRUE;
        }
        else {
            pAppendBlock2 = pAppendParent;
            pAppendParent = pAppendParent->GetParent();
        }
    }

    pAppendBlock->Unlink();

    if( pChild == 0 ){
        m_pChild = pAppendChild;
    }
    else {
        CEditElement *pOldNext = pChild->m_pNext;
        pChild->SetNextSibling( pAppendChild );

        // kind of sleazy.  we haven't updated the link, GetLastChild will
        //  return the end of pAppendBlock's children.
        GetLastChild()->SetNextSibling( pOldNext );
    }
    pAppendBlock->m_pChild = 0;

    while( pAppendChild ){
        pAppendChild->m_pParent = this;
        pAppendChild = pAppendChild->GetNextSibling();
    }

    //
    // LTNOTE: whack the container type to reflect the thing we just pulled 
    //  up. 
    //
    //XP_ASSERT( IsContainer() && pAppendBlock->IsContainer() );
    if( bCopyAppendAttributes && IsContainer() && pAppendBlock->IsContainer() ){
        Container()->CopyAttributes( pAppendBlock->Container() );
    }

    delete pAppendBlock;
}


#if 0
void CEditElement::Merge( CEditElement *pAppendBlock ){
    CEditElement* pChild = GetLastChild();
    CEditElement* pAppendChild = pAppendBlock->GetChild();

    //
    // Check to see if pAppendBlock is a child of this.
    //    
    // LTNOTE: this is a case where we have
    // UL: 
    //   text: some text 
    //   LI: 
    //      text: XXX some More text
    //   LI:
    //      text: and some more.
    //
    // Merge occurs before xxx
    //  
    CEditElement *pAppendParent = pAppendBlock->GetParent();
    CEditElement *pAppendBlock2 = pAppendBlock;
    XP_Bool bDone = FALSE;
    while( !bDone && pAppendParent ){
        if( pAppendParent == this ){
            pChild = pAppendBlock2->GetPreviousSibling();            
            bDone = TRUE;
        }
        else {
            pAppendBlock2 = pAppendParent;
            pAppendParent = pAppendParent->GetParent();
        }
    }

    pAppendBlock->Unlink();

    if( pChild == 0 ){
        m_pChild = pAppendChild;
    }
    else {
        CEditElement *pOldNext = pChild->m_pNext;
        pChild->m_pNext = pAppendChild;

        // kind of sleazy.  we haven't updated the link, GetLastChild will
        //  return the end of pAppendBlock's children.
        GetLastChild()->m_pNext = pOldNext;
    }
    pAppendBlock->m_pChild = 0;

    while( pAppendChild ){
        pAppendChild->m_pParent = this;
        pAppendChild = pAppendChild->GetNextSibling();
    }
    // delete pAppendBlock;
}
#endif

// By default, yes for any non-atomic tag
XP_Bool CEditElement::IsContainerContainer(){
    return !BitSet( edt_setNoEndTag, GetType());
}

//
// Search up the tree for the element that contains us.
// - start with us.
CEditContainerElement* CEditElement::FindContainer(){
    CEditElement *pRet = this;
    while( pRet ){
        if ( pRet->IsSubDoc() && pRet != this ) break;
        if( pRet->IsContainer() ){
            return pRet->Container();
        }
        pRet = pRet->GetParent();
    }
    return 0;
}

//
// Search up the tree for the element that contains us.
// - skip us.

CEditContainerElement* CEditElement::FindEnclosingContainer(){
    CEditElement *pRet = this->GetParent();
    if ( pRet ){
        return pRet->FindContainer();
    }
    return 0;
}

#if 0
CEditElement* CEditElement::FindList(){
    CEditElement *pRet = this;
    while( pRet = pRet->GetParent() ){
        if( pRet->IsList() ){
            return pRet;
        }
    }
    return 0;
}
#endif


void CEditElement::FindList( CEditContainerElement*& pContainer, 
            CEditListElement*& pList ) {
    
    pList = 0;
    XP_Bool bDone = FALSE;
    pContainer = FindEnclosingContainer();
    while( !bDone ){
        if( pContainer->GetParent()->IsList() ){
            pList = pContainer->GetParent()->List();
            bDone = TRUE;
        }
        else {
            CEditElement *pParentContainer = pContainer->FindEnclosingContainer();
            if( pParentContainer ){
                pContainer = pParentContainer->Container();
            }
            else {
                bDone = TRUE;
            }
        }
    }
}


CEditElement* CEditElement::FindContainerContainer(){
    CEditElement *pRet = this;
    while( pRet ){
        // Don't need explicit subdoc test because
        // sub-docs are paragraph containers.
        if ( pRet->IsContainerContainer() ){
            return pRet;
        }
        pRet = pRet->GetParent();
    }
    return 0;
}


#ifdef DEBUG
void CEditElement::ValidateTree(){
    CEditElement* pChild;
    CEditElement* pLoopFinder;
    // Make sure that all of our children point to us.
    // Makes sure all children are valid.
    // Make sure we don't have an infinite loop of children.
    // Use a second pointer that goes
    // around the loop twice as fast -- if it ever catches up with
    // pChild, there's a loop. (And yes, since you're wondering,
    // we did run into infinite loops here...)
    pChild = GetChild();
    pLoopFinder = pChild;
    while( pChild ){
        if( pChild->GetParent() != this ){
            XP_ASSERT(FALSE);
        }
        XP_ASSERT(IsAcceptableChild(*pChild));
        pChild->ValidateTree();
        for ( int i = 0; i < 2; i++ ){
            if ( pLoopFinder ) {
                pLoopFinder = pLoopFinder->GetNextSibling();
                if (pLoopFinder == pChild) {
                    XP_ASSERT(FALSE);
                    return;
                }
            }
        }
        pChild = pChild->GetNextSibling();
    }
}
#endif

CEditElement* CEditElement::Divide(int /* iOffset */){
    return this;
}

XP_Bool CEditElement::DeleteElement(CEditElement* pTellIfKilled){
    CEditElement *pKill = this;
    CEditElement *pParent;
    XP_Bool bKilled = FALSE;

    do {
        pParent = pKill->GetParent();
        pKill->Unlink();
        if( pKill == pTellIfKilled ){
            bKilled = TRUE;
        }
        delete pKill;
        pKill = pParent;
    } while( pKill->GetChild() == 0 );
    return bKilled;
}

void CEditElement::DeleteChildren(){
    CEditElement* pNext;
    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pNext ) {
        pNext = pChild->GetNextSibling();
        pChild->Unlink();
        pChild->DeleteChildren();
        delete pChild;
    }
}

// class CEditSubDocElement

CEditSubDocElement::CEditSubDocElement(CEditElement *pParent, int tagType, char* pData)
    : CEditElement(pParent, tagType, pData)
{
}

CEditSubDocElement::CEditSubDocElement(CEditElement *pParent, PA_Tag *pTag)
    : CEditElement(pParent, pTag)
{
}

CEditSubDocElement::CEditSubDocElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer)
    : CEditElement(pStreamIn, pBuffer)
{
}

void CEditSubDocElement::FinishedLoad( CEditBuffer* pBuffer ){
    CEditElement* pChild = GetChild();
    if ( ! pChild ) {
        // Subdocs have to have children.
        // Put an empty paragraph into any empty subdoc.
        pChild = CEditContainerElement::NewDefaultContainer( this, 
                        GetDefaultAlignment() );
        new CEditTextElement(pChild, 0);
    }
    // Put any leaves into a container.
    CEditContainerElement* pContainer = NULL;
    CEditElement* pNext;
    for ( ;
        pChild;
        pChild = pNext) {
        pNext = pChild->GetNextSibling(); // We might unlink pChild
        if ( ! IsAcceptableChild(*pChild) ){
            if ( pChild->IsLeaf() ){
                if ( ! pContainer ){
                    pContainer = CEditContainerElement::NewDefaultContainer(NULL, GetDefaultAlignment());
                    pContainer->InsertAfter(pChild);
                }
                pChild->Unlink();
                pChild->InsertAsLastChild(pContainer);
            }
            else {
                XP_TRACE(("CEditSubDocElement: deleteing child of unknown type %d.\n",
                    pChild->GetElementType()));
                delete pChild;
                pChild = NULL;
            }
        }
        else {
            if ( pContainer ){
                pContainer->FinishedLoad(pBuffer);
                pContainer = NULL;
            }
        }
        if ( pChild ) {
            pChild->FinishedLoad(pBuffer);
        }
    }
    if ( pContainer ){
        pContainer->FinishedLoad(pBuffer);
        pContainer = NULL;
    }
}

XP_Bool CEditSubDocElement::Reduce( CEditBuffer* /* pBuffer */ ){
    return FALSE;
}

// class CEditTableElement

CEditTableElement::CEditTableElement(intn columns, intn rows)
    : CEditElement(0, P_TABLE, 0),
      m_backgroundColor(),
      m_align(ED_ALIGN_LEFT),
      m_malign(ED_ALIGN_DEFAULT)
{
    EDT_TRY {
        for (intn row = 0; row < rows; row++ ){
            CEditTableRowElement* pRow = new CEditTableRowElement(columns);
            pRow->InsertAsFirstChild(this);
        }
    }
    EDT_CATCH_ALL {
        Finalize();
        EDT_RETHROW;
    }
}

CEditTableElement::CEditTableElement(CEditElement *pParent, PA_Tag *pTag, ED_Alignment align)
    : CEditElement(pParent, P_TABLE),
      m_backgroundColor(),
      m_align(align),
      m_malign(ED_ALIGN_DEFAULT)
{
    switch( m_align ) {
    case ED_ALIGN_CENTER:
        /* OK case -- 'center' tag. */
        m_align = ED_ALIGN_ABSCENTER;
        break;
    case ED_ALIGN_LEFT:
    case ED_ALIGN_ABSCENTER:
    case ED_ALIGN_RIGHT:
        break;
    default:
        XP_ASSERT(FALSE);
        /* Falls through */
    case ED_ALIGN_DEFAULT:
        m_align = ED_ALIGN_LEFT;
        break;
    }
    if( pTag ){
        EDT_TableData *pData = ParseParams(pTag);
        pData->align = m_align;
        SetData(pData);
        FreeData(pData);
    }
}

CEditTableElement::CEditTableElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer)
    : CEditElement(pStreamIn, pBuffer),
      m_backgroundColor()
{
    m_align = (ED_Alignment) pStreamIn->ReadInt();
    m_malign = (ED_Alignment) pStreamIn->ReadInt();
}

PA_Tag* CEditTableElement::TagOpen( int iEditOffset ){
    return InternalTagOpen(iEditOffset, FALSE);
}

PA_Tag* CEditTableElement::InternalTagOpen( int iEditOffset, XP_Bool bForPrinting){
    PA_Tag *pRet = 0;
    PA_Tag* pTag;

    // create the DIV tag if we need to.
    if( m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ){
        pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        if( m_align== ED_ALIGN_RIGHT ){
            SetTagData( pTag, "ALIGN=right>");
            pTag->type = P_DIVISION;
        }
        else {
            SetTagData( pTag, ">");
            pTag->type = P_CENTER;
        }
        pRet = pTag;
    }
    // create the actual table tag

    EDT_TableData* pTableData = GetData();
    char* pTagData = CreateTagData(pTableData, bForPrinting);
    if ( pTagData ) {
        pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        SetTagData( pTag, pTagData );
        XP_FREE(pTagData);
    }
    else {
        pTag = CEditElement::TagOpen( iEditOffset );
    }
    FreeData(pTableData);

    // link the tags together.
    if( pRet == 0 ){
        pRet = pTag;
    }
    else {
        pRet->next = pTag;
    }
    return pRet;
}

PA_Tag* CEditTableElement::TagEnd( ){
    PA_Tag *pRet = CEditElement::TagEnd();
    if( m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ){
        PA_Tag* pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        pTag->is_end = TRUE;
        if( m_align == ED_ALIGN_RIGHT ){
            pTag->type = P_DIVISION;
        }
        else {
            pTag->type = P_CENTER;
        }

        if( pRet == 0 ){
            pRet = pTag;
        }
        else {
            pRet->next = pTag;
        }
    }
    return pRet;
}

intn CEditTableElement::GetRows(){
    intn rows = 0;
    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pChild->GetNextSibling()) {
        if ( pChild->IsTableRow() ) {
            rows++;
        }
    }
    return rows;
}

intn CEditTableElement::GetColumns(){
    intn maxCols = 0;
    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pChild->GetNextSibling()) {
        if ( pChild->IsTableRow() ){
            intn cols = pChild->TableRow()->GetCells();
            if (cols > maxCols) {
                maxCols = cols;
            }
        }
    }
    return maxCols;
}

void CEditTableElement::InsertRows(intn iPosition, intn number, CEditTableElement* pSource){
    if ( number == 0 ) {
        return; /* A waste of time, but legal. */
    }
    intn rows = GetRows();
    intn cols = GetColumns();
    if ( number < 0 || iPosition < 0 || iPosition > rows){
        /* Illegal. */
        XP_ASSERT(FALSE);
        return;
    }
    if ( iPosition == 0 ) {
        // Insert at the start.
        for ( intn row = 0; row < number; row++ ){
            CEditTableRowElement* pRow;
            if ( pSource ) {
                pRow = pSource->FindRow(number - row - 1);
                pRow->Unlink();
            }
            else {
                pRow = new CEditTableRowElement(cols);
            }
            pRow->InsertAsFirstChild(this);
            AdjustCaption();
        }
    }
    else {
        CEditTableRowElement* pExisting = FindRow(iPosition-1);
        if ( ! pExisting ) {
            XP_ASSERT(FALSE);
            return;
        }
        for ( intn row = 0; row < number; row++ ){
            CEditTableRowElement* pRow;
            if ( pSource ) {
                pRow = pSource->FindRow(0);
                pRow->Unlink();
            }
            else {
                pRow = new CEditTableRowElement(cols);
            }
            pRow->InsertAfter(pExisting);
        }
    }
}

void CEditTableElement::InsertColumns(intn iPosition, intn number, CEditTableElement* pSource){
    if ( number == 0 ) {
        return; /* A waste of time, but legal. */
    }
    intn rows = GetRows();
    intn cols = GetColumns();
    if ( number < 0 || iPosition < 0 || iPosition > cols){
        /* Illegal. */
        XP_ASSERT(FALSE);
        return;
    }
    for(intn row = 0; row < rows; row++ ) {
        CEditTableRowElement* pExisting = FindRow(row);
        if ( ! pExisting ) {
            XP_ASSERT(FALSE);
            return;
        }
        CEditTableRowElement* pSourceRow = pSource ? pSource->FindRow(0) : NULL;
        pExisting->InsertCells(iPosition, number, pSourceRow);
        delete pSourceRow;
    }
}

void CEditTableElement::DeleteRows(intn iPosition, intn number, CEditTableElement* pUndoContainer){
    if ( number == 0 ) {
        return; /* A waste of time, but legal. */
    }
    intn rows = GetRows();
    if ( number < 0 || iPosition < 0 || iPosition >= rows
        || iPosition + number > rows){
        /* Illegal. */
        XP_ASSERT(FALSE);
        return;
    }
    for ( intn row = iPosition + number - 1; row >= iPosition; row-- ) {
        CEditTableRowElement* pExisting = FindRow(row);
        if ( ! pExisting ) {
            XP_ASSERT(FALSE);
            return;
        }
        if ( pUndoContainer ) {
            pExisting->Unlink();
            pExisting->InsertAsFirstChild(pUndoContainer);
        }
        else {
            delete pExisting;
        }
    }
}

void CEditTableElement::DeleteColumns(intn iPosition, intn number, CEditTableElement* pUndoContainer){
    if ( number == 0 ) {
        return; /* A waste of time, but legal. */
    }
    intn rows = GetRows();
    intn cols = GetColumns();
    if ( number < 0 || iPosition < 0 || iPosition >= cols
        || iPosition + number > cols){
        /* Illegal. */
        XP_ASSERT(FALSE);
        return;
    }
    for ( intn row = 0; row < rows; row++ ) {
        CEditTableRowElement* pExisting = FindRow(row);
        if ( ! pExisting ) {
            XP_ASSERT(FALSE);
            return;
        }
        CEditTableRowElement* pUndoRow = NULL;
        if ( pUndoContainer ) {
            pUndoRow = new CEditTableRowElement();
            pUndoRow->InsertAsLastChild(pUndoContainer);
        }
        pExisting->DeleteCells(iPosition, number, pUndoRow);
    }
}

CEditTableRowElement* CEditTableElement::FindRow(intn number){
    intn count = 0;
    if ( number < 0 ) {
        XP_ASSERT(FALSE);
        return NULL;
    }
    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pChild->GetNextSibling()) {
        if ( pChild->IsTableRow() ) {
            if ( count++ == number ) {
                return (CEditTableRowElement*) pChild;
            }
        }
    }
    return NULL;
}

CEditTableCellElement* CEditTableElement::FindCell(intn column, intn row) {
    CEditTableRowElement* pRow = FindRow(row);
    if ( pRow ) {
        return pRow->FindCell(column);
    }
    return NULL;
}

CEditCaptionElement* CEditTableElement::GetCaption(){
    // Normally first or last, but we check everybody to be robust.
    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pChild->GetNextSibling()) {
        if ( pChild->IsCaption() ) {
            return (CEditCaptionElement*) pChild;
        }
    }
    return NULL;
}

void CEditTableElement::SetCaption(CEditCaptionElement* pCaption){
    DeleteCaption();
    if ( pCaption ){
        EDT_TableCaptionData* pData = pCaption->GetData();
        XP_Bool bAddAtTop = ! ( pData->align == ED_ALIGN_BOTTOM || pData->align == ED_ALIGN_ABSBOTTOM);
        CEditCaptionElement::FreeData(pData);
        if ( bAddAtTop ){
            pCaption->InsertAsFirstChild(this);
        }
        else {
            pCaption->InsertAsLastChild(this);
        }
    }
}

void CEditTableElement::DeleteCaption(){
    CEditCaptionElement* pOldCaption = GetCaption();
    delete pOldCaption;
}

void CEditTableElement::FinishedLoad( CEditBuffer* pBuffer ){
    // From experimentation, we know that the 2.0 navigator dumps
    // tags that aren't in td, tr, or caption cells into the doc
    // before the table.
    //
    // That's a little too radical for me. So what I do is
    // wrap tds in trs, put anything else
    // into a caption

    CEditCaptionElement* pCaption = NULL;
    CEditTableRowElement* pRow = NULL;
    CEditElement* pNext;
    
    if ( GetRows() <= 0 ) {
        // Force a row
        CEditTableRowElement* pTempRow = new CEditTableRowElement();
        pTempRow->InsertAsFirstChild(this);
    }
    
	CEditElement* pChild;
    for ( pChild = GetChild();
        pChild;
        pChild = pNext) {
        pNext = pChild->GetNextSibling(); // We might unlink pChild
        if ( ! IsAcceptableChild(*pChild) ){
            if ( pChild->IsTableCell() ){
                if ( ! pRow ){
                    pRow = new CEditTableRowElement();
                    pRow->InsertAfter(pChild);
                }
                pChild->Unlink();
                pChild->InsertAsLastChild(pRow);
                if ( pCaption ){
                    pCaption->FinishedLoad(pBuffer);
                    pCaption = NULL;
                }
            }
            else {
                // Put random children into a caption
                if ( ! pCaption ){
                    pCaption = new CEditCaptionElement();
                    pCaption->InsertAfter(pChild);
                }
                pChild->Unlink();
                pChild->InsertAsLastChild(pCaption);
                if ( pRow ){
                    pRow->FinishedLoad(pBuffer);
                    pRow = NULL;
                }
            }
        }
        else {
            if ( pRow ){
                pRow->FinishedLoad(pBuffer);
                pRow = NULL;
            }
            if ( pCaption ){
                pCaption->FinishedLoad(pBuffer);
                pCaption = NULL;
            }
        }
        pChild->FinishedLoad(pBuffer);
    }
    if ( pRow ){
        pRow->FinishedLoad(pBuffer);
        pRow = NULL;
    }
    if ( pCaption ){
        pCaption->FinishedLoad(pBuffer);
        pCaption = NULL;
    }

    // Merge all captions together.
    pCaption = NULL;
    for ( pChild = GetChild();
        pChild;
        pChild = pNext) {
        pNext = pChild->GetNextSibling(); // We might unlink pChild
        if ( pChild->IsCaption() ){
            if ( pCaption ) {
                // This is an extra caption. Merge its contents into the other caption.
                pChild->Unlink();
                CEditElement* pItem;
                while ( ( pItem = pChild->GetChild() ) != NULL ){
                    pItem->Unlink();
                    pItem->InsertAsLastChild(pCaption);
                }
                delete pChild;
            }
            else {
                pCaption = (CEditCaptionElement*) pChild;
            }
        }
    }
    AdjustCaption();

    EnsureSelectableSiblings(pBuffer);
}

void CEditTableElement::AdjustCaption() {
    CEditCaptionElement* pCaption = GetCaption();
    if ( pCaption ) {
        // Now move the caption to the start or the end of the table, as appropriate.
        pCaption->Unlink();
        SetCaption(pCaption);
    }
}

void CEditTableElement::StreamOut( IStreamOut *pOut){
    CEditElement::StreamOut( pOut );
    pOut->WriteInt( (int32)m_align );
    pOut->WriteInt( (int32)m_malign );
}

void CEditTableElement::SetData( EDT_TableData *pData ){
    char *pNew = CreateTagData(pData, FALSE);
    CEditElement::SetTagData( pNew );
    m_align = pData->align;
    m_malign = pData->malign; // Keep track of alignment seperately to avoid crashing when editing.
    if ( pNew ) {
        free(pNew); // XP_FREE?
    }
}

char* CEditTableElement::CreateTagData(EDT_TableData *pData, XP_Bool bPrinting) {
    char *pNew = 0;
    m_align = pData->align;
    m_malign = pData->malign;
    if( bPrinting && pData->malign != ED_ALIGN_DEFAULT ){
        pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->malign) );
    }
    if ( pData->iBorderWidth ){
        pNew = PR_sprintf_append( pNew, "BORDER=%d ", pData->iBorderWidth );
    }
    if ( pData->iCellSpacing != 1 ){
        pNew = PR_sprintf_append( pNew, "CELLSPACING=%d ", pData->iCellSpacing );
    }
    if ( pData->iCellPadding != 1 ){
        pNew = PR_sprintf_append( pNew, "CELLPADDING=%d ", pData->iCellPadding );
    }
    if( pData->bWidthDefined ){
        if( pData->bWidthPercent ){
            pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld%%\" ", 
                                      (long)min(100,max(1,pData->iWidth)) );
        }
        else {
            pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld\" ", (long)pData->iWidth );
        }
    }
    if( pData->bHeightDefined ){
        if( pData->bHeightPercent ){
            pNew = PR_sprintf_append( pNew, "HEIGHT=\"%ld%%\" ", 
                                      (long)min(100,max(1, pData->iHeight)) );
        }
        else {
            pNew = PR_sprintf_append( pNew, "HEIGHT=\"%ld\" ", (long)pData->iHeight );
        }
    }
    if ( pData->pColorBackground ) {
        SetBackgroundColor(EDT_LO_COLOR(pData->pColorBackground));
        pNew = PR_sprintf_append( pNew, "BGCOLOR=\"#%06lX\" ", GetBackgroundColor().GetAsLong() );
    }
    else {
        SetBackgroundColor(ED_Color::GetUndefined());
    }
    if( pData->pExtra  ){
        pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra );
    }

    if( pNew ){
        pNew = PR_sprintf_append( pNew, ">" );
    }
    return pNew;
}

EDT_TableData* CEditTableElement::GetData( ){
    EDT_TableData *pRet;
    PA_Tag* pTag = CEditElement::TagOpen(0);
    pRet = ParseParams( pTag );
    // Alignment isn't kept in the tag data, because tag data is used for editing,
    // and we crash if we try to edit an aligned table, because layout doesn't
    // set up the data structures that we need.
    pRet->align = m_align;
    pRet->malign = m_malign;
    PA_FreeTag( pTag );
    return pRet;
}

static char *tableParams[] = {
    PARAM_ALIGN,
    PARAM_BORDER,
    PARAM_CELLSPACE,
    PARAM_CELLPAD,
    PARAM_WIDTH,
    PARAM_HEIGHT,
    PARAM_BGCOLOR,
    0
};

EDT_TableData* CEditTableElement::ParseParams( PA_Tag *pTag ){
    EDT_TableData *pData = NewData();
    ED_Alignment malign;
    
    malign = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT );
    // Center not supported in 3.0
    if( malign == ED_ALIGN_RIGHT || malign == ED_ALIGN_LEFT || malign == ED_ALIGN_DEFAULT ){
        pData->malign = malign;
    }

    pData->iRows = GetRows();
    pData->iColumns = GetColumns();
    pData->iBorderWidth = edt_FetchParamInt(pTag, PARAM_BORDER, 0);
    pData->iCellSpacing = edt_FetchParamInt(pTag, PARAM_CELLSPACE, 1);
    pData->iCellPadding = edt_FetchParamInt(pTag, PARAM_CELLPAD, 1);
    pData->bWidthDefined = edt_FetchDimension( pTag, PARAM_WIDTH, 
                    &pData->iWidth, &pData->bWidthPercent,
                    100, TRUE );
    pData->bHeightDefined = edt_FetchDimension( pTag, PARAM_HEIGHT, 
                    &pData->iHeight, &pData->bHeightPercent,
                    100, TRUE );
    pData->pColorBackground = edt_MakeLoColor(edt_FetchParamColor( pTag, PARAM_BGCOLOR ));
    pData->pExtra = edt_FetchParamExtras( pTag, tableParams );

    return pData;
}

EDT_TableData* CEditTableElement::NewData(){
    EDT_TableData *pData = XP_NEW( EDT_TableData );
    if( pData == 0 ){
        // throw();
        return pData;
    }
    pData->iRows = 0;
    pData->iColumns = 0;
    pData->align = ED_ALIGN_LEFT;
    pData->malign = ED_ALIGN_DEFAULT;
    pData->iBorderWidth = 0;
    pData->iCellSpacing = 1;
    pData->iCellPadding = 1;
    pData->bWidthDefined = FALSE;
    pData->bWidthPercent = TRUE;
    pData->iWidth = 100;
    pData->bHeightDefined = FALSE;
    pData->bHeightPercent = TRUE;
    pData->iHeight = 100;
    pData->pColorBackground = 0;
    pData->pExtra = 0;
    return pData;
}

void CEditTableElement::FreeData( EDT_TableData *pData ){
    if( pData->pColorBackground ) XP_FREE( pData->pColorBackground );
    if( pData->pExtra ) XP_FREE( pData->pExtra );
    XP_FREE( pData );
}

void CEditTableElement::SetBackgroundColor( ED_Color iColor ){
    m_backgroundColor = iColor;
}

ED_Color CEditTableElement::GetBackgroundColor(){
    return m_backgroundColor;
}

void CEditTableElement::PrintOpen( CPrintState *pPrintState ){
    pPrintState->m_iCharPos = 0;
    pPrintState->m_pOut->Printf( "\n"); 

    PA_Tag *pTag = InternalTagOpen(0, TRUE);
    while( pTag ){
        char *pData = 0;
        if( pTag->data ){
            PA_LOCK( pData, char*, pTag->data );
        }

        if( pData && *pData != '>' ) {
            pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s %s", 
                    EDT_TagString(pTag->type), pData );
        }
        else {
            pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s>", 
                    EDT_TagString(pTag->type));
        }

        if( pTag->data ){
            PA_UNLOCK( pTag->data );
        }

        PA_Tag* pDelTag = pTag;
        pTag = pTag->next;
        PA_FreeTag(pDelTag);
    }
}

void CEditTableElement::PrintEnd( CPrintState *pPrintState ){
    PA_Tag *pTag = TagEnd();
    while( pTag ){
        char *pData = 0;
        if( pTag->data ){
            PA_LOCK( pData, char*, pTag->data );
        }

        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "</%s>",
            EDT_TagString( pTag->type ) );

        if( pTag->data ){
            PA_UNLOCK( pTag->data );
        }

        PA_Tag* pDelTag = pTag;
        pTag = pTag->next;
        PA_FreeTag(pDelTag);
    }

    pPrintState->m_iCharPos = 0;
    pPrintState->m_pOut->Printf( "\n"); 
}

// class CEditTableRowElement

CEditTableRowElement::CEditTableRowElement()
    : CEditElement((CEditElement*) NULL, P_TABLE_ROW),
      m_backgroundColor()
{
}

CEditTableRowElement::CEditTableRowElement(intn columns)
    : CEditElement((CEditElement*) NULL, P_TABLE_ROW),
      m_backgroundColor()
{
    EDT_TRY {
        for ( intn column = 0; column < columns; column++ ) {
            CEditTableCellElement* pCell = new CEditTableCellElement();
            pCell->InsertAsFirstChild(this);
        }
    }
    EDT_CATCH_ALL {
        Finalize();
        EDT_RETHROW;
    }
}

CEditTableRowElement::CEditTableRowElement(CEditElement *pParent, PA_Tag *pTag)
    : CEditElement(pParent, P_TABLE_ROW),
      m_backgroundColor()
{
    if( pTag ){
        char *locked_buff;
            
        PA_LOCK(locked_buff, char *, pTag->data );
        if( locked_buff ){
            SetTagData( locked_buff );
        }
        PA_UNLOCK(pTag->data);
    }
}

CEditTableRowElement::CEditTableRowElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer)
    : CEditElement(pStreamIn, pBuffer),
      m_backgroundColor()
{
}

intn CEditTableRowElement::GetCells(){
    intn cells = 0;
    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pChild->GetNextSibling()) {
        if ( pChild->IsTableCell() ){
            cells++;
        }
    }
    return cells;
}

intn CEditTableRowElement::GetRowIndex(){
    CEditTableElement* pTable = GetTable();
    intn rowIndex = 0;
    if ( pTable ) {
        for ( CEditElement* pChild = pTable->GetChild();
            pChild;
            pChild = pChild->GetNextSibling()) {
            if ( pChild == this ){
                return rowIndex;
            }
            if ( pChild->IsTableRow() ){
                rowIndex++;
            }
        }
        XP_ASSERT(FALSE);
    }
    else {
        XP_ASSERT(FALSE);
    }
    return rowIndex;
}

void CEditTableRowElement::FinishedLoad( CEditBuffer* pBuffer ){
    // Wrap anything that's not a table cell in a table cell.
    CEditTableCellElement* pCell = NULL;
    CEditElement* pNext;
    
    if ( ! GetChild() ) {
        // Force a cell
        CEditTableCellElement* pTempCell = new CEditTableCellElement();
        pTempCell->InsertAsFirstChild(this);
    }

    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pNext) {
        pNext = pChild->GetNextSibling(); // We might unlink pChild
        if ( ! IsAcceptableChild(*pChild) ){
            if ( ! pCell ){
                pCell = new CEditTableCellElement();
                pCell->InsertAfter(pChild);
            }
            pChild->Unlink();
            pChild->InsertAsLastChild(pCell);
        }
        else {
            if ( pCell ){
                pCell->FinishedLoad(pBuffer);
                pCell = NULL;
            }
        }
        pChild->FinishedLoad(pBuffer);
    }
    if ( pCell ){
        pCell->FinishedLoad(pBuffer);
        pCell = NULL;
    }
}

void CEditTableRowElement::InsertCells(intn iPosition, intn number, CEditTableRowElement* pSource){
    if ( number == 0 ) {
        return; /* A waste of time, but legal. */
    }
    intn cells = GetCells();
    if ( number < 0 || iPosition < 0 || iPosition > cells){
        /* Illegal. */
        XP_ASSERT(FALSE);
        return;
    }
    if ( iPosition == 0 ) {
        // Insert at the start.
        for ( intn row = 0; row < number; row++ ){
            CEditTableCellElement* pCell;
            if ( pSource ) {
                pCell = pSource->FindCell(number - row - 1);
                pCell->Unlink();
            }
            else {
                pCell = new CEditTableCellElement();
            }
            pCell->InsertAsFirstChild(this);
        }
    }
    else {
        CEditTableCellElement* pExisting = FindCell(iPosition-1);
        if ( ! pExisting ) {
            XP_ASSERT(FALSE);
            return;
        }
        for ( intn row = 0; row < number; row++ ){
            CEditTableCellElement* pCell;
            if ( pSource ) {
                pCell = pSource->FindCell(0);
                pCell->Unlink();
            }
            else {
                pCell = new CEditTableCellElement();
            }
            pCell->InsertAfter(pExisting);
        }
    }
}

void CEditTableRowElement::DeleteCells(intn iPosition, intn number, CEditTableRowElement* pUndoContainer){
    if ( number == 0 ) {
        return; /* A waste of time, but legal. */
    }
    intn cells = GetCells();
    if ( number < 0 || iPosition < 0 || iPosition >= cells
        || iPosition + number > cells){
        /* Illegal. */
        XP_ASSERT(FALSE);
        return;
    }
    for ( intn cell = iPosition + number - 1; cell >= iPosition; cell-- ) {
        CEditTableCellElement* pExisting = FindCell(cell);
        if ( ! pExisting ) {
            XP_ASSERT(FALSE);
            return;
        }
        if ( pUndoContainer ) {
            pExisting->Unlink();
            pExisting->InsertAsFirstChild(pUndoContainer);
        }
        else {
            delete pExisting;
        }
    }
}

CEditTableCellElement* CEditTableRowElement::FindCell(intn number){
    intn count = 0;
    if ( number < 0 ) {
        XP_ASSERT(FALSE);
        return NULL;
    }
    for ( CEditElement* pChild = GetChild();
        pChild;
        pChild = pChild->GetNextSibling()) {
        if ( pChild->IsTableCell() ) {
            if ( count++ == number ) {
                return (CEditTableCellElement*) pChild;
            }
        }
    }
    return NULL;
}

void CEditTableRowElement::SetData( EDT_TableRowData *pData ){
    char *pNew = 0;
    if( pData->align != ED_ALIGN_DEFAULT ){
        pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) );
    }

    if( pData->valign != ED_ALIGN_DEFAULT ){
        pNew = PR_sprintf_append( pNew, "VALIGN=%s ", EDT_AlignString(pData->valign) );
    }

    if ( pData->pColorBackground ) {
        SetBackgroundColor(EDT_LO_COLOR(pData->pColorBackground));
        pNew = PR_sprintf_append( pNew, "BGCOLOR=\"#%06lX\" ", GetBackgroundColor().GetAsLong() );
    }
    else {
        SetBackgroundColor(ED_Color::GetUndefined());
    }

    if( pData->pExtra  ){
        pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra );
    }

    if( pNew ){
        pNew = PR_sprintf_append( pNew, ">" );
    }
    SetTagData( pNew );
    if ( pNew ) {
        free(pNew);
    }
}

EDT_TableRowData* CEditTableRowElement::GetData( ){
    EDT_TableRowData *pRet;
    PA_Tag* pTag = TagOpen(0);
    pRet = ParseParams( pTag );
    PA_FreeTag( pTag );
    return pRet;
}

static char *tableRowParams[] = {
    PARAM_ALIGN,
    PARAM_VALIGN,
    PARAM_BGCOLOR,
    0
};

EDT_TableRowData* CEditTableRowElement::ParseParams( PA_Tag *pTag ){
    EDT_TableRowData* pData = NewData();
    ED_Alignment align;
    
    align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT );
    if( align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT || align == ED_ALIGN_ABSCENTER ){
        pData->align = align;
    }
    align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT, TRUE );
    if( align == ED_ALIGN_ABSTOP || align == ED_ALIGN_ABSBOTTOM || align == ED_ALIGN_ABSCENTER
        || align == ED_ALIGN_BASELINE ){
        pData->valign = align;
    }
    pData->pColorBackground = edt_MakeLoColor(edt_FetchParamColor( pTag, PARAM_BGCOLOR ));
    pData->pExtra = edt_FetchParamExtras( pTag, tableRowParams );

    return pData;
}

EDT_TableRowData* CEditTableRowElement::NewData(){
    EDT_TableRowData* pData = XP_NEW( EDT_TableRowData );
    if( pData == 0 ){
        // throw();
        return pData;
    }
    pData->align = ED_ALIGN_DEFAULT;
    pData->valign = ED_ALIGN_DEFAULT;
    pData->pColorBackground = 0;
    pData->pExtra = 0;
    return pData;
}

void CEditTableRowElement::FreeData( EDT_TableRowData *pData ){
    if( pData->pColorBackground ) XP_FREE( pData->pColorBackground );
    if( pData->pExtra ) XP_FREE( pData->pExtra );
    XP_FREE( pData );
}

void CEditTableRowElement::SetBackgroundColor( ED_Color iColor ){
    m_backgroundColor = iColor;
}

ED_Color CEditTableRowElement::GetBackgroundColor(){
    return m_backgroundColor;
}

// class CEditCaptionElement

CEditCaptionElement::CEditCaptionElement()
    : CEditSubDocElement((CEditElement*) NULL, P_CAPTION)
{
}

CEditCaptionElement::CEditCaptionElement(CEditElement *pParent, PA_Tag *pTag)
    : CEditSubDocElement(pParent, P_CAPTION)
{
    if( pTag ){
        char *locked_buff;
            
        PA_LOCK(locked_buff, char *, pTag->data );
        if( locked_buff ){
            SetTagData( locked_buff );
        }
        PA_UNLOCK(pTag->data);
    }
}

CEditCaptionElement::CEditCaptionElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer)
    : CEditSubDocElement(pStreamIn, pBuffer)
{
}

void CEditCaptionElement::SetData( EDT_TableCaptionData *pData ){
    char *pNew = 0;
    if( pData->align == ED_ALIGN_ABSBOTTOM ){
        pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) );
    }
    if( pData->pExtra  ){
        pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra );
    }

    if( pNew ){
        pNew = PR_sprintf_append( pNew, ">" );
    }
    SetTagData( pNew );
    if ( pNew ) {
        free(pNew);
    }
}

EDT_TableCaptionData* CEditCaptionElement::GetData( ){
    EDT_TableCaptionData* pRet;
    PA_Tag* pTag = TagOpen(0);
    pRet = ParseParams( pTag );
    PA_FreeTag( pTag );
    return pRet;
}

static char *tableCaptionParams[] = {
    PARAM_ALIGN,
    0
};

EDT_TableCaptionData* CEditCaptionElement::ParseParams( PA_Tag *pTag ){
    EDT_TableCaptionData* pData = NewData();
    pData->align = edt_FetchParamAlignment( pTag, ED_ALIGN_ABSTOP );
    pData->pExtra = edt_FetchParamExtras( pTag, tableCaptionParams );
    return pData;
}

EDT_TableCaptionData* CEditCaptionElement::NewData(){
    EDT_TableCaptionData* pData = XP_NEW( EDT_TableCaptionData );
    if( pData == 0 ){
        // throw();
        return pData;
    }
    pData->align = ED_ALIGN_CENTER;
    pData->pExtra = 0;
    return pData;
}

void CEditCaptionElement::FreeData( EDT_TableCaptionData *pData ){
    if( pData->pExtra ) XP_FREE( pData->pExtra );
    XP_FREE( pData );
}

// class CEditTableCellElement

CEditTableCellElement::CEditTableCellElement()
    : CEditSubDocElement((CEditElement*) NULL, P_TABLE_DATA),
      m_backgroundColor()
{
}

CEditTableCellElement::CEditTableCellElement(XP_Bool bIsHeader)
    : CEditSubDocElement((CEditElement*) NULL, bIsHeader ? P_TABLE_HEADER : P_TABLE_DATA),
      m_backgroundColor()
{
}

CEditTableCellElement::CEditTableCellElement(CEditElement *pParent, PA_Tag *pTag)
    : CEditSubDocElement(pParent, pTag->type),
      m_backgroundColor()
{
    if( pTag ){
        char *locked_buff;
            
        PA_LOCK(locked_buff, char *, pTag->data );
        if( locked_buff ){
            SetTagData( locked_buff );
        }
        PA_UNLOCK(pTag->data);
    }
}

CEditTableCellElement::CEditTableCellElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer)
    : CEditSubDocElement(pStreamIn, pBuffer),
      m_backgroundColor()
{
}

XP_Bool CEditTableCellElement::IsTableCell(){
    return TRUE;
}

EEditElementType CEditTableCellElement::GetElementType(){
    return eTableCellElement;
}

ED_Alignment CEditTableCellElement::GetDefaultAlignment(){
    return ED_ALIGN_DEFAULT;
/*
    EDT_TableCellData* pData = GetData();
    ED_Alignment result = IsTableData() ? ED_ALIGN_LEFT : ED_ALIGN_ABSCENTER;
    if ( pData->align != ED_ALIGN_DEFAULT ) {
        result = pData->align;
    }
    FreeData(pData);
    return result;
*/
}

XP_Bool CEditTableCellElement::IsTableData(){
    return GetType() == P_TABLE_DATA;
}

intn CEditTableCellElement::GetRowIndex(){
    CEditTableRowElement* pTableRow = GetTableRow();
    if ( pTableRow ) {
        return pTableRow->GetRowIndex();
    }
    else {
        XP_ASSERT(FALSE);
    }
    return 0;
}

intn CEditTableCellElement::GetColumnIndex(){
    CEditTableRowElement* pTableRow = GetTableRow();
    intn cellIndex = 0;
    if ( pTableRow ) {
        for ( CEditElement* pChild = pTableRow->GetChild();
            pChild;
            pChild = pChild->GetNextSibling()) {
            if ( pChild == this ){
                return cellIndex;
            }
            if ( pChild->IsTableCell() ){
                cellIndex++;
            }
        }
        XP_ASSERT(FALSE);
    }
    else {
        XP_ASSERT(FALSE);
    }
    return cellIndex;
}


void CEditTableCellElement::SetData( EDT_TableCellData *pData ){
    // bHeader is actually stored as the tag data
    if ( pData->bHeader ) {
        SetType(P_TABLE_HEADER);
    }
    else {
        SetType(P_TABLE_DATA);
    }
    char *pNew = 0;
    if( pData->align != ED_ALIGN_DEFAULT ){
        pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) );
    }
    if( pData->valign != ED_ALIGN_DEFAULT ){
        pNew = PR_sprintf_append( pNew, "VALIGN=%s ", EDT_AlignString(pData->valign) );
    }
    if ( pData->iColSpan != 1 ){
        pNew = PR_sprintf_append( pNew, "COLSPAN=\"%d\" ", pData->iColSpan );
    }
    if ( pData->iRowSpan != 1 ){
        pNew = PR_sprintf_append( pNew, "ROWSPAN=\"%d\" ", pData->iRowSpan );
    }
    if ( pData->bNoWrap ){
        pNew = PR_sprintf_append( pNew, "NOWRAP " );
    }
    if( pData->bWidthDefined ){
        if( pData->bWidthPercent ){
            pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld%%\" ", 
                                      (long)min(100,max(1,pData->iWidth)) );
        }
        else {
            pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld\" ", (long)pData->iWidth );
        }
    }
    if( pData->bHeightDefined ){
        if( pData->bHeightPercent ){
            pNew = PR_sprintf_append( pNew, "HEIGHT=\"%ld%%\" ", 
                                      (long)min(100,max(1, pData->iHeight)) );
        }
        else {
            pNew = PR_sprintf_append( pNew, "HEIGHT=\"%ld\" ", (long)pData->iHeight );
        }
    }

    if ( pData->pColorBackground ) {
        SetBackgroundColor(EDT_LO_COLOR(pData->pColorBackground));
        pNew = PR_sprintf_append( pNew, "BGCOLOR=\"#%06lX\" ", GetBackgroundColor().GetAsLong() );
    }
    else {
        SetBackgroundColor(ED_Color::GetUndefined());
    }

    if( pData->pExtra  ){
        pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra );
    }

    if( pNew ){
        pNew = PR_sprintf_append( pNew, ">" );
    }
    SetTagData( pNew );
    if ( pNew ) {
        free(pNew);
    }
}

EDT_TableCellData* CEditTableCellElement::GetData( ){
    EDT_TableCellData *pRet;
    PA_Tag* pTag = TagOpen(0);
    pRet = ParseParams( pTag );
    PA_FreeTag( pTag );
    return pRet;
}

static char *tableCellParams[] = {
    PARAM_ALIGN,
    PARAM_VALIGN,
    PARAM_COLSPAN,
    PARAM_ROWSPAN,
    PARAM_NOWRAP,
    PARAM_WIDTH,
    PARAM_HEIGHT,
    PARAM_BGCOLOR,
    0
};

EDT_TableCellData* CEditTableCellElement::ParseParams( PA_Tag *pTag ){
    EDT_TableCellData *pData = NewData();
    
    ED_Alignment align;
    
    align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT );
    if( align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT || align == ED_ALIGN_ABSCENTER ){
        pData->align = align;
    }

    align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT, TRUE );
    if( align == ED_ALIGN_ABSTOP || align == ED_ALIGN_ABSBOTTOM || align == ED_ALIGN_ABSCENTER
        || align == ED_ALIGN_BASELINE ){
        pData->valign = align;
    }

    pData->iColSpan = edt_FetchParamInt(pTag, PARAM_COLSPAN, 1);
    pData->iRowSpan = edt_FetchParamInt(pTag, PARAM_ROWSPAN, 1);
    pData->bHeader = GetType() == P_TABLE_HEADER;
    pData->bNoWrap = edt_FetchParamBoolExist(pTag, PARAM_NOWRAP );
    pData->bWidthDefined = edt_FetchDimension( pTag, PARAM_WIDTH, 
                    &pData->iWidth, &pData->bWidthPercent,
                    100, TRUE );
    pData->bHeightDefined = edt_FetchDimension( pTag, PARAM_HEIGHT, 
                    &pData->iHeight, &pData->bHeightPercent,
                    100, TRUE );

    pData->pColorBackground = edt_MakeLoColor(edt_FetchParamColor( pTag, PARAM_BGCOLOR ));
    pData->pExtra = edt_FetchParamExtras( pTag, tableCellParams );

    return pData;
}

EDT_TableCellData* CEditTableCellElement::NewData(){
    EDT_TableCellData *pData = XP_NEW( EDT_TableCellData );
    if( pData == 0 ){
        // throw();
        return pData;
    }
    pData->align = ED_ALIGN_DEFAULT;
    pData->valign = ED_ALIGN_DEFAULT;
    pData->iColSpan = 1;
    pData->iRowSpan = 1;
    pData->bHeader = FALSE;
    pData->bNoWrap = FALSE;
    pData->bWidthDefined = FALSE;
    pData->bWidthPercent = TRUE;
    pData->iWidth = 100;
    pData->bHeightDefined = FALSE;
    pData->bHeightPercent = TRUE;
    pData->iHeight = 100;
    pData->pColorBackground = 0;
    pData->pExtra = 0;
    return pData;
}

void CEditTableCellElement::FreeData( EDT_TableCellData *pData ){
    if( pData->pColorBackground ) XP_FREE( pData->pColorBackground );
    if( pData->pExtra ) XP_FREE( pData->pExtra );
    XP_FREE( pData );
}

void CEditTableCellElement::SetBackgroundColor( ED_Color iColor ){
    m_backgroundColor = iColor;
}

ED_Color CEditTableCellElement::GetBackgroundColor(){
    return m_backgroundColor;
}

// class CEditMultiColumnElement

CEditMultiColumnElement::CEditMultiColumnElement(intn columns)
    : CEditElement(0, P_MULTICOLUMN, 0)
{
    EDT_MultiColumnData* pData = NewData();
    pData->iColumns = columns;
    SetData(pData);
    FreeData(pData);
}

CEditMultiColumnElement::CEditMultiColumnElement(CEditElement *pParent, PA_Tag *pTag)
    : CEditElement(pParent, P_MULTICOLUMN)
{
    if( pTag ){
        char *locked_buff;
            
        PA_LOCK(locked_buff, char *, pTag->data );
        if( locked_buff ){
            SetTagData( locked_buff );
        }
        PA_UNLOCK(pTag->data);
    }
}

CEditMultiColumnElement::CEditMultiColumnElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer)
    : CEditElement(pStreamIn, pBuffer)
{
}

void CEditMultiColumnElement::FinishedLoad( CEditBuffer* pBuffer ){
    EnsureSelectableSiblings(pBuffer);
    CEditElement::FinishedLoad(pBuffer);
}

void CEditMultiColumnElement::SetData( EDT_MultiColumnData *pData ){
    char *pNew = 0;
    if ( pData->iColumns > 1){
        pNew = PR_sprintf_append( pNew, "COLS=%d ", pData->iColumns );
    }
    if( pData->pExtra  ){
        pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra );
    }
    if( pNew ){
        pNew = PR_sprintf_append( pNew, ">" );
    }
    SetTagData( pNew );
    if ( pNew ) {
        free(pNew);
    }
}

EDT_MultiColumnData* CEditMultiColumnElement::GetData( ){
    EDT_MultiColumnData *pRet;
    PA_Tag* pTag = TagOpen(0);
    pRet = ParseParams( pTag );
    PA_FreeTag( pTag );
    return pRet;
}

static char *multiColumnParams[] = {
    PARAM_COLS,
    0
};

EDT_MultiColumnData* CEditMultiColumnElement::ParseParams( PA_Tag *pTag ){
    EDT_MultiColumnData *pData = NewData();
    
    pData->iColumns = edt_FetchParamInt(pTag, PARAM_COLS, 1);
    pData->pExtra = edt_FetchParamExtras( pTag, multiColumnParams );

    return pData;
}

EDT_MultiColumnData* CEditMultiColumnElement::NewData(){
    EDT_MultiColumnData *pData = XP_NEW( EDT_MultiColumnData );
    if( pData == 0 ){
        // throw();
        return pData;
    }
    pData->iColumns = 1;
    pData->pExtra = 0;
    return pData;
}

void CEditMultiColumnElement::FreeData( EDT_MultiColumnData *pData ){
    if( pData->pExtra ) XP_FREE( pData->pExtra );
    XP_FREE( pData );
}

//-----------------------------------------------------------------------------
// CEditRootDocElement
//-----------------------------------------------------------------------------
void CEditRootDocElement::PrintOpen( CPrintState *pPrintState ){
    m_pBuffer->PrintDocumentHead( pPrintState );
}

void CEditRootDocElement::PrintEnd( CPrintState *pPrintState ){
    m_pBuffer->PrintDocumentEnd( pPrintState );
}

void CEditRootDocElement::FinishedLoad( CEditBuffer *pBuffer ){
    CEditSubDocElement::FinishedLoad(pBuffer);
    if ( ! GetLastChild() || ! GetLastChild()->IsEndContainer() ) {
        CEditEndContainerElement* pEndContainer = new CEditEndContainerElement(NULL);
        CEditEndElement* pEnd = new CEditEndElement(pEndContainer);
        pEndContainer->InsertAsLastChild(this);
    }
}

#ifdef DEBUG
void CEditRootDocElement::ValidateTree(){
    CEditElement::ValidateTree();
    XP_ASSERT(GetParent() == NULL);
    XP_ASSERT(GetChild() != NULL);
    XP_ASSERT(GetLastMostChild()->GetElementType() == eEndElement);
}

#endif


//-----------------------------------------------------------------------------
// CEditLeafElement
//-----------------------------------------------------------------------------


XP_Bool CEditLeafElement::PrevPosition(ElementOffset iOffset, 
                CEditLeafElement*& pNew, ElementOffset& iNewOffset ){

    XP_Bool bPreviousElement = FALSE;
    XP_Bool bMoved = TRUE;

    iNewOffset = iOffset - 1;
    pNew = this;

    if( iNewOffset == 0 ){

        CEditElement *pPrev = PreviousLeafInContainer();
        if( pPrev ){
            if( pPrev->IsLeaf()){
                bPreviousElement = TRUE;
            }
        }
    }

    //
    // we need to set the position to the end of the previous element.
    //
    if( iNewOffset < 0 || bPreviousElement ){
        CEditLeafElement *pPrev = PreviousLeaf();
        if( pPrev ){
            if( pPrev->IsLeaf()){
                pNew = pPrev;
                iNewOffset = pNew->Leaf()->GetLen();
            }
        }
        else{
            // no previous element, we are at the beginning of the buffer.
            iNewOffset = iOffset;
            bMoved = FALSE;
        }
    }
    return bMoved;
}

XP_Bool CEditLeafElement::NextPosition(ElementOffset iOffset, 
                CEditLeafElement*& pNew, ElementOffset& iNewOffset ){
    LO_Element* pRetText;
    CEditLeafElement *pNext;
    int iRetPos;
    XP_Bool retVal = TRUE;

    pNew = this;
    iNewOffset = iOffset + 1;

    //
    // if the new offset is obviously greater, move past it.  If
    //  Make sure the layout engine actually layed out the element.
    //
    if( iNewOffset > Leaf()->GetLen() ||
        !Leaf()->GetLOElementAndOffset( iOffset, FALSE, pRetText, iRetPos ))
    {
        pNext = NextLeaf();
        if( pNext ){
            pNew = pNext;
            if( pNext->PreviousLeafInContainer() == 0 ){
                iNewOffset = 0;
            }
            else {
                iNewOffset = 1;
            }
        }
        else {
            iNewOffset = iOffset;
            retVal = FALSE;
        }
    }
    return retVal;
}

void CEditLeafElement::DisconnectLayoutElements(LO_Element* pElement){
    // Clear back pointer out of the edit elements.
    LO_Element* e = pElement;
    while ( e && e->lo_any.edit_element == this ){
        e->lo_any.edit_element = 0;
        while ( e && e->lo_any.edit_element == 0 ){
            e = e->lo_any.next;
        }
    }
}

void CEditLeafElement::SetLayoutElementHelper( intn desiredType, LO_Element** pElementHolder,
                                              intn iEditOffset, intn lo_type, 
                                              LO_Element* pLoElement){
    // XP_TRACE(("SetLayoutElementHelper this(0x%08x) iEditOffset(%d) pLoElement(0x%08x)", this, iEditOffset, pLoElement));

    if( lo_type == desiredType ){
        // iEditOffset is:
        // 0 for normal case,
        // -1 for end of document element.
        // > 0 for text items after a line wrap.
        //     Don't disconnect layout elements because this is
        //     called for each layout element of a wrapped text
        //     edit element. Since DisconnectLayoutElements goes
        //     ahead and zeros out all the "next" elements with
        //     the same back pointer, we end up trashing later
        //     elements if the elements are set in some order
        //     other than first-to-last. This happens when just the 2nd line of a two-line
        //     text element is being relaid out.
        if ( iEditOffset <= 0 ) {
            // Don't reset the old element, because functions like lo_BreakOldElement depend upon the
            // old element having a good value.
            *pElementHolder = pLoElement;
        }
        pLoElement->lo_any.edit_element = this;
        pLoElement->lo_any.edit_offset = iEditOffset;
    }
    else {
        // We get called back whenever linefeeds are generated. That's so that
        // breaks can find their linefeed. But it also means we are
        // called back more often than nescessary.
    //    XP_TRACE(("  ignoring inconsistent type.\n"));
    }
}

void CEditLeafElement::ResetLayoutElementHelper( LO_Element** pElementHolder, intn iEditOffset, 
            LO_Element* pLoElement ){
    // XP_TRACE(("ResetLayoutElementHelper this(0x%08x) iEditOffset(%d) pLoElement(0x%08x)", this, iEditOffset, pLoElement));
    if ( iEditOffset <= 0 ) {
        if ( *pElementHolder == pLoElement ){
            *pElementHolder = NULL;
        }
        else {
            XP_TRACE(("  Woops -- we're currently pointing to 0x%08x\n", *pElementHolder));
        }
    }
}

CEditElement* CEditLeafElement::Divide(int iOffset){
    CEditElement* pNext;
    if( iOffset == 0 ){
        return this;
    }

    pNext = LeafInContainerAfter();
    if( iOffset >= GetLen() ){
        if( pNext != 0 ){
            return pNext;
        }
        else {
            CEditTextElement* pPrev;
            CEditTextElement* pNew;
            if( IsA( P_TEXT )) {
                pPrev = this->Text();
            }
            else {
                pPrev = PreviousTextInContainer();
            }

            if( pPrev ){
                pNew = pPrev->CopyEmptyText();
            }
            else {
                pNew = new CEditTextElement((CEditElement*)0,0);
            }
            pNew->InsertAfter(this);
            return pNew;
        }
    }

    // offset is in the middle somewhere Must be a TEXT.
    XP_ASSERT( IsA(P_TEXT) );
    CEditTextElement* pNew = Text()->CopyEmptyText();
    pNew->SetText( Text()->GetText()+iOffset );
    Text()->GetText()[iOffset] = 0;
    pNew->InsertAfter(this);
    return pNew;
}

CEditTextElement* CEditLeafElement::CopyEmptyText(CEditElement *pParent){
       return new CEditTextElement(pParent,0);
}

CPersistentEditInsertPoint CEditLeafElement::GetPersistentInsertPoint(ElementOffset offset){
    ElementIndex len = GetPersistentCount();
    if ( offset > len )
    {
#ifdef DEBUG_EDIT_LAYOUT
        XP_ASSERT(FALSE);
#endif
        offset = len;
    }
    return CPersistentEditInsertPoint(GetElementIndex() + offset, offset == 0);
}

//
// Default implementation
//
PA_Tag* CEditLeafElement::TagOpen( int /* iEditOffset */ ){
    PA_Tag *pTag = XP_NEW( PA_Tag );
    XP_BZERO( pTag, sizeof( PA_Tag ) );
    if( GetTagData() ){
        SetTagData( pTag, GetTagData() );
    }
    else {
        SetTagData( pTag, ">" );
    }
    return pTag;
}

ElementIndex CEditLeafElement::GetPersistentCount()
{
    return GetLen();
}

void CEditLeafElement::GetAll(CEditSelection& selection) {
    selection.m_start.m_pElement = this;
    selection.m_start.m_iPos = 0;
    selection.m_end.m_pElement = this;
    selection.m_end.m_iPos = Leaf()->GetLen();
    selection.m_bFromStart = FALSE;
}


#ifdef DEBUG
void CEditLeafElement::ValidateTree(){
    CEditElement::ValidateTree();
    CEditElement *pChild = GetChild();
    XP_ASSERT(!pChild); // Must have no children.
    // Parent must exist and be a container
    CEditElement *pParent = GetParent();
    XP_ASSERT(pParent); 
    XP_ASSERT(pParent->IsContainer()); 
}
#endif


//-----------------------------------------------------------------------------
// CEditContainerElement
//-----------------------------------------------------------------------------
//
// Static constructor to create default element
//
CEditContainerElement* CEditContainerElement::NewDefaultContainer( CEditElement *pParent,
        ED_Alignment align  ){
    PA_Tag *pTag = XP_NEW( PA_Tag );
    XP_BZERO( pTag, sizeof( PA_Tag ) );
    pTag->type = P_PARAGRAPH;
    CEditContainerElement *pRet = new CEditContainerElement( pParent, pTag, align );
    PA_FreeTag( pTag );        
    return pRet;
}

CEditContainerElement::CEditContainerElement(CEditElement *pParent, PA_Tag *pTag,
                ED_Alignment defaultAlign ): 
            CEditElement(pParent, pTag ? pTag->type : P_PARAGRAPH), 
            m_defaultAlign( defaultAlign ),
            m_align( defaultAlign )
{ 

    if( pTag ){
        EDT_ContainerData *pData = ParseParams( pTag );
        SetData( pData );
        FreeData( pData );
    }
}

CEditContainerElement::CEditContainerElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer): 
        CEditElement( pStreamIn, pBuffer )
{
    m_defaultAlign = (ED_Alignment) pStreamIn->ReadInt();
    m_align = (ED_Alignment) pStreamIn->ReadInt();
}

void CEditContainerElement::StreamOut( IStreamOut *pOut ){
    CEditElement::StreamOut( pOut );
    pOut->WriteInt( m_defaultAlign );
    pOut->WriteInt( m_align );
}

void CEditContainerElement::StreamInChildren(IStreamIn* pIn, CEditBuffer* pBuffer){
    CEditElement::StreamInChildren(pIn, pBuffer);
    if ( GetChild() == NULL ){
        // I need a dummy child.
        CEditTextElement* pChild = new CEditTextElement(this, 0);
        // Creating it automaticly inserts it
    }
}

ElementIndex CEditContainerElement::GetPersistentCount(){
    // One extra element: the end of the last item in the container.
    return CEditElement::GetPersistentCount() + 1;
}

void CEditContainerElement::FinishedLoad( CEditBuffer *pBuffer ){
    {
        if ( GetType() != P_PREFORMAT ) {
            // Don't allow more than one space before non-text elements
            CEditElement* pNext;
            for ( CEditElement* pChild = GetChild();
                pChild;
                pChild = pNext ) {
                pNext = pChild->GetNextSibling();
                if ( pChild->IsText() && ! (pNext && pNext->IsText()) ){
                    CEditTextElement* pTextChild = pChild->Text();
                    char* pText = pTextChild->GetText();
                    int size = pTextChild->GetLen();
                    XP_Bool trimming = FALSE;
                    while ( size > 1 && pText[size-1] == ' ' && pText[size-2] == ' ') {
                        // More than one character of white space at the end of
                        // a text element will get laid out as one character.
                        trimming = TRUE;
                        size--;
                    }
                    if ( trimming ) {
                        if (size <= 0 ) {
                            delete pChild;
                        }
                        else {
                            char* pCopy = XP_STRDUP(pText);
                            pCopy[size] = '\0';
                            pTextChild->SetText(pCopy);
                            XP_FREE(pCopy);
                        }
                    }
                }
            }
        }
    }
    if ( GetChild() == NULL ){
        // I need a dummy child.
        CEditTextElement* pChild = new CEditTextElement(this, 0);
        // Creating it automaticly inserts it
		pChild->FinishedLoad(pBuffer);
    }
    CEditElement::FinishedLoad(pBuffer);
}

PA_Tag* CEditContainerElement::TagOpen( int iEditOffset ){
    PA_Tag *pRet = 0;
    PA_Tag* pTag;

    // create the DIV tag if we need to.
    if( !SupportsAlign() && (m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ) ){
        pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        if( m_align== ED_ALIGN_RIGHT ){
            SetTagData( pTag, "ALIGN=right>");
            pTag->type = P_DIVISION;
        }
        else {
            SetTagData( pTag, ">");
            pTag->type = P_CENTER;
        }
        pRet = pTag;
    }

    // create the actual paragraph tag
    if( GetTagData() ){
        pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        SetTagData( pTag, GetTagData() );
    }
    else {
        pTag = CEditElement::TagOpen( iEditOffset );
    }

    // link the tags together.
    if( pRet == 0 ){
        pRet = pTag;
    }
    else {
        pRet->next = pTag;
    }
    return pRet;
}

PA_Tag* CEditContainerElement::TagEnd( ){
    PA_Tag *pRet = CEditElement::TagEnd();
    if( !SupportsAlign() && m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ){
        PA_Tag* pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        pTag->is_end = TRUE;
        if( m_align == ED_ALIGN_RIGHT ){
            pTag->type = P_DIVISION;
        }
        else {
            pTag->type = P_CENTER;
        }

        if( pRet == 0 ){
            pRet = pTag;
        }
        else {
            pRet->next = pTag;
        }
    }
    return pRet;
}

EEditElementType CEditContainerElement::GetElementType()
{
    return eContainerElement;
}

XP_Bool CEditContainerElement::IsPlainFirstContainer(){
    if ( GetType() == P_PARAGRAPH
        && IsFirstContainer() ) {
        ED_Alignment alignment = GetAlignment();
        ED_Alignment defaultAlignment = GetDefaultAlignment();
        if ( alignment == ED_ALIGN_DEFAULT
            || alignment == defaultAlignment
            || ( alignment == ED_ALIGN_LEFT
                && defaultAlignment == ED_ALIGN_DEFAULT )) {
            return TRUE;
        }
    }
    return FALSE;
}

XP_Bool CEditContainerElement::IsFirstContainer(){
    CEditSubDocElement* pSubDoc = GetSubDoc();
    return ! pSubDoc || pSubDoc->GetChild() == this;
}

XP_Bool CEditContainerElement::SupportsAlign(){
    return BitSet( edt_setContainerSupportsAlign, GetType() );
}

void CEditContainerElement::PrintOpen( CPrintState *pPrintState ){
    if ( ShouldSkipTags() ) return;

    pPrintState->m_iCharPos = 0;
    pPrintState->m_pOut->Printf( "\n"); 

    PA_Tag *pTag = TagOpen(0);
    while( pTag ){
        char *pData = 0;
        if( pTag->data ){
            PA_LOCK( pData, char*, pTag->data );
        }

        if( pData && *pData != '>' ) {
            pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s %s", 
                    EDT_TagString(pTag->type), pData );
        }
        else {
            pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s>", 
                    EDT_TagString(pTag->type));
        }

        if( pTag->data ){
            PA_UNLOCK( pTag->data );
        }

        PA_Tag* pDelTag = pTag;
        pTag = pTag->next;
        PA_FreeTag(pDelTag);
    }
}

XP_Bool CEditContainerElement::ShouldSkipTags(){
    XP_Bool parentIsTableCell = GetParent() && GetParent()->IsTableCell();
    return parentIsTableCell && IsPlainFirstContainer() || IsEmpty();
}

void CEditContainerElement::PrintEnd( CPrintState *pPrintState ){
    if ( ShouldSkipTags() ) return;

    PA_Tag *pTag = TagEnd();
    while( pTag ){
        char *pData = 0;
        if( pTag->data ){
            PA_LOCK( pData, char*, pTag->data );
        }

        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "</%s>",
            EDT_TagString( pTag->type ) );

        if( pTag->data ){
            PA_UNLOCK( pTag->data );
        }

        PA_Tag* pDelTag = pTag;
        pTag = pTag->next;
        PA_FreeTag(pDelTag);
    }

    pPrintState->m_iCharPos = 0;
    pPrintState->m_pOut->Printf( "\n"); 
}



CEditElement* CEditContainerElement::Clone( CEditElement* pParent ){
    PA_Tag *pTag = TagOpen(0);

    // LTNOTE: we get division tags at the beginning of paragraphs
    //  for their alignment. Ignore these tags, we get it directly from the
    //  container itself.
    //  Its a total hack.
    //
    if( pTag->type == P_DIVISION || pTag->type == P_CENTER ){
        PA_Tag *pDelTag = pTag;
        pTag = pTag->next;
        PA_FreeTag( pDelTag );
    }
    CEditElement *pRet = new CEditContainerElement(pParent, pTag, m_align );
    PA_FreeTag( pTag );
    return pRet;
}

// Property Getting and Setting Stuff.

EDT_ContainerData* CEditContainerElement::NewData(){
    EDT_ContainerData *pData = XP_NEW( EDT_ContainerData );
    pData->align = ED_ALIGN_LEFT;
    return pData;
}

void CEditContainerElement::FreeData( EDT_ContainerData *pData ){
    XP_FREE( pData );
}

void CEditContainerElement::SetData( EDT_ContainerData *pData ){
    char *pNew = 0;

    m_align = pData->align;
    if( m_align == ED_ALIGN_CENTER ) m_align = ED_ALIGN_ABSCENTER;

    if( SupportsAlign() 
            && ( m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ) ){
        pNew = PR_sprintf_append( pNew, "ALIGN=%s>", EDT_AlignString(m_align) );
    }
    else {
        pNew = PR_sprintf_append( pNew, ">" );
    }
    SetTagData( pNew );
    free(pNew);
}

EDT_ContainerData* CEditContainerElement::GetData( ){
    EDT_ContainerData *pRet;
    PA_Tag* pTag = TagOpen(0);
    pRet = ParseParams( pTag );
    PA_FreeTag( pTag );
    return pRet;
}

EDT_ContainerData* CEditContainerElement::ParseParams( PA_Tag *pTag ){
    EDT_ContainerData *pData = NewData();
    ED_Alignment align;
    
    align = edt_FetchParamAlignment( pTag, m_defaultAlign );
    if( align == ED_ALIGN_CENTER ) align =  ED_ALIGN_ABSCENTER;

    if( align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT  || align == ED_ALIGN_ABSCENTER){
        pData->align = align;
    }
    return pData;
}

void CEditContainerElement::SetAlignment( ED_Alignment eAlign ){
    EDT_ContainerData *pData = GetData();
    pData->align = eAlign;
    SetData( pData );
    FreeData( pData );
}

void CEditContainerElement::CopyAttributes(CEditContainerElement *pFrom ){
    SetType( pFrom->GetType() );
    SetAlignment( pFrom->GetAlignment() );
}

XP_Bool CEditContainerElement::IsEmpty(){
    CEditElement *pChild = GetChild();
    return ( pChild == 0 
                || ( pChild->IsA( P_TEXT )
                    && pChild->Text()->GetLen() == 0
                    && pChild->LeafInContainerAfter() == 0 )
            );
}

#ifdef DEBUG
void CEditContainerElement::ValidateTree(){
    CEditElement::ValidateTree();
    CEditElement* pChild = GetChild();
    XP_ASSERT(pChild); // Must have at least one child.
}
#endif

// 
// Sets the alignment if the container is empty (or almost empty).  Used only
//  during the parse phase to handle things like <p><center><img>
//
void CEditContainerElement::AlignIfEmpty( ED_Alignment eAlign ){
    CEditElement *pChild = GetChild();
    while( pChild ){
        if( pChild->IsA( P_TEXT ) 
                && pChild->Leaf()->GetLen() != 0 ){
            return;
        }
        pChild = pChild->GetNextSibling();
    }
    SetAlignment( eAlign );
}


//-----------------------------------------------------------------------------
// CEditListElement
//-----------------------------------------------------------------------------

CEditListElement::CEditListElement( 
        CEditElement *pParent, PA_Tag *pTag ):
            CEditElement( pParent, pTag->type ){
   
    if( pTag ){
        char *locked_buff;
                
        PA_LOCK(locked_buff, char *, pTag->data );
        SetTagData( locked_buff );
        PA_UNLOCK(pTag->data);
    }
}

CEditListElement::CEditListElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer): 
        CEditElement( pStreamIn, pBuffer )
{
}

PA_Tag* CEditListElement::TagOpen( int /* iEditOffset */ ){
    PA_Tag *pTag = XP_NEW( PA_Tag );
    XP_BZERO( pTag, sizeof( PA_Tag ) );
    SetTagData( pTag, GetTagData() ? GetTagData() : ">" );
    return pTag;
}

CEditElement* CEditListElement::Clone( CEditElement *pParent ){ 
    PA_Tag* pTag = TagOpen(0);
    CEditElement* pNew = new CEditListElement( pParent, pTag );
    PA_FreeTag( pTag );
    return pNew;
}

// Property Getting and Setting Stuff.

EDT_ListData* CEditListElement::NewData(){
    EDT_ListData *pData = XP_NEW( EDT_ListData );
    if( pData == 0 ){
        // throw();
        return pData;
    }
    pData->iTagType = P_UNUM_LIST;
    pData->bCompact = FALSE;
    pData->eType = ED_LIST_TYPE_DEFAULT;
    pData->iStart = 1;
    pData->pExtra = 0;
    return pData;
}

void CEditListElement::FreeData( EDT_ListData *pData ){
    if( pData->pExtra ) XP_FREE( pData->pExtra );
    XP_FREE( pData );
}

void CEditListElement::SetData( EDT_ListData *pData ){
    char *pNew = 0;

    SetType( pData->iTagType );
    
    if( pData->bCompact ){
        pNew = PR_sprintf_append( pNew, " COMPACT");
    }
    if( pData->iStart != 1 ){
        pNew = PR_sprintf_append( pNew, " START=%d", pData->iStart );
    }
    switch( pData->eType ){
        case ED_LIST_TYPE_DEFAULT:
            break;
        case ED_LIST_TYPE_DIGIT:
            SetType( P_NUM_LIST );
            break;
        case ED_LIST_TYPE_BIG_ROMAN:
            SetType( P_NUM_LIST );
            pNew = PR_sprintf_append( pNew, " TYPE=I" );
            break;
        case ED_LIST_TYPE_SMALL_ROMAN:
            SetType( P_NUM_LIST );
            pNew = PR_sprintf_append( pNew, " TYPE=i" );
            break;
        case ED_LIST_TYPE_BIG_LETTERS:
            SetType( P_NUM_LIST );
            pNew = PR_sprintf_append( pNew, " TYPE=A" );
            break;
        case ED_LIST_TYPE_SMALL_LETTERS:
            SetType( P_NUM_LIST );
            pNew = PR_sprintf_append( pNew, " TYPE=a" );
            break;
        case ED_LIST_TYPE_CIRCLE:
            SetType( P_UNUM_LIST );
            pNew = PR_sprintf_append( pNew, " TYPE=CIRCLE" );
            break;
        case ED_LIST_TYPE_SQUARE:
            SetType( P_UNUM_LIST );
            pNew = PR_sprintf_append( pNew, " TYPE=SQUARE" );
            break;
        case ED_LIST_TYPE_DISC:
            SetType( P_UNUM_LIST );
            pNew = PR_sprintf_append( pNew, " TYPE=DISC" );
            break;
    }

    if( pData->pExtra  ){
        pNew = PR_sprintf_append( pNew, " %s", pData->pExtra );
    }

    if( pNew ){
        pNew = PR_sprintf_append( pNew, ">" );
    }

    SetTagData( pNew );

    if ( pNew ){
        free(pNew);
    }
}

EDT_ListData* CEditListElement::GetData( ){
    EDT_ListData *pRet;
    PA_Tag* pTag = TagOpen(0);
    pRet = ParseParams( pTag );
    PA_FreeTag( pTag );
    return pRet;
}

static char* listParams[] = {
    PARAM_TYPE,
    PARAM_COMPACT,
    PARAM_START,
    0
};

EDT_ListData* CEditListElement::ParseParams( PA_Tag *pTag ){
    EDT_ListData *pData = NewData();
    char *pType;

    pData->iTagType = pTag->type;
    pData->bCompact = edt_FetchParamBoolExist( pTag, PARAM_COMPACT );

    pType = edt_FetchParamString( pTag, PARAM_TYPE );
    if( pType == 0 ){
        if( pTag->type == P_NUM_LIST ){
            pData->eType = ED_LIST_TYPE_DIGIT;
        }
        else {
            pData->eType = ED_LIST_TYPE_DEFAULT;
        }
    }
    else {
        switch (*pType){
        case 'A':
            pData->eType = ED_LIST_TYPE_BIG_LETTERS;
            break;
        case 'a':
            pData->eType = ED_LIST_TYPE_SMALL_LETTERS;
            break;
        case 'i':
            pData->eType = ED_LIST_TYPE_SMALL_ROMAN;
            break;
        case 'I':
            pData->eType = ED_LIST_TYPE_BIG_ROMAN;
            break;
        case 'c':
        case 'C':
            pData->eType = ED_LIST_TYPE_CIRCLE;
            break;
        case 'd':
        case 'D':
            pData->eType = ED_LIST_TYPE_DISC;
            break;
        case 's':
        case 'S':
            pData->eType = ED_LIST_TYPE_SQUARE;
            break;
        default:
            pData->eType = ED_LIST_TYPE_DEFAULT;
            break;
        }
    }

    pData->iStart = edt_FetchParamInt( pTag, PARAM_START, 1 );
    pData->pExtra = edt_FetchParamExtras( pTag, listParams );
    return pData;
}

void CEditListElement::CopyData(CEditListElement* pOther){
    EDT_ListData* pData = pOther->GetData();
    SetData(pData);
    FreeData(pData);
}

#ifdef DEBUG
void CEditListElement::ValidateTree(){
    CEditElement::ValidateTree();
    // Must have at least one child.
    XP_ASSERT(GetChild());
    // We rely on the children to check that
    // they can have a list as a parent.
}
#endif

EEditElementType CEditListElement::GetElementType(){
    return eListElement;
}

//-----------------------------------------------------------------------------
// CEditTextElement
//-----------------------------------------------------------------------------

CEditTextElement::CEditTextElement(CEditElement *pParent, char *pText):
        CEditLeafElement(pParent, P_TEXT), 
        m_pFirstLayoutElement(0),
        m_tf(TF_NONE),
        m_iFontSize(DEF_FONTSIZE),
        m_href(ED_LINK_ID_NONE),
        m_color(ED_Color::GetUndefined()),
        m_pFace(NULL),
        m_pScriptExtra(NULL)

{
    if( pText && *pText ){
        m_pText = XP_STRDUP(pText);             // should be new??
        m_textSize = XP_STRLEN(m_pText)+1;
    }
    else {
        m_pText = 0;
        m_textSize = 0;
    }
}

CEditTextElement::CEditTextElement( IStreamIn *pIn, CEditBuffer* pBuffer ):
        CEditLeafElement(pIn, pBuffer), 
        m_pFirstLayoutElement(0),
        m_tf(TF_NONE),
        m_iFontSize(DEF_FONTSIZE),
        m_href(ED_LINK_ID_NONE),
        m_pText(0),
        m_color(ED_Color::GetUndefined()),
        m_pFace(NULL),
        m_pScriptExtra(NULL)
{
    m_tf = (ED_TextFormat)pIn->ReadInt( );
    
    m_iFontSize = pIn->ReadInt( );
    
    m_color = pIn->ReadInt( );
    
    if( m_tf & TF_HREF ){
        char* pHref = pIn->ReadZString();
        char* pExtra = pIn->ReadZString();
        SetHREF( pBuffer->linkManager.Add( pHref, pExtra ));
        XP_FREE( pHref );
        XP_FREE( pExtra );
    }

    if ( m_tf & TF_FONT_FACE ) {
        m_pFace = pIn->ReadZString();
    }

    if ( EDT_IS_SCRIPT(m_tf) ) {
        m_pScriptExtra = pIn->ReadZString();
    }

    m_pText = pIn->ReadZString();
    if( m_pText ){
        m_textSize = XP_STRLEN( m_pText );
    }
    else {
        m_textSize = 0;
    }
}

CEditTextElement::~CEditTextElement(){
    DisconnectLayoutElements((LO_Element*) m_pFirstLayoutElement);
    if ( m_pText) XP_FREE(m_pText); 
    if( m_href != ED_LINK_ID_NONE ) m_href->Release();
    if ( m_pFace ) {
        XP_FREE(m_pFace);
        m_pFace = NULL;
    }
    if ( m_pScriptExtra ) {
        XP_FREE(m_pScriptExtra);
        m_pScriptExtra = NULL;
    }
}

void CEditTextElement::StreamOut( IStreamOut *pOut ){
    CEditSelection all;
    GetAll(all);
    PartialStreamOut(pOut, all );
}

void CEditTextElement::PartialStreamOut( IStreamOut *pOut, CEditSelection& selection ){
    CEditSelection local;
    if ( ClipToMe(selection, local) ) {

        // Fake a stream out.
        CEditLeafElement::StreamOut( pOut  );
    
        pOut->WriteInt( m_tf );
    
        pOut->WriteInt( m_iFontSize );
    
        pOut->WriteInt( m_color.GetAsLong() );
    
        if( m_tf & TF_HREF ){
            pOut->WriteZString( m_href->hrefStr );
            pOut->WriteZString( m_href->pExtra );
        }

        if( m_tf & TF_FONT_FACE ){
            pOut->WriteZString( m_pFace );
        }

        if( EDT_IS_SCRIPT(m_tf) ){
            pOut->WriteZString( m_pScriptExtra );
        }

        pOut->WritePartialZString( m_pText, local.m_start.m_iPos, local.m_end.m_iPos);

        // No children
        pOut->WriteInt((int32)eElementNone);
    }
}


EEditElementType CEditTextElement::GetElementType()
{
    return eTextElement;
}

void CEditTextElement::SetLayoutElement( intn iEditOffset, intn lo_type, 
                        LO_Element* pLoElement ){
    SetLayoutElementHelper(LO_TEXT, (LO_Element**) &m_pFirstLayoutElement,
        iEditOffset, lo_type, pLoElement);
}

void CEditTextElement::ResetLayoutElement( intn iEditOffset, 
            LO_Element* pLoElement ){
    ResetLayoutElementHelper((LO_Element**) &m_pFirstLayoutElement,
        iEditOffset, pLoElement);
}

void CEditTextElement::SetText( char *pText ){
    if( m_pText ){
        XP_FREE( m_pText );
    }
    if( pText && *pText ){
        m_pText = XP_STRDUP(pText);             // should be new??
        m_textSize = XP_STRLEN(m_pText)+1;
    }
    else {
        m_pText = 0;
        m_textSize = 0;
    }
}


void CEditTextElement::SetColor( ED_Color iColor ){ 
    m_color = iColor; 
    // Dont set color if we are a script
    if(!iColor.IsDefined() || EDT_IS_SCRIPT(m_tf) ){
        m_tf  &= ~TF_FONT_COLOR;
    }
    else {
        m_tf |= TF_FONT_COLOR;
    }
}

void CEditTextElement::SetFontSize( int iSize ){
    m_iFontSize = iSize; 
    if( m_iFontSize > 7 ) m_iFontSize = 7;
    // if( m_iFontSize < 1 ) m_iFontSize = 1;
    
    //Override (use default for) ALL font size requests for Java Script
    if( EDT_IS_SCRIPT(m_tf) ){
        iSize = 0;
    }
    
    // Use 0 (or less) to signify changing to default
    if( iSize <= 0 ){
        m_iFontSize = GetDefaultFontSize();
    }
    if( m_iFontSize == GetDefaultFontSize() ){
        m_tf  &= ~TF_FONT_SIZE;
    }
    else {
        m_tf |= TF_FONT_SIZE;
    }
}

void CEditTextElement::SetFontFace( char* pFace ){
    if ( m_pFace ) {
        XP_FREE(m_pFace);
        m_pFace = NULL;
        m_tf &= ~TF_FONT_FACE;
    }
    if ( pFace ) {
        m_tf |= TF_FONT_FACE;
        m_pFace = XP_STRDUP(pFace);
    }
}

void CEditTextElement::SetScriptExtra( char* pScriptExtra ){
    if ( m_pScriptExtra ) {
        XP_FREE(m_pScriptExtra);
        m_pScriptExtra = NULL;
    }
    if ( pScriptExtra ) {
        m_pScriptExtra = XP_STRDUP(pScriptExtra);
    }
}

void CEditTextElement::SetHREF( ED_LinkId id ){
    if( m_href != ED_LINK_ID_NONE ){
        m_href->Release();
    }
    m_href = id;
    // Dont set HREF if we are a script
    if( id != ED_LINK_ID_NONE && !EDT_IS_SCRIPT(m_tf) ){
        id->AddRef();
        m_tf |= TF_HREF;
    }
    else {
        m_tf &= ~TF_HREF;
    }
}

int CEditTextElement::GetFontSize(){ 
    if( m_tf & TF_FONT_SIZE ){
        return m_iFontSize;
    }
    else {
        return GetDefaultFontSize();
    }
}

char* CEditTextElement::GetFontFace(){ 
    return m_pFace;
}

char* CEditTextElement::GetScriptExtra(){ 
    return m_pScriptExtra;
}

void CEditTextElement::ClearFormatting(){
    if ( m_tf & TF_HREF) {
        if ( m_href != ED_LINK_ID_NONE ) {
            m_href->Release();
        }
        else {
            XP_ASSERT(FALSE);
        }
    }
    m_href = ED_LINK_ID_NONE;
    m_tf = 0;
    SetFontSize( GetDefaultFontSize() );
    m_color = ED_Color::GetUndefined();
    
    /* We preserve font face since there's no user API for changing it.
     * Once we can set the font face, clear instead of preserving.
     */
    if ( m_pFace ) {
        m_tf = TF_FONT_FACE;
    }

    /* We don't preserve the script extra because the SCRIPT attribute is cleared.
     */
    SetScriptExtra(NULL);
}

void CEditTextElement::SetData( EDT_CharacterData *pData ){

    // Either Java Script type
    ED_TextFormat tfJava = TF_SERVER | TF_SCRIPT;

    // if we are setting server or script,
    //   then it is the only thing we need to do
    if( (pData->mask & pData->values ) & tfJava ){
        ClearFormatting();
        m_tf = (pData->mask & pData->values ) & tfJava;
        return;
    }
    // If we are already a Java Script type,
    //   and we are NOT removing that type (mask bit is not set)
    //   then ignore all other attributes
    if( (0 != (m_tf & TF_SERVER) && 
         0 == (pData->mask & TF_SERVER)) ||
        (0 != (m_tf & TF_SCRIPT) && 
         0 == (pData->mask & TF_SCRIPT)) ){
        return;
    }

    if( pData->mask & TF_HREF ){
        SetHREF( pData->linkId );
    }
    if( pData->mask & TF_FONT_COLOR ){
        if( (pData->values & TF_FONT_COLOR) ){
            SetColor( EDT_LO_COLOR(pData->pColor) );
        }
        else {
            SetColor( ED_Color::GetUndefined() );
        }
    }
    if( pData->mask & TF_FONT_SIZE ){
        if( pData->values & TF_FONT_SIZE ){
            SetFontSize( pData->iSize );
        }
        else {
            SetFontSize( GetDefaultFontSize() );
            XP_ASSERT( (m_tf & TF_FONT_SIZE) == 0 );
        }
    }

    // remove the elements we've already handled.
    ED_TextFormat mask = pData->mask & ~( TF_HREF 
                                        | TF_FONT_SIZE
                                        | TF_FONT_COLOR
                                        | TF_SCRIPT
                                        | TF_SERVER );

    ED_TextFormat values = pData->values & mask;

    m_tf = (m_tf & ~mask) | values;
}

EDT_CharacterData* CEditTextElement::GetData(){
    EDT_CharacterData *pData = EDT_NewCharacterData();
    pData->mask = -1;
    pData->values = m_tf;
    pData->iSize = GetFontSize();
    if( m_tf & TF_FONT_COLOR ){
        pData->pColor = edt_MakeLoColor( GetColor() );
    }
    if( m_tf & TF_HREF ){
        if( m_href != ED_LINK_ID_NONE ){
            pData->pHREFData = m_href->GetData();
        }
        pData->linkId = m_href;
    }
    return pData;
}

void CEditTextElement::MaskData( EDT_CharacterData*& pData ){
    if( pData == 0 ){
        pData = GetData();
        return;
    }

    ED_TextFormat differences = pData->values ^ m_tf;
    if( differences ){
        pData->mask &= ~differences;

        if( (pData->mask & pData->values) & TF_FONT_COLOR 
                    && EDT_LO_COLOR( pData->pColor ) != GetColor() ){
            pData->mask &= ~TF_FONT_COLOR;
            XP_FREE( pData->pColor );
            pData->pColor = 0;
        }
        if( (pData->mask & pData->values) & TF_HREF 
                    && (pData->linkId != m_href ) ){
            pData->mask &= ~TF_HREF;
        }
        if( (pData->mask & pData->values) & TF_FONT_SIZE 
                    && (pData->iSize != m_iFontSize ) ){
            pData->mask &= ~TF_FONT_SIZE;
        }
    }
}


//
// insert a character into the text buffer. At some point we should make sure we
//  don't insert two spaces together.
//
XP_Bool CEditTextElement::InsertChar( int iOffset, int newChar ){
    //
    // If we are inserting a space at a point where there already is a space,
    //  don't do it.
    //
    if( newChar == ' '  && !InFormattedText() ) {
        if( m_pText ){
            XP_Bool bUnderSpace = (m_pText[iOffset] == ' ');    
            if( bUnderSpace  || !( iOffset != 0 && m_pText[iOffset-1] != ' ')){
                return bUnderSpace;
            }
        }
        else {
            CEditElement* pPrevious = PreviousTextInContainer();
            if( (pPrevious == 0 )
                || (pPrevious->Text()->GetText()[pPrevious->Text()->GetLen()-1]
                        == ' ' ) 
                    ){
                return FALSE;
            }
        }
    }

    // guarantee the buffer size
    //
    int iSize = GetLen()  + 1;
    if( iSize + 1 >= m_textSize ){
        int newSize = GROW_TEXT( m_textSize + 1 );
        char *pNewBuf = (char*)XP_ALLOC( newSize );
        if( pNewBuf == 0 ){
            // out of memory, should probably throw.
            return FALSE; 
        }
        if( m_pText ){
            XP_STRCPY( pNewBuf, m_pText );
            XP_FREE( m_pText );
        }
        else {
            pNewBuf[0] = 0;
        }
        m_pText = pNewBuf;
        m_textSize = newSize;
    }

    //
    // create an empty space in the string.  We have to start from the end of 
    //  the string and move backward.
    //
    int i = iSize - 1;
    while( i >= iOffset ){
        m_pText[i+1] = m_pText[i];
        i--;
    }
    m_pText[iOffset] = newChar;
    return TRUE;
}

void CEditTextElement::DeleteChar( MWContext *pContext, int iOffset ){
    
    int i = iOffset;
    char c = 1;
    int clen;

    clen = INTL_CharLen(pContext->win_csid, (unsigned char *)m_pText+iOffset);

    if( m_pText[iOffset] ){
        while( c != 0 ){
            m_pText[i] = c = m_pText[i+clen];
            i++;
        }
    }
}

char* CEditTextElement::DebugFormat(){
    static char buf[1024];
    strcpy(buf, "( " );
    int i;
    if(m_tf & TF_BOLD)         strcat(buf,"B ");
    if(m_tf & TF_ITALIC)       strcat(buf,"I ");
    if(m_tf & TF_FIXED)        strcat(buf,"TT ");
    if(m_tf & TF_SUPER)        strcat(buf,"SUP ");
    if(m_tf & TF_SUB)          strcat(buf,"SUB ");
    if(m_tf & TF_STRIKEOUT)    strcat(buf,"SO ");
    if(m_tf & TF_UNDERLINE)    strcat(buf,"U ");
    if(m_tf & TF_BLINK)        strcat(buf,"BL ");
    if(m_tf & TF_SERVER)       strcat(buf,"SERVER ");
    if(m_tf & TF_SCRIPT)       strcat(buf,"SCRIPT ");

    if(m_pScriptExtra){
        strcat(buf, m_pScriptExtra);
        strcat(buf," ");
    }

    if ( m_tf & TF_FONT_FACE && m_pFace ) {
        strcat( buf, m_pFace );
        strcat( buf, " ");
    }

    if( m_tf & TF_FONT_COLOR ){ 
        char color_str[8];
        PR_snprintf(color_str, 8, "#%06lX ", GetColor().GetAsLong() );
        strcat( buf, color_str );
    }

    if( m_tf & TF_FONT_SIZE ){
        i = XP_STRLEN(buf);
        buf[i] = (GetFontSize() < 3  ? '-': '+');
        buf[i+1] = '0' + abs(GetFontSize() - 3);
        buf[i+2] = ' ';
        buf[i+3] = 0;
    }

    if( m_tf & TF_HREF ){ 
        strcat( buf, m_href->hrefStr );
    }

    strcat(buf, ") " );
    return buf;
}   

//
// Create a list of tags that represents the character formatting for this
//  text element.
//
void CEditTextElement::FormatOpenTags(PA_Tag*& pStart, PA_Tag*& pEnd){


    if(m_tf & TF_BOLD){ edt_AddTag( pStart, pEnd, P_BOLD, FALSE ); }
    if(m_tf & TF_ITALIC){ edt_AddTag( pStart, pEnd, P_ITALIC, FALSE ); }
    if(m_tf & TF_FIXED){ edt_AddTag( pStart, pEnd, P_FIXED, FALSE ); }
    if(m_tf & TF_SUPER){ edt_AddTag( pStart,pEnd, P_SUPER, FALSE ); }
    if(m_tf & TF_SUB){ edt_AddTag( pStart,pEnd, P_SUB, FALSE ); }
    if(m_tf & TF_STRIKEOUT){ edt_AddTag( pStart,pEnd, P_STRIKEOUT, FALSE ); }
    if(m_tf & TF_UNDERLINE){ edt_AddTag( pStart,pEnd, P_UNDERLINE, FALSE ); }
    if(m_tf & TF_BLINK){ edt_AddTag( pStart,pEnd, P_BLINK, FALSE ); }

    // Face and color have to come before size for old browsers.
    if( m_tf & TF_FONT_FACE ){
        char buf[200];
        XP_STRCPY( buf, " FACE=\"" );
        strcat( buf, m_pFace );
        strcat( buf, "\">" );
        edt_AddTag( pStart,pEnd, P_FONT, FALSE, buf );
    }

    if( GetColor().IsDefined() ){ 
        char buf[20];
        PR_snprintf(buf, 20, "COLOR=\"#%06lX\">", GetColor().GetAsLong() );
        edt_AddTag( pStart,pEnd, P_FONT, FALSE, buf );
    }

    if( m_tf & TF_FONT_SIZE ){
        char buf[20];
        XP_STRCPY( buf, " SIZE=" );
        int i = XP_STRLEN(buf);
        buf[i] = (GetFontSize() < 3  ? '-': '+');
        buf[i+1] = '0' + abs(GetFontSize() - 3);
        buf[i+2] = '>';
        buf[i+3] = 0;
        edt_AddTag( pStart,pEnd, P_FONT, FALSE, buf );
    }

    if( m_tf & TF_HREF){ 
        char *pBuf = PR_smprintf("HREF=%s %s\">", edt_MakeParamString(m_href->hrefStr),
            (m_href->pExtra ? m_href->pExtra : "") );        
        edt_AddTag( pStart,pEnd, P_ANCHOR, FALSE, pBuf );
        free( pBuf );
    }

    // if EVAL, hack the character attributes...
    if(m_tf & TF_SERVER){ 
        edt_AddTag( pStart, pEnd, P_SERVER, FALSE );
        return;
    }

    if(m_tf & TF_SCRIPT){ 
        edt_AddTag( pStart, pEnd, P_SCRIPT, FALSE );
        return;
    }

}

void CEditTextElement::FormatTransitionTags(CEditTextElement* /* pNext */,
            PA_Tag*& /* pStart */, PA_Tag*& /* pEnd */){
}

void CEditTextElement::FormatCloseTags(PA_Tag*& pStart, PA_Tag*& pEnd){

    // WARNING: order here is real important, Open and Close must match
    //  exactly.

    // if EVAL must be last
    if(m_tf & TF_SCRIPT){ 
        edt_AddTag( pStart, pEnd, P_SCRIPT, TRUE );
    }

    if(m_tf & TF_SERVER){ 
        edt_AddTag( pStart, pEnd, P_SERVER, TRUE );
    }

    //if(m_href){ edt_AddTag( pStart,pEnd, P_ITALIC, FALSE ); }
    if( m_tf & TF_HREF ){ edt_AddTag( pStart,pEnd, P_ANCHOR, TRUE );}
    if( m_tf & TF_FONT_SIZE ){edt_AddTag( pStart,pEnd, P_FONT, TRUE );}
    if( GetColor().IsDefined() ){edt_AddTag( pStart,pEnd, P_FONT, TRUE );}
    if( m_tf & TF_FONT_FACE ) edt_AddTag( pStart,pEnd, P_FONT, TRUE );

    if(m_tf & TF_BLINK ){ edt_AddTag( pStart,pEnd, P_BLINK, TRUE); }
    if(m_tf & TF_STRIKEOUT ){ edt_AddTag( pStart,pEnd, P_STRIKEOUT, TRUE); }
    if(m_tf & TF_UNDERLINE ){ edt_AddTag( pStart,pEnd, P_UNDERLINE, TRUE); }
    if(m_tf & TF_SUB ){ edt_AddTag( pStart,pEnd, P_SUB, TRUE); }
    if(m_tf & TF_SUPER ){ edt_AddTag( pStart,pEnd, P_SUPER, TRUE); }
    if(m_tf & TF_FIXED ){ edt_AddTag( pStart, pEnd, P_FIXED, TRUE); }
    if(m_tf & TF_ITALIC ){ edt_AddTag( pStart, pEnd, P_ITALIC, TRUE); }
    if(m_tf & TF_BOLD ){ edt_AddTag( pStart, pEnd, P_BOLD, TRUE); }


}

//
// Create a text tag.  We are going to be feeding these tags to the layout 
//  engine.  The layout element this edit element points to is going to change.
//
PA_Tag* CEditTextElement::TagOpen( int iEditOffset ){
    // LTNOTE: I think that we only want to do this if we are the first text
    //  in the container, other wise, we don't do anything.
    PA_Tag *pStart = 0;
    PA_Tag *pEnd = 0;
    XP_Bool bFirst = IsFirstInContainer();

    if( GetLen() == 0 && (!bFirst || (bFirst && LeafInContainerAfter() != 0))){
        return 0;
    }

    PA_Tag *pTag = XP_NEW( PA_Tag );
    XP_BZERO( pTag, sizeof( PA_Tag ) );

    FormatOpenTags( pStart, pEnd );    

    if( pStart == 0 ){
        pStart = pTag;
    }
    if( pEnd ){
        pEnd->next = pTag;
    }
    pEnd = pTag;

    if(GetLen() == 0){
        // As of 3.0b5, a non-breaking space always caused a memory leak when
        // it was laid out. See bug 23404
        // So use a different character here. The character is
        // removed by code in CEditBuffer::Relayout
        SetTagData(pTag, ".");
        //SetTagData(pTag, NON_BREAKING_SPACE_STRING);
        //SetTagData(pTag, "");
    }
    else {
        SetTagData(pTag, GetText()+iEditOffset);
    }
    if( iEditOffset==0 ){
        m_pFirstLayoutElement = 0;
    }

    FormatCloseTags( pStart, pEnd );

    return pStart;
}

XP_Bool CEditTextElement::GetLOTextAndOffset( ElementOffset iEditOffset, XP_Bool bStickyAfter, LO_TextStruct*&  pRetText, 
        int& iLayoutOffset ){
    
    LO_TextStruct *pText;
    LO_Element* pLoElement = m_pFirstLayoutElement;
    XP_ASSERT(!pLoElement || pLoElement->lo_any.edit_offset == 0);
    intn len = GetLen();

    while(pLoElement != 0){
        if( pLoElement->type == LO_TEXT ){
            // if we have scanned past our edit element, we can't find the 
            // insert point.
            pText = &(pLoElement->lo_text);
            if( pText->edit_element != this){
                /* assert(0)
                */
                return FALSE;
            }

            int16 textLen = pText->text_len;
            int32 loEnd = pText->edit_offset + textLen;

            //
            // See if we are at the dreaded lopped of the last space case.
            //
            if( loEnd+1 == iEditOffset ){
                // ok, we've scaned past the end of the Element
                if( len == iEditOffset && m_pText[iEditOffset-1] == ' '){
                    // we wraped to the next element.  Return the next Text Element.
                    CEditElement *pNext = FindNextElement(&CEditElement::FindText,0);
                    if( pNext ){
                        return pNext->Text()->GetLOTextAndOffset( 0, FALSE, pRetText, iLayoutOffset );
                    }

                    // we should have all these cases handled now.

                    // jhp Nope. (a least as of Beta 2) If you have a very narrow document,
                    // and you
                    // type in two lines of text, and you're at the end of the
                    // second line, and you type a space, and the
                    // space causes a line wrap, then you end up here.
                    // XP_ASSERT(FALSE);

                    return FALSE;
                }
            }

            if( loEnd > iEditOffset || (loEnd == iEditOffset
                && (!bStickyAfter || len == iEditOffset || textLen == 0 ) ) ){
                // we've found the right element, return the information.
                iLayoutOffset = iEditOffset - pText->edit_offset;
                pRetText = pText;
                return TRUE;
            }
            
        }
        pLoElement = pLoElement->lo_any.next;
    }
    return FALSE;
}

LO_TextStruct* CEditTextElement::GetLOText( int iEditOffset ){
    
    LO_TextStruct *pText;
    LO_Element* pLoElement = m_pFirstLayoutElement;

    while(pLoElement != 0){
        if( pLoElement->type == LO_TEXT ){
            // if we have scanned past our edit element, we can't find the 
            // insert point.
            pText = &(pLoElement->lo_text);
            if( pText->edit_element != this){
                return 0;
            }
            if( pText->edit_offset > iEditOffset ){
                return 0;
            }

            if( pText->edit_offset + pText->text_len >= iEditOffset ){
                // we've found the right element, return the information.
                return pText;
            }
            
        }
        pLoElement = pLoElement->lo_any.next;
    }
    return 0;
}

void CEditTextElement::PrintTagOpen( CPrintState *pPrintState, TagType t, 
            ED_TextFormat tf, char* pExtra ){
    if ( ! pExtra ) pExtra = ">";
    pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s%s", 
                EDT_TagString(t), pExtra );
    pPrintState->m_formatStack.Push(tf);
    pPrintState->m_elementStack.Push(this);
}


void CEditTextElement::PrintFormatDifference( CPrintState *ps, 
        ED_TextFormat bitsDifferent ){
    if(bitsDifferent & TF_BOLD){ PrintTagOpen( ps, P_BOLD, TF_BOLD ); } 
    if(bitsDifferent & TF_ITALIC){ PrintTagOpen( ps, P_ITALIC, TF_ITALIC ); } 
    if(bitsDifferent & TF_FIXED){ PrintTagOpen( ps, P_FIXED, TF_FIXED ); } 
    if(bitsDifferent & TF_SUPER){ PrintTagOpen( ps, P_SUPER, TF_SUPER ); } 
    if(bitsDifferent & TF_SUB){ PrintTagOpen( ps, P_SUB, TF_SUB ); } 
    if(bitsDifferent & TF_STRIKEOUT){ PrintTagOpen( ps, P_STRIKEOUT, TF_STRIKEOUT ); } 
    if(bitsDifferent & TF_UNDERLINE){ PrintTagOpen( ps, P_UNDERLINE, TF_UNDERLINE ); } 
    if(bitsDifferent & TF_BLINK){ PrintTagOpen( ps, P_BLINK, TF_BLINK ); } 

    // Face and color have to preceed FONT_SIZE, or else old browsers will
    // reset the font_size to zero when they encounter the unknown tags.

    if( bitsDifferent & TF_FONT_FACE ){
        ps->m_iCharPos += ps->m_pOut->Printf( "<FONT FACE=\"%s\">", m_pFace);
        ps->m_formatStack.Push(TF_FONT_FACE);
        ps->m_elementStack.Push(this);
    }

    if( bitsDifferent & TF_FONT_COLOR ){
        ps->m_iCharPos += ps->m_pOut->Printf( "<FONT COLOR=\"#%06lX\">", 
                    GetColor().GetAsLong() );
        ps->m_formatStack.Push(TF_FONT_COLOR);
        ps->m_elementStack.Push(this);
    }

    if( bitsDifferent & TF_FONT_SIZE ){
        char buf[4];
        buf[0] = (GetFontSize() < 3  ? '-': '+');
        buf[1] = '0' + abs(GetFontSize() - 3);
        buf[2] = 0;
        ps->m_iCharPos += ps->m_pOut->Printf( "<FONT SIZE=%s>", buf);
        ps->m_formatStack.Push(TF_FONT_SIZE);
        ps->m_elementStack.Push(this);
    }

    if( bitsDifferent & TF_HREF){ 
        // doesn't use the output routine because HREF can be large.
        char *pS1 = "";
        char *pS2 = "";
        if( m_href->pExtra ){
            pS1 = " ";
            pS2 = m_href->pExtra;
        }
        ps->m_iCharPos += ps->m_pOut->Printf( 
            "<A HREF=%s%s%s>", edt_MakeParamString(m_href->hrefStr), pS1, pS2 );

        ps->m_formatStack.Push(TF_HREF);
        ps->m_elementStack.Push(this);
    }

    if(bitsDifferent & TF_SCRIPT){
        PrintTagOpen( ps, P_SCRIPT, TF_SCRIPT, GetScriptExtra() ); } 
    if(bitsDifferent & TF_SERVER){ PrintTagOpen( ps, P_SERVER, TF_SERVER, GetScriptExtra() ); } 
}

void CEditTextElement::PrintFormat( CPrintState *ps, 
                                    CEditTextElement *pFirst, 
                                    ED_TextFormat mask ){
    ED_TextFormat bitsCommon = 0;
    ED_TextFormat bitsDifferent = 0;

    ComputeDifference(pFirst, mask, bitsCommon, bitsDifferent);

    CEditTextElement *pNext = (CEditTextElement*) TextInContainerAfter();

    if( bitsCommon ){
        // While we're in a run of the same style, there's nothing to do.
        // Avoid unnescessary recursion so we don't blow the stack on Mac or Win16
        while ( pNext && SameAttributes(pNext) ){
            pNext = (CEditTextElement*) pNext->TextInContainerAfter();
        }
        if( pNext ){
            pNext->PrintFormat( ps, pFirst, bitsCommon );
        }
        else {
            // we hit the end, so we have to open everything
            pFirst->PrintFormatDifference( ps, bitsCommon);
        }
    }
    if( bitsDifferent ){
        pFirst->PrintFormatDifference( ps, bitsDifferent );
    }
}

XP_Bool CEditTextElement::SameAttributes(CEditTextElement *pCompare){
    ED_TextFormat bitsCommon;
    ED_TextFormat bitsDifferent;
    ComputeDifference(pCompare, -1, bitsCommon, bitsDifferent);
    return ( bitsCommon == m_tf);
}

void CEditTextElement::ComputeDifference(CEditTextElement *pFirst, 
        ED_TextFormat mask, ED_TextFormat& bitsCommon, ED_TextFormat& bitsDifferent){
    bitsCommon = m_tf & mask;
    bitsDifferent = mask & ~bitsCommon;

    if( (bitsCommon & TF_FONT_SIZE)&& (GetFontSize() != pFirst->GetFontSize())){
        bitsCommon &= ~TF_FONT_SIZE;
        bitsDifferent |= TF_FONT_SIZE;
    }

    if( (bitsCommon & TF_FONT_COLOR)&& (GetColor() != pFirst->GetColor())){
        bitsCommon &= ~TF_FONT_COLOR;
        bitsDifferent |= TF_FONT_COLOR;
    }

    if( bitsCommon & TF_FONT_FACE ) {
        char* a = GetFontFace();
        char* b = pFirst->GetFontFace();
        XP_Bool bA = a != NULL;
        XP_Bool bB = b != NULL;
        if ((bA ^ bB) ||
            (bA && bB && XP_STRCMP(a, b) != 0)) {
            bitsCommon &= ~TF_FONT_FACE;
            bitsDifferent |= TF_FONT_FACE;
        }
    }

    if( (bitsCommon & TF_HREF)&& (GetHREF() != pFirst->GetHREF())){
        bitsCommon &= ~TF_HREF;
        bitsDifferent |= TF_HREF;
    }
}

void CEditTextElement::PrintTagClose( CPrintState *ps, TagType t ){
    ps->m_iCharPos += ps->m_pOut->Printf( "</%s>", 
                EDT_TagString(t) );
}

void CEditTextElement::PrintPopFormat( CPrintState *ps, int iStackTop ){

    while( ps->m_formatStack.m_iTop >= iStackTop ){
        
        ED_TextFormat tf = (ED_TextFormat) ps->m_formatStack.Pop();
        ps->m_elementStack.Pop();

        if( tf & TF_SCRIPT){ PrintTagClose( ps, P_SCRIPT ); } 
        if( tf & TF_SERVER){ PrintTagClose( ps, P_SERVER ); } 
        if( tf & TF_BOLD){ PrintTagClose( ps, P_BOLD ); } 
        if( tf & TF_ITALIC){ PrintTagClose( ps, P_ITALIC ); } 
        if( tf & TF_FIXED){ PrintTagClose( ps, P_FIXED ); } 
        if( tf & TF_SUPER){ PrintTagClose( ps, P_SUPER ); } 
        if( tf & TF_SUB){ PrintTagClose( ps, P_SUB ); } 
        if( tf & TF_STRIKEOUT){ PrintTagClose( ps, P_STRIKEOUT ); } 
        if( tf & TF_UNDERLINE){ PrintTagClose( ps, P_UNDERLINE ); } 
        if( tf & TF_BLINK){ PrintTagClose( ps, P_BLINK ); } 
        if( tf & TF_FONT_SIZE){ PrintTagClose( ps, P_FONT ); } 
        if( tf & TF_FONT_COLOR){ PrintTagClose( ps, P_FONT ); } 
        if( tf & TF_FONT_FACE){ PrintTagClose( ps, P_FONT ); } 
        if( tf & TF_HREF){ PrintTagClose( ps, P_ANCHOR ); } 
    }
}

ED_TextFormat CEditTextElement::PrintFormatClose( CPrintState *ps ){
    XP_Bool bDiff = FALSE;
    ED_TextFormat bitsNeedFormat = m_tf ;

    if( ps->m_formatStack.IsEmpty()){
        return bitsNeedFormat;        
    }
    int i = -1;
    while( !bDiff && ++i <= ps->m_formatStack.m_iTop ){
        ED_TextFormat tf = (ED_TextFormat) ps->m_formatStack[i];
        if( m_tf & tf ){
            if( tf == TF_FONT_COLOR 
                    && GetColor() != ps->m_elementStack[i]->GetColor()){
                bDiff = TRUE;
            }
            else if( tf == TF_FONT_SIZE 
                    && GetFontSize() != ps->m_elementStack[i]->GetFontSize()){
                bDiff = TRUE;
            }
            else if( tf == TF_FONT_FACE 
                    && XP_STRCMP(GetFontFace(), ps->m_elementStack[i]->GetFontFace()) != 0){
                bDiff = TRUE;
            }
            else if( tf == TF_HREF
                    && GetHREF() != ps->m_elementStack[i]->GetHREF() ){
                bDiff = TRUE;
            }
        }
        else {
            bDiff = TRUE;
        }

        // if the stuff is on the stack, then it doesn't need to be formatted
        if( !bDiff ){
            bitsNeedFormat &=  ~tf;
        }
    }
    PrintPopFormat( ps, i );
    return bitsNeedFormat;

}

// for use within 
void CEditTextElement::PrintWithEscapes( CPrintState *ps ){
    char *p = GetText();

    edt_PrintWithEscapes( ps, p, !InFormattedText() );
}

void CEditTextElement::PrintLiteral( CPrintState *ps ){
    int iLen = GetLen();
    ps->m_pOut->Write( GetText(), iLen );
    ps->m_iCharPos += iLen;
}

void CEditTextElement::PrintOpen( CPrintState *ps){
    ED_TextFormat bitsToFormat = PrintFormatClose( ps );

    CEditTextElement *pNext = (CEditTextElement*) TextInContainerAfter();
    if( pNext ){
        pNext->PrintFormat( ps, this, bitsToFormat );
    }
    else {
        PrintFormatDifference( ps, bitsToFormat );
    }
    
    if( m_tf & (TF_SERVER|TF_SCRIPT) ){
        PrintLiteral( ps );
    }
    else {
        PrintWithEscapes( ps );
    }

    if( pNext == 0 ) {
        PrintPopFormat( ps, 0 );
    }
}


XP_Bool CEditTextElement::Reduce( CEditBuffer *pBuffer ){
    if( GetLen() == 0 ){
        if( pBuffer->m_pCurrent == this 
                || (IsFirstInContainer()
                        && LeafInContainerAfter() == 0)
            ){
            return FALSE;
        }
        else {
            return TRUE;
        }
    }
    else {
        return FALSE;
    }
}

#ifdef DEBUG
void CEditTextElement::ValidateTree(){
    CEditLeafElement::ValidateTree();
    LO_TextStruct *pText = (LO_TextStruct*) GetLayoutElement();

    if( GetType() != P_TEXT  ){
        XP_ASSERT(FALSE);
    }
#if 0
    if(GetLen() != 0 && pText == 0 ){
        XP_ASSERT(FALSE);
    }
#endif
    if(m_pText && pText ) {
        if (pText->edit_element != this){
            XP_ASSERT(FALSE);
        }
        if (pText->edit_offset != 0){
            XP_ASSERT(FALSE);
        }
    }
    XP_Bool bHasLinkTag = (m_tf & TF_HREF) != 0;
    XP_Bool bHasLinkRef = (m_href != ED_LINK_ID_NONE);
    if( bHasLinkTag != bHasLinkRef ){
        XP_ASSERT(FALSE);
    };
}
#endif	//	DEBUG


//-----------------------------------------------------------------------------
// Insertion routines.
//-----------------------------------------------------------------------------

//
// Split text object into two text objects.  Parent is the same.
//
CEditElement* CEditTextElement::SplitText( int iOffset ){
    CEditTextElement* pNew = new CEditTextElement( 0, m_pText+iOffset );
    m_pText[iOffset] = 0;
    GetParent()->Split( this, pNew, &CEditElement::SplitContainerTest, 0 );
    return  pNew;
}

CEditTextElement* CEditTextElement::CopyEmptyText( CEditElement *pParent ){
    CEditTextElement* pNew = new CEditTextElement(pParent, 0);

    pNew->m_tf = m_tf;
    pNew->m_iFontSize = m_iFontSize;
    pNew->m_color = m_color;
    if( (m_tf & TF_HREF) && m_href != ED_LINK_ID_NONE){
        pNew->SetHREF(m_href);
    }
    if ( m_pFace ) {
        pNew->SetFontFace(m_pFace);
    }
    if ( m_pScriptExtra ) {
        pNew->SetScriptExtra(m_pScriptExtra);
    }
    return pNew;
}

void CEditTextElement::DeleteText(){
    CEditElement *pKill = this;
    CEditElement *pParent;

    do {
        pParent = pKill->GetParent();
        pKill->Unlink();
        delete pKill;
        pKill = pParent;
    } while( BitSet( edt_setCharFormat, pKill->GetType() ) && pKill->GetChild() == 0 );
}

//----------------------------------------------------------------------------
// CEditImageElement
//----------------------------------------------------------------------------

//
// LTNOTE: these should be static functions on CEditImageData..
//
EDT_ImageData* edt_NewImageData(){
    EDT_ImageData *pData = XP_NEW( EDT_ImageData );
    if( pData == 0 ){
        // throw();
        return pData;
    }
    pData->bIsMap = FALSE;
    pData->pUseMap = 0;
    pData->align = ED_ALIGN_DEFAULT;
    pData->pSrc = 0;
    pData->pLowSrc = 0;
    pData->pName = 0;
    pData->pAlt = 0;
    pData->iWidth = 0;
    pData->iHeight = 0;
    pData->bWidthPercent = FALSE;
    pData->bHeightPercent = FALSE;
    pData->iHSpace = 0;
    pData->iVSpace = 0;
    pData->iBorder = -1;
    pData->pHREFData = NULL;
    pData->pExtra = 0;
    return pData;
}

inline char *edt_StrDup( char *pStr ){
    if( pStr ){ 
        return XP_STRDUP( pStr );
    }
    else {
        return 0;
    }
}

EDT_ImageData* edt_DupImageData( EDT_ImageData *pOldData ){
    EDT_ImageData *pData = XP_NEW( EDT_ImageData );

    pData->pUseMap = edt_StrDup(pOldData->pUseMap);
    pData->pSrc = edt_StrDup(pOldData->pSrc);
    pData->pName = edt_StrDup(pOldData->pName);
    pData->pLowSrc = edt_StrDup(pOldData->pLowSrc);
    pData->pAlt = edt_StrDup(pOldData->pAlt);
    pData->pExtra = edt_StrDup(pOldData->pExtra);

    pData->bIsMap = pOldData->bIsMap;
    pData->align = pOldData->align;
    pData->iWidth = pOldData->iWidth;
    pData->iHeight = pOldData->iHeight;
    pData->bWidthPercent = pOldData->bWidthPercent;
    pData->bHeightPercent = pOldData->bHeightPercent;
    pData->iHSpace = pOldData->iHSpace;
    pData->iVSpace = pOldData->iVSpace;
    pData->iBorder = pOldData->iBorder;

    if( pOldData->pHREFData ){
        pData->pHREFData = EDT_DupHREFData( pOldData->pHREFData );
    }
    else {
        pData->pHREFData = 0;
    }
        
    return pData;
}


void edt_FreeImageData( EDT_ImageData *pData ){
    if( pData->pUseMap ) XP_FREE( pData->pUseMap );
    if( pData->pSrc ) XP_FREE( pData->pSrc );
    if( pData->pLowSrc ) XP_FREE( pData->pLowSrc );
    if( pData->pName ) XP_FREE( pData->pName );
    if( pData->pAlt ) XP_FREE( pData->pAlt );
    if( pData->pExtra ) XP_FREE( pData->pExtra );
    if( pData->pHREFData ) EDT_FreeHREFData( pData->pHREFData );
    XP_FREE( pData );
}

//
// Constructors, Streamers..
//
CEditImageElement::CEditImageElement( CEditElement *pParent, PA_Tag* pTag,
                ED_LinkId href ):
            CEditLeafElement( pParent, P_IMAGE ), 
            m_pLoImage(0),
            m_pParams(0),
            m_iHeight(0),
            m_iWidth(0),
            m_iSaveIndex(0),
            m_iSaveLowIndex(0),
            m_href(ED_LINK_ID_NONE),
            m_align( ED_ALIGN_DEFAULT),
            m_bSizeWasGiven(FALSE),
            m_bSizeIsBogus(FALSE)
{
    // must set href before calling SetImageData to allow GetDefaultBorder to work
    if( href != ED_LINK_ID_NONE ){
        SetHREF( href );
    }
    if( pTag ){
        EDT_ImageData *pData = ParseParams( pTag );
        if(  pData->iBorder == -1 ){
            pData->iBorder = GetDefaultBorder();
        }
        SetImageData( pData );
        edt_FreeImageData( pData );
    }
}

CEditImageElement::CEditImageElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer):
            CEditLeafElement(pStreamIn, pBuffer), 
            m_pLoImage(0),
            m_pParams(0), 
            m_href(ED_LINK_ID_NONE),
            m_align(ED_ALIGN_DEFAULT) {
    m_align = (ED_Alignment) pStreamIn->ReadInt();
    m_bSizeWasGiven = (XP_Bool) pStreamIn->ReadInt();
    m_bSizeIsBogus = (XP_Bool) pStreamIn->ReadInt();
    m_iHeight = pStreamIn->ReadInt();
    m_iWidth = pStreamIn->ReadInt();
    m_pParams = pStreamIn->ReadZString();
    char *pHrefString = pStreamIn->ReadZString();
    char *pExtra = pStreamIn->ReadZString();
    if( pHrefString ){
        SetHREF( pBuffer->linkManager.Add( pHrefString, pExtra ));
        XP_FREE( pHrefString );
        if( pExtra ) XP_FREE( pExtra );
    }
}

CEditImageElement::~CEditImageElement(){
    DisconnectLayoutElements((LO_Element*) m_pLoImage);
    if( m_pParams) XP_FREE( m_pParams ); 
    if( m_href != ED_LINK_ID_NONE ) m_href->Release();
}

void CEditImageElement::SetLayoutElement( intn iEditOffset, intn lo_type, 
                        LO_Element* pLoElement ){
    SetLayoutElementHelper(LO_IMAGE, (LO_Element**) &m_pLoImage,
        iEditOffset, lo_type, pLoElement);
}

void CEditImageElement::ResetLayoutElement( intn iEditOffset, 
            LO_Element* pLoElement ){
    ResetLayoutElementHelper((LO_Element**) &m_pLoImage,
        iEditOffset, pLoElement);
}

LO_Element* CEditImageElement::GetLayoutElement(){
    return (LO_Element*)m_pLoImage;
}

void CEditImageElement::StreamOut( IStreamOut *pOut){
    CEditLeafElement::StreamOut( pOut );
    
    pOut->WriteInt( (int32)m_align );
    pOut->WriteInt( m_bSizeWasGiven );
    pOut->WriteInt( m_bSizeIsBogus );
    pOut->WriteInt( m_iHeight );
    pOut->WriteInt( m_iWidth );

    // We need to make the Image Src and LowSrc Parameters absolute.
    // We do this by first converting the parameter string to and Edit
    //  data structure and then making the paths absolute.
    EDT_ImageData *pData = GetImageData();
    CEditBuffer *pBuffer = GetEditBuffer();
    if( pData && pBuffer && pBuffer->GetBaseURL() ){
        char *p;
        if( pData->pSrc ) {
            p = NET_MakeAbsoluteURL( pBuffer->GetBaseURL(), pData->pSrc );
            if( p ){
                XP_FREE( pData->pSrc );
                pData->pSrc = p;
            }
        }
        if( pData->pLowSrc ) {
            p = NET_MakeAbsoluteURL( pBuffer->GetBaseURL(), pData->pLowSrc );
            if( p ){
                XP_FREE( pData->pLowSrc );
                pData->pLowSrc = p;
            }
        }

        // Format the parameters for output.
        p = FormatParams( pData, TRUE );
        pOut->WriteZString( p );
        XP_FREE(p);
        edt_FreeImageData( pData );
    }
    else {
        pOut->WriteZString( m_pParams );
    }


    if( m_href != ED_LINK_ID_NONE ){
        pOut->WriteZString( m_href->hrefStr );
        pOut->WriteZString( m_href->pExtra );
    }
    else {
        pOut->WriteZString( 0 );
        pOut->WriteZString( 0 );
    }
}

EEditElementType CEditImageElement::GetElementType()
{
    return eImageElement;
}

PA_Tag* CEditImageElement::TagOpen( int /* iEditOffset */ ){
    char *pCur = 0;
    PA_Tag *pRetTag = 0;
    PA_Tag *pEndTag = 0;

    if( m_href != ED_LINK_ID_NONE ){
        char *pBuf = PR_smprintf("HREF=%s %s>", edt_MakeParamString(m_href->hrefStr),
            (m_href->pExtra ? m_href->pExtra : "") );        
        edt_AddTag( pRetTag, pEndTag, P_ANCHOR, FALSE, pBuf );
        free( pBuf );
    }

    if( m_pParams ){
        if( SizeIsKnown() ){    
            pCur = PR_smprintf("%s HEIGHT=%ld WIDTH=%ld ", m_pParams, 
                        (long)m_iHeight, (long)m_iWidth );
        }
        else {
            // We don't know the size, but if we don't give a size,
            // and if the image isn't known, we will block and
            // crash. So we put in a bogus size here, and take it
            // out in FinishedLoad iff there's image data available.
            m_bSizeIsBogus = TRUE;
            pCur = PR_smprintf("%s HEIGHT=24 WIDTH=22", m_pParams );
        }
    }        

    // For editing, we don't support baseline, left or right alignment of images.

    if( m_align != ED_ALIGN_DEFAULT && m_align != ED_ALIGN_BASELINE 
                && m_align != ED_ALIGN_LEFT && m_align != ED_ALIGN_RIGHT ){
        pCur = PR_sprintf_append(pCur, "ALIGN=%s ", EDT_AlignString(m_align) );
    }
    
    pCur = PR_sprintf_append(pCur, ">");

    edt_AddTag( pRetTag, pEndTag, P_IMAGE, FALSE, pCur );
    pEndTag->edit_element = this;

    if( m_href != ED_LINK_ID_NONE ){
        edt_AddTag( pRetTag, pEndTag, P_ANCHOR, TRUE );
    }
    free( pCur );
    return pRetTag;
}

void CEditImageElement::PrintOpen( CPrintState *pPrintState ){
    char *pCur = 0;
    char *pAlignStr = "";

    if( m_href != ED_LINK_ID_NONE ){
        char *pS1 = "";
        char *pS2 = "";
        if( m_href->pExtra ){
            pS1 = " ";
            pS2 = m_href->pExtra;
        }
        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( 
            "<A HREF=%s%s%s>", edt_MakeParamString(m_href->hrefStr), pS1, pS2 );
    }

    // Use special versions of params which has BORDER removed if it's the default

    char* pParams = NULL;
    {
        EDT_ImageData* pData = GetImageData();
        pParams = FormatParams(pData, TRUE);
        EDT_FreeImageData(pData);
    }

    if( SizeIsKnown() && ! m_bSizeIsBogus ){
        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<IMG %sHEIGHT=%ld WIDTH=%ld", 
                    (pParams ? pParams : " " ),
                    (long)m_iHeight,
                    (long)m_iWidth );
    }
    else {
        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<IMG %s", 
                    (pParams ? pParams : " " ));
    }

    if ( pParams )
        XP_FREE(pParams);

    if( m_align != ED_ALIGN_DEFAULT && m_align != ED_ALIGN_BASELINE ){
        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( " ALIGN=%s", 
                EDT_AlignString(m_align));
    }

    pPrintState->m_pOut->Write( ">", 1 );
    pPrintState->m_iCharPos++;
    
    if( m_href != ED_LINK_ID_NONE ){
        pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( 
            "</A>" );        
    }

}

static char *imageParams[] = {
    PARAM_ISMAP,
    PARAM_USEMAP,
    PARAM_SRC,
    PARAM_LOWSRC,
    PARAM_NAME,
    PARAM_ALT,
    PARAM_WIDTH,
    PARAM_HEIGHT,
    PARAM_ALIGN,
    PARAM_HSPACE,
    PARAM_VSPACE,
    PARAM_BORDER,
    PARAM_ALIGN,
    0
};

EDT_ImageData* CEditImageElement::ParseParams( PA_Tag *pTag ){
    PA_Block buff;

    EDT_ImageData* pData = edt_NewImageData();

    buff = PA_FetchParamValue(pTag, PARAM_ISMAP);
    if (buff != NULL){
        pData->bIsMap = TRUE;
        PA_FREE(buff);
    }
    pData->pUseMap = edt_FetchParamString( pTag, PARAM_USEMAP );
    pData->pSrc = edt_FetchParamString( pTag, PARAM_SRC );
    pData->pLowSrc = edt_FetchParamString( pTag, PARAM_LOWSRC );
    pData->pName = edt_FetchParamString( pTag, PARAM_NAME );
    pData->pAlt = edt_FetchParamString( pTag, PARAM_ALT );
    
    // Get width dimension from string and parse for "%" Default = 100%
    edt_FetchDimension( pTag, PARAM_WIDTH, 
                        &pData->iWidth, &pData->bWidthPercent,
                        0, FALSE );
    edt_FetchDimension( pTag, PARAM_HEIGHT, 
                        &pData->iHeight, &pData->bHeightPercent,
                        0, FALSE );
    m_bSizeWasGiven = pData->iWidth != 0 && pData->iHeight != 0;

    pData->align = edt_FetchParamAlignment( pTag, ED_ALIGN_BASELINE );

    pData->iHSpace = edt_FetchParamInt( pTag, PARAM_HSPACE, 0 );
    pData->iVSpace = edt_FetchParamInt( pTag, PARAM_VSPACE, 0 );
    pData->iBorder = edt_FetchParamInt( pTag, PARAM_BORDER, -1 );
    pData->pHREFData = 0;

    pData->pExtra = edt_FetchParamExtras( pTag, imageParams );

    return pData;
}

EDT_ImageData* CEditImageElement::GetImageData(){
    EDT_ImageData *pRet;

    // LTNOTE: hackola time.  We don't want TagOpen to return multiple tags
    //  in this use, so we prevent it by faking out the href ID and restoring
    //  it when we are done.  Not real maintainable.

    ED_LinkId saveHref = m_href;
    m_href = ED_LINK_ID_NONE;
    PA_Tag* pTag = TagOpen(0);
    m_href = saveHref;

    pRet = ParseParams( pTag );

    if ( pRet->iBorder < 0 ) {
        pRet->iBorder = GetDefaultBorder();
    }

    if ( m_bSizeIsBogus ) {
        pRet->iWidth = 0; pRet->bWidthPercent = FALSE;
        pRet->iHeight = 0; pRet->bHeightPercent = FALSE;
    }

    if( m_href != ED_LINK_ID_NONE ){
        pRet->pHREFData = m_href->GetData();
    }
    pRet->align = m_align;
    PA_FreeTag( pTag );
    return pRet;
}

inline int edt_strlen(char* pStr){ return pStr ? XP_STRLEN( pStr ) : 0 ; }


//
// border is weird.  If we are in a link the border is default 2, if 
//  we are not in a link, border is default 0.  In order not to foist this
//  weirdness on the user, we are always explicit on our image borders.
//
int32 CEditImageElement::GetDefaultBorder(){
    return ( m_href != ED_LINK_ID_NONE ) ? 2 : 0;
}

void CEditImageElement::SetImageData( EDT_ImageData *pData ){
    char* pCur = FormatParams(pData, FALSE);

    if( m_pParams ){
        XP_FREE( m_pParams );
    }
    m_pParams = pCur;

    m_align = pData->align;
    
    if (pData->iHeight != 0 && pData->iWidth != 0 ) {
        m_bSizeWasGiven = TRUE;
        m_bSizeIsBogus = FALSE;
    }
    else {
        m_bSizeWasGiven = FALSE;
    }
    m_iHeight = pData->iHeight;
    m_iWidth = pData->iWidth;
}

char* CEditImageElement::FormatParams(EDT_ImageData* pData, XP_Bool bForPrinting){
    char *pCur = 0;

    if( pData->bIsMap ){ 
        pCur = PR_sprintf_append( pCur, "ISMAP " );
    }

    if( pData->pUseMap ){
        pCur = PR_sprintf_append( pCur, "USEMAP=%s ", edt_MakeParamString(pData->pUseMap) );
    }

    if( pData->pSrc ){
        pCur = PR_sprintf_append( pCur, "SRC=%s ", edt_MakeParamString( pData->pSrc ) );
    }

    if( pData->pLowSrc ){
        pCur = PR_sprintf_append( pCur, "LOWSRC=%s ", edt_MakeParamString( pData->pLowSrc ) );
    }

    if( pData->pName ){
        pCur = PR_sprintf_append( pCur, "NAME=%s ", edt_MakeParamString( pData->pName ) );
    }

    if( pData->pAlt ){
        pCur = PR_sprintf_append( pCur, "ALT=%s ", edt_MakeParamString( pData->pAlt ) );
    }

    if( pData->iHSpace ){
        pCur = PR_sprintf_append( pCur, "HSPACE=%ld ", (long)pData->iHSpace );
    }

    if( pData->iVSpace ){
        pCur = PR_sprintf_append( pCur, "VSPACE=%ld ", (long)pData->iVSpace );
    }

    // border is weird.  If we are in a link the border is default 2, if 
    // we are not in a link, border is default 0.  When printing, we only write out a
    // border if it's different than the default.
    //
    // Other times, we always write it out because we want it to stay the same even if the
    // default border size changes. Whew!
    {
        if( !bForPrinting || ( pData->iBorder  >=0 && pData->iBorder != GetDefaultBorder())){
            int32 border = pData->iBorder;
            if ( border < 0 )
                border = GetDefaultBorder();
            pCur = PR_sprintf_append( pCur, "BORDER=%ld ", (long)pData->iBorder );
        }
    }

    if( pData->pExtra  != 0 ){
        pCur = PR_sprintf_append( pCur, "%s ", pData->pExtra);
    }
    return pCur;
}


void CEditImageElement::FinishedLoad( CEditBuffer *pBuffer ){
    if( m_pLoImage ){
        if ( ! m_bSizeWasGiven || m_bSizeIsBogus ) {
            // The user didn't specify a size. Look at what we got.
            m_iHeight = pBuffer->m_pContext->convertPixY * m_pLoImage->height;
            m_iWidth = pBuffer->m_pContext->convertPixX * m_pLoImage->width;
            if ( m_pLoImage->il_image ) {
                // If there is image data, believe it.
                if ( m_pLoImage->il_image->height > 0 && m_pLoImage->il_image->width > 0 ) {
                    m_iHeight = pBuffer->m_pContext->convertPixY * m_pLoImage->il_image->height;
                    m_iWidth = pBuffer->m_pContext->convertPixX * m_pLoImage->il_image->width;
                    m_bSizeIsBogus = FALSE;
                }
                else {
                    // This case happens when the user canceled the original browser
                    // load, and then hit the edit button.
                    // The size is bogus.
                    // (The image is a broken link icon (24, 22).)
                    // ToDo: Figure out how to trigger a document reload once the image is
                    // finally loaded.
                    m_bSizeIsBogus = TRUE;
                    m_iHeight = 0;
                    m_iWidth = 0;
                }
            }
        }
    }
}

void CEditImageElement::SetHREF( ED_LinkId id ){
    if( m_href != ED_LINK_ID_NONE ){
        m_href->Release();
    }
    m_href = id;
    if( id != ED_LINK_ID_NONE ){
        id->AddRef();
    }
}

XP_Bool CEditImageElement::SizeIsKnown() {
    return m_iHeight > 0;
}

void CEditImageElement::MaskData( EDT_CharacterData*& pData ){
    if( pData == 0 ){
        pData = GetCharacterData();
        return;
    }
    
    // An image can have an HREF text format
    ED_TextFormat tf = ( m_href == ED_LINK_ID_NONE ) ? 0 : TF_HREF;

    ED_TextFormat differences = pData->values ^ tf;
    if( differences ){
        pData->mask &= ~differences;
        if( (pData->mask & pData->values) & TF_HREF 
                    && (pData->linkId != m_href ) ){
            pData->mask &= ~TF_HREF;
        }
    }
}

EDT_CharacterData* CEditImageElement::GetCharacterData(){
    EDT_CharacterData *pData = EDT_NewCharacterData();
    // We are only interested in HREF?
    // pData->mask = TF_HREF;
    pData->mask = -1;
    if( m_href != ED_LINK_ID_NONE ){
        pData->pHREFData = m_href->GetData();
        pData->values = TF_HREF;
    }
    pData->linkId = m_href;
    return pData;
}
//-----------------------------------------------------------------------------
// CEditHorizRuleElement
//-----------------------------------------------------------------------------


CEditHorizRuleElement::CEditHorizRuleElement( CEditElement *pParent, PA_Tag* pTag ):
            CEditLeafElement( pParent, P_HRULE ), 
            m_pLoHorizRule(0)
            {

    if( pTag ){
        char *locked_buff;
                
        PA_LOCK(locked_buff, char *, pTag->data );
        if( locked_buff ){
            SetTagData( locked_buff );
        }
        PA_UNLOCK(pTag->data);
    }
}

CEditHorizRuleElement::CEditHorizRuleElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer):
            CEditLeafElement(pStreamIn, pBuffer), 
            m_pLoHorizRule(0)
            {
}

CEditHorizRuleElement::~CEditHorizRuleElement(){
    DisconnectLayoutElements((LO_Element*) m_pLoHorizRule);
}


LO_Element* CEditHorizRuleElement::GetLayoutElement(){
    return (LO_Element*)m_pLoHorizRule;
}

void CEditHorizRuleElement::SetLayoutElement( intn iEditOffset, intn lo_type, 
                        LO_Element* pLoElement ){
    SetLayoutElementHelper(LO_HRULE, (LO_Element**) &m_pLoHorizRule,
        iEditOffset, lo_type, pLoElement);
}

void CEditHorizRuleElement::ResetLayoutElement( intn iEditOffset, 
            LO_Element* pLoElement ){
    ResetLayoutElementHelper((LO_Element**) &m_pLoHorizRule,
        iEditOffset, pLoElement);
}

void CEditHorizRuleElement::StreamOut( IStreamOut *pOut ){
    CEditLeafElement::StreamOut( pOut );
}

EEditElementType CEditHorizRuleElement::GetElementType()
{
    return eHorizRuleElement;
}

// Property Getting and Setting Stuff.

EDT_HorizRuleData* CEditHorizRuleElement::NewData(){
    EDT_HorizRuleData *pData = XP_NEW( EDT_HorizRuleData );
    if( pData == 0 ){
        // throw();
        return pData;
    }
    pData->align = ED_ALIGN_CENTER;
    pData->size = DEFAULT_HR_THICKNESS;
    pData->bNoShade = 0;
    pData->iWidth = 100;
    pData->bWidthPercent = TRUE;
    pData->pExtra = 0;
    return pData;
}

void CEditHorizRuleElement::FreeData( EDT_HorizRuleData *pData ){
    if( pData->pExtra ) XP_FREE( pData->pExtra );
    XP_FREE( pData );
}

void CEditHorizRuleElement::SetData( EDT_HorizRuleData *pData ){
    char *pNew = 0;

    if( pData->align == ED_ALIGN_RIGHT || pData->align == ED_ALIGN_LEFT ){
        pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) );
    }
    if( pData->size != DEFAULT_HR_THICKNESS ){
        pNew = PR_sprintf_append( pNew, "SIZE=%ld ", (long)pData->size );
    }
    if( pData->bNoShade ){
        pNew = PR_sprintf_append( pNew, "NOSHADE " );
    }
    if( pData->iWidth ){
        if( pData->bWidthPercent ){
            pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld%%\" ", 
                                      (long)min(100,max(1,pData->iWidth)) );
        }
        else {
            pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld\" ", (long)pData->iWidth );
        }
    }
    if( pData->pExtra){
        pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra );
    }

    pNew = PR_sprintf_append( pNew, ">" );

    SetTagData( pNew );        
    free(pNew);
}

EDT_HorizRuleData* CEditHorizRuleElement::GetData( ){
    EDT_HorizRuleData *pRet;
    PA_Tag* pTag = TagOpen(0);
    pRet = ParseParams( pTag );
    PA_FreeTag( pTag );
    return pRet;
}

static char *hruleParams[] = {
    PARAM_ALIGN,
    PARAM_NOSHADE,
    PARAM_WIDTH,
    PARAM_SIZE,
    0
};

EDT_HorizRuleData* CEditHorizRuleElement::ParseParams( PA_Tag *pTag ){
    EDT_HorizRuleData *pData = NewData();
    ED_Alignment align;
    
    align = edt_FetchParamAlignment( pTag, ED_ALIGN_CENTER );
    if( align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT ){
        pData->align = align;
    }
    // Get width dimension from string and parse for "%" Default = 100%
    edt_FetchDimension( pTag, PARAM_WIDTH, 
                        &pData->iWidth, &pData->bWidthPercent,
                        100, TRUE );
    pData->bNoShade = edt_FetchParamBoolExist( pTag, PARAM_NOSHADE );
    pData->size = edt_FetchParamInt( pTag, PARAM_SIZE, DEFAULT_HR_THICKNESS );
    pData->pExtra = edt_FetchParamExtras( pTag, hruleParams );
    return pData;
}

//-----------------------------------------------------------------------------
// CEditIconElement
//-----------------------------------------------------------------------------
static char *ppIconTags[] = {
    "align=absbotom border=0 src=\"internal-edit-named-anchor\" alt=\"%s\">",
    "align=absbotom border=0 src=\"internal-edit-form-element\">",
    "src=\"internal-edit-unsupported-tag\" alt=\"<%s\"",
    "src=\"internal-edit-unsupported-end-tag\" alt=\"</%s\"",
    "align=absbotom border=0 src=\"internal-edit-java\">",
    "align=absbotom border=0 src=\"internal-edit-plugin\">",
    0
};

CEditIconElement::CEditIconElement( CEditElement *pParent, int32 iconTag, 
                        PA_Tag* pTag )
            :
                CEditLeafElement( pParent, P_IMAGE ), 
                m_originalTagType( pTag ? pTag->type : P_UNKNOWN ),
                m_iconTag( iconTag ),
                m_bEndTag( pTag ? pTag->is_end : 0 ),
                m_pSpoofData(0),
                m_pLoIcon(0)
            {

    if( pTag ){
        char *locked_buff;
                
        PA_LOCK(locked_buff, char *, pTag->data );
        if( locked_buff ){
            char *p = locked_buff;
            if( m_originalTagType != P_ANCHOR 
                    && m_originalTagType != P_UNKNOWN ){

                while( *p == ' ' ) p++;
                char *pSpace = " ";
                if( *p == '>' ) pSpace = "";

                char *pTagData = PR_smprintf("%s%s%s", 
                        EDT_TagString(m_originalTagType), pSpace, p );
                SetTagData( pTagData );
                SetTagData( pTag, pTagData );
                m_originalTagType = P_UNKNOWN;
                free( pTagData );
            }
            else {
                SetTagData( locked_buff );
            }
        }
        PA_UNLOCK(pTag->data);
        SetSpoofData( pTag );
    }
}

CEditIconElement::~CEditIconElement(){
    DisconnectLayoutElements((LO_Element*) m_pLoIcon);
    if( m_pSpoofData ){
        free( m_pSpoofData );
    }
}

CEditIconElement::CEditIconElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer):
            CEditLeafElement(pStreamIn, pBuffer), 
            m_pLoIcon(0)
            {
    m_originalTagType = (TagType) pStreamIn->ReadInt();
    m_iconTag = pStreamIn->ReadInt();
    m_bEndTag = (XP_Bool) pStreamIn->ReadInt();
    m_pSpoofData = pStreamIn->ReadZString();
}

void CEditIconElement::SetLayoutElement( intn iEditOffset, intn lo_type, 
                        LO_Element* pLoElement ){
    SetLayoutElementHelper(LO_IMAGE, (LO_Element**) &m_pLoIcon,
        iEditOffset, lo_type, pLoElement);
}

void CEditIconElement::ResetLayoutElement( intn iEditOffset, 
            LO_Element* pLoElement ){
    ResetLayoutElementHelper((LO_Element**) &m_pLoIcon,
        iEditOffset, pLoElement);
}

void CEditIconElement::StreamOut( IStreamOut *pOut ){
    CEditLeafElement::StreamOut( pOut );
    pOut->WriteInt( m_originalTagType );
    pOut->WriteInt( m_iconTag );
    pOut->WriteInt( m_bEndTag );
    pOut->WriteZString( m_pSpoofData );
}

PA_Tag* CEditIconElement::TagOpen( int /* iEditOffset */ ){
    PA_Tag *pTag = XP_NEW( PA_Tag );
    XP_BZERO( pTag, sizeof( PA_Tag ) );
    SetTagData( pTag, m_pSpoofData );
    return pTag;
}

#define IS_WHITE(p) ((p) == ' ' || (p) == '\r' || (p) == '\n')

//
// Do some hueristic validation to make sure 
//
ED_TagValidateResult CEditIconElement::ValidateTag( char *pData, XP_Bool bNoBrackets ){
    char *p = pData;

    while( *p && IS_WHITE(*p) ){
        p++;
    }
    if( !bNoBrackets ){
        if( *p != '<' ){
            return ED_TAG_UNOPENED;
        }
        else {
            p++;
        }
        if( *p == '/' ){
            p++;
        }

        if( IS_WHITE(*p) ){
            return ED_TAG_TAGNAME_EXPECTED;
        }
    }

    // Is this a comment?
    XP_Bool isComment = p[0] == '!' && p[1] == '-' && p[2] == '-';

    if ( isComment ) {
        char* start = p;
        while( *p ){
            p++;
        }
        if ( ! bNoBrackets ) {
            --p;
        }

        XP_Bool endComment = (p >= start + 6) && p[-2] == '-' && p[-1] == '-';
        if ( ! endComment ) {
            return ED_TAG_PREMATURE_CLOSE; // ToDo: Use a specific error message
        }
    }
    else {
        // look for unterminated strings.
        while( *p && *p != '>' ){
            if( *p == '"' || *p == '\'' ){
                char quote = *p++;
                while( *p && *p != quote ){
                    p++;
                }
                if( *p == 0 ){
                    return ED_TAG_UNTERMINATED_STRING;
                }
            }
            p++;
        }
    }
    if( bNoBrackets ){
        if( *p == 0 ){
            return ED_TAG_OK;
        }
        else {
            XP_ASSERT( *p == '>' );
            return ED_TAG_PREMATURE_CLOSE;
        }
    }   
    else if( *p == '>' ){
        p++;
        while( IS_WHITE( *p ) ){
            p++;
        }
        if( *p != 0 ){
            return ED_TAG_PREMATURE_CLOSE;
        }
        else {
            return ED_TAG_OK;
        }
    }
    else {
        XP_ASSERT( *p == 0 );
        return ED_TAG_UNCLOSED;
    }
}

//
// Restore the original tag type so we save the right tag type when we output
//  we print.
//
void CEditIconElement::PrintOpen( CPrintState *pPrintState ){
    if( m_originalTagType != P_UNKNOWN ){
        TagType tSave = GetType();
        SetType( m_originalTagType );
        CEditLeafElement::PrintOpen( pPrintState );
        SetType( tSave );
    }
    else {
        if( m_bEndTag ){
            pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "</%s", 
                GetTagData());
        }
        else {
            pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s", 
                GetTagData());
        }
    }
}

void CEditIconElement::PrintEnd( CPrintState *pPrintState ){
    if( m_originalTagType != P_UNKNOWN ){
        TagType tSave = GetType();
        SetType( m_originalTagType );
        CEditLeafElement::PrintEnd( pPrintState );
        SetType( tSave );
    }
}

char* CEditIconElement::GetData(){
    int iLen = XP_STRLEN( GetTagData() );
    char *p = (char*)XP_ALLOC( iLen+3 );
    char *pRet = p;
    *p++ = '<';
    if( m_bEndTag ){
        *p++ = '/';
    }
    XP_STRCPY( p, GetTagData() );
    return pRet;
}

XP_Bool IsHTMLCommentTag(char* string) {
    return string && string[0] == '!'
        && string[1] == '-'
        && string[2] == '-';
}

void CEditIconElement::SetSpoofData( PA_Tag* pTag ){
    
    if( m_iconTag == EDT_ICON_UNSUPPORTED_TAG 
            || m_iconTag == EDT_ICON_UNSUPPORTED_END_TAG ){

        char *pTagString;
        PA_LOCK(pTagString, char *, pTag->data );
    
        char *pData = PR_smprintf( ppIconTags[m_iconTag], 
                    edt_QuoteString( pTagString ));
        
        XP_Bool bDimensionFound = FALSE;
        XP_Bool bComment = m_originalTagType == P_UNKNOWN &&
            IsHTMLCommentTag(pTagString);

        if ( ! bComment ) {
            int32 height;
            int32 width;
            XP_Bool bPercent;
        
            edt_FetchDimension( pTag, PARAM_HEIGHT, &height, &bPercent, -1, TRUE );
            if( height != -1 ){
                pData = PR_sprintf_append( pData, " HEIGHT=%d%s", height, 
                            (bPercent ? "%" : "" ) );
                bDimensionFound = TRUE;
            }

            edt_FetchDimension( pTag, PARAM_WIDTH, &width, &bPercent, -1, TRUE );
            if( width != -1 ){
                pData = PR_sprintf_append( pData, " WIDTH=%d%s", width, 
                        (bPercent ? "%" : "" ) );
                bDimensionFound = TRUE;
            }
        }

        if( bDimensionFound ){
            pData = PR_sprintf_append(pData, " BORDER=2>");
        }
        else {
            pData = PR_sprintf_append(pData, " BORDER=0 ALIGN=ABSBOTTOM>");
        }

        if( m_pSpoofData ){
            free( m_pSpoofData );
        }
        m_pSpoofData = pData;
        PA_UNLOCK(pTag->data);
    }
    else {
        m_pSpoofData = XP_STRDUP( ppIconTags[m_iconTag] );
    }

}

void CEditIconElement::SetSpoofData( char* pData ){
    char *pNewData = PR_smprintf( ppIconTags[m_iconTag], 
                edt_QuoteString( pData ));
    if( m_pSpoofData ){
        XP_FREE( m_pSpoofData );
    }
    m_pSpoofData = pNewData;
}

void CEditIconElement::SetData( char *pData ){

    // If you assert here, you aren't validating your tag.
    XP_ASSERT( pData[0] == '<' );
    if( *pData != '<' )
        return;

    pData++;

    if( *pData == '/' ){
        pData++;
        m_bEndTag = TRUE;
    }
    SetTagData( pData );


    // Build up a tag that we can fetch parameter strings from.
    PA_Tag *pTag = XP_NEW( PA_Tag );
    XP_BZERO( pTag, sizeof( PA_Tag ) );
    SetTagData( pTag, pData );
    pTag->is_end = m_bEndTag;

    SetSpoofData( pTag );
    PA_FreeTag( pTag );
}

//
// We need to convert a tag handed to us by the parser into a tag we can
//  display in the editor.  In order to do this, we 'morph' the tag by swaping
//  the contents with desired tag.  We need an additional tag for swap space.
//  All this is done to insure that the memory location of 'pTag' doesn't 
//  change.  Perhaps this routine should be implemented at the CEditElement
//  layer.
//             
void CEditIconElement::MorphTag( PA_Tag *pTag ){
    PA_Tag *pNewTag = TagOpen(0);
    PA_Tag tempTag;

    tempTag = *pTag;
    *pTag = *pNewTag;
    *pNewTag = tempTag;

    PA_FreeTag( pNewTag );
}             
   
   
//-----------------------------------------------------------------------------
// CEditTargetElement
//-----------------------------------------------------------------------------
CEditTargetElement::CEditTargetElement(CEditElement *pParent, PA_Tag* pTag)
        :
            CEditIconElement( pParent, EDT_ICON_NAMED_ANCHOR,  pTag )
        {
        m_originalTagType = P_ANCHOR;
        SetData( GetData() );
}

CEditTargetElement::CEditTargetElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer)
        :
            CEditIconElement( pStreamIn, pBuffer )
        {
}

void CEditTargetElement::StreamOut( IStreamOut *pOut){
    CEditIconElement::StreamOut( pOut );
}

PA_Tag* CEditTargetElement::TagOpen( int iEditOffset ){
    // We need to create the actual anchor tag set.
    return CEditIconElement::TagOpen( iEditOffset );
}

void CEditTargetElement::SetData( char* pData ){
    char *pNew = 0;

    pNew = PR_sprintf_append( pNew, "NAME=%s>", edt_MakeParamString( pData ) );
    CEditIconElement::SetSpoofData( pData );        
    SetTagData( pNew );        
    free(pNew);
}

char* CEditTargetElement::GetData( ){
    // Get the actual tag data, not the faked up stuff at the Icon layer.
    PA_Tag *pTag = CEditLeafElement::TagOpen(0);
    char *pName = edt_FetchParamString( pTag, PARAM_NAME );
    PA_FreeTag( pTag );
    return pName;
}

//-----------------------------------------------------------------------------
// CEditBreakElement
//-----------------------------------------------------------------------------

CEditBreakElement::CEditBreakElement( CEditElement *pParent, PA_Tag* pTag ):
    CEditLeafElement( pParent, P_LINEBREAK ),
    m_pLoLinefeed(0)
{
    if( pTag ){
        char *locked_buff;
                
        PA_LOCK(locked_buff, char *, pTag->data );
        if( locked_buff ){
            SetTagData( locked_buff );
        }
        PA_UNLOCK(pTag->data);
    }
}

CEditBreakElement::CEditBreakElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer):
    CEditLeafElement(pStreamIn, pBuffer),
    m_pLoLinefeed(0)
{
}

CEditBreakElement::~CEditBreakElement(){
    DisconnectLayoutElements((LO_Element*) m_pLoLinefeed);
}


void CEditBreakElement::StreamOut( IStreamOut *pOut){
    CEditLeafElement::StreamOut( pOut );
}

EEditElementType CEditBreakElement::GetElementType(){
    return eBreakElement;
}

void CEditBreakElement::SetLayoutElement( intn iEditOffset, intn lo_type, 
                        LO_Element* pLoElement ){
    SetLayoutElementHelper(LO_LINEFEED, (LO_Element**) &m_pLoLinefeed,
        iEditOffset, lo_type, pLoElement);
}

void CEditBreakElement::ResetLayoutElement( intn iEditOffset, 
            LO_Element* pLoElement ){
    ResetLayoutElementHelper((LO_Element**) &m_pLoLinefeed,
        iEditOffset, pLoElement);
}

LO_Element* CEditBreakElement::GetLayoutElement(){
    return (LO_Element*)m_pLoLinefeed;
}

XP_Bool CEditBreakElement::GetLOElementAndOffset( ElementOffset iEditOffset, XP_Bool /* bStickyAfter */,
            LO_Element*& pRetElement, 
            int& pLayoutOffset ){ 
    pLayoutOffset = 0; 
    pRetElement = (LO_Element*)m_pLoLinefeed;
    pLayoutOffset = iEditOffset;
    return TRUE;
}

//
// if we are within PREFORMAT (or the like) breaks are returns.
//
void CEditBreakElement::PrintOpen( CPrintState *ps ){
    CEditElement *pPrev = PreviousLeafInContainer();
    while( pPrev && pPrev->IsA(P_LINEBREAK) ){
        pPrev = pPrev->PreviousLeafInContainer();
    }
    CEditElement *pNext = LeafInContainerAfter();
    while( pNext && pNext->IsA(P_LINEBREAK) ){
        pNext = pNext->LeafInContainerAfter();
    }
    XP_Bool bScriptBefore = pPrev 
                && pPrev->IsA(P_TEXT) 
                && (pPrev->Text()->m_tf & (TF_SERVER|TF_SCRIPT));
    XP_Bool bScriptAfter = pNext && pNext->IsA(P_TEXT) && (pNext->Text()->m_tf & (TF_SERVER|TF_SCRIPT));
    
    if ( !bScriptBefore && bScriptAfter ){
        // Don't print <BR>s before <SCRIPT> tags. This swallows both legal <BR> tags,
        // and also swallows '\n' at the start of scripts. If we didn't do this,
        // then we would turn <SCRIPT>\nFoo into <BR><SCRIPT>Foo.
        // The case of a legitimate <BR> before a <SCRIPT> tag is much rarer than the
        // case of a \n just after the <SCRIPT> tag. So we err on the side that lets
        // more pages work.
        // Take this code out when text styles are extended to all leaf elements.
        return;
    }
    if( InFormattedText() 
            || bScriptBefore ){
        ps->m_pOut->Write( "\n", 1 );
        ps->m_iCharPos = 0;
    }
    else {
        CEditLeafElement::PrintOpen( ps );
    }
}

#endif