/* AbiSource
 * 
 * Copyright (C) 2002 Dom Lachowicz <cinamod@hotmail.com>
 * Copyright (C) 2004 Robert Staudinger <robsta@stereolyzer.net>
 * Copyright (C) 2005 Daniel d'Andrada T. de Carvalho
 * <daniel.carvalho@indt.org.br>
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
 * 02111-1307, USA.
 */

// Class definition include
#include "OD_TextContent_ListenerState.h"

// Internal includes
#include "OD_Office_Styles.h"
#include "OD_Style_List.h"
#include "OD_Style_Style.h"
#include "OD_Style_MasterPage.h"
#include "OD_ListenerStateAction.h"
#include "OD_ListLevelStyle.h"
#include "OD_NotesConfiguration.h"
#include "OD_StartTag.h"
#include "OD_ElementStack.h"
#include "OD_TableOfContent_ListenerState.h"

// AbiWord includes
#include <ut_misc.h>
#include <pd_Document.h>
#include <pf_Frag_Strux.h>


/**
 * Constructor
 */
OD_TextContent_ListenerState::OD_TextContent_ListenerState (
                PD_Document* pDocument,
                OD_Office_Styles* pStyles,
                OD_ElementStack& rElementStack)
                : OD_ListenerState("TextContent", rElementStack),
                  m_pAbiDocument ( pDocument ),
                  m_pStyles(pStyles),
                  m_bAcceptingText(false),
                  m_bInSection(false),
                  m_bOnContentStream(false),
                  m_pCurrentListStyle(NULL),
                  m_listLevel(0),
                  m_alreadyDefinedAbiParagraphForList(false),
                  m_elementParsingLevel(0),
                  m_pendingNoteAnchorInsertion(false),
                  m_pCurrentTOCParser(NULL),
                  m_addedMasterPageSectionProps(false)
{
    UT_ASSERT(m_pAbiDocument);
    UT_ASSERT(m_pStyles);
}


/**
 * Destructor
 */
OD_TextContent_ListenerState::~OD_TextContent_ListenerState() {

    UT_ASSERT(m_tablesOfContentProps.getItemCount()==0);
   
    if (m_tablesOfContentProps.getItemCount() > 0) {
        UT_VECTOR_PURGEALL(UT_UTF8String*, m_tablesOfContentProps);
        m_tablesOfContentProps.clear();
    }
}


/**
 * Called when the XML parser finds a start element tag.
 * 
 * @param pName The name of the element.
 * @param ppAtts The attributes of the parsed start tag.
 */
void OD_TextContent_ListenerState::startElement (const XML_Char* pName,
                                          const XML_Char** ppAtts,
                                          OD_ListenerStateAction& rAction)
{
    if (!UT_strcmp(pName, "text:section" )) {

        const XML_Char* pStyleName = UT_getAttribute ("text:style-name", ppAtts);
        const OD_Style_Style* pStyle = m_pStyles->getSectionStyle(pStyleName,
                                                        m_bOnContentStream);
                                                        
        UT_UTF8String props;
        
        pStyle->getAbiPropsAttrString(props);

        _insureInSection(props.utf8_str());

    } else if (!UT_strcmp(pName, "text:p" )) {
        
        // It's so big that it deserves its own function.
        _startParagraphElement(pName, ppAtts, rAction);
        
    } else if (!UT_strcmp(pName, "text:h" )) {

        const XML_Char* pStyleName;
        const XML_Char* pOutlineLevel;
        const OD_Style_Style* pStyle;
        
        pStyleName = UT_getAttribute("text:style-name", ppAtts);
        UT_ASSERT(pStyleName);
        
        pOutlineLevel = UT_getAttribute("text:outline-level", ppAtts);
        if (pOutlineLevel == NULL) {
            // Headings without a level attribute are assumed to
            // be at level 1.
            pOutlineLevel = "1";
        }
        
        pStyle = m_pStyles->getParagraphStyle(pStyleName, m_bOnContentStream);
        UT_ASSERT(pStyle);
        
        m_headingStyles.ins(UT_UTF8String(pOutlineLevel),
                            pStyle->getDisplayName());

        
        // It's so big that it deserves its own function.
        _startParagraphElement(pName, ppAtts, rAction);
        
    } else if (!UT_strcmp(pName, "text:s")) {
        // A number of consecutive white-space characters.
        
        const XML_Char* pSpaceCount;
        UT_uint32 spaceCount, i;
        UT_UCS4String string;
        
        pSpaceCount = UT_getAttribute("text:c", ppAtts);
        
        if (pSpaceCount) {
            i = sscanf(pSpaceCount, "%d", &spaceCount);
            UT_ASSERT(i==1);
        } else {
            // From the OpenDocument specification:
            // "A missing text:c attribute is interpreted as meaning a
            // single SPACE character."
            spaceCount = 1;
        }
        
        
        // TODO: A faster (wiser) implementation can be done, I think. (Daniel d'Andrada)
        string.clear();
        for (i=0; i<spaceCount; i++) {
            string += " ";
        }
        
        // Write the text that has not been written yet.
        // Otherwise the spaces will appear in the wrong position.
        _flush();
        
        m_pAbiDocument->appendSpan(string.ucs4_str(), string.size());
        
    } else if (!UT_strcmp(pName, "text:tab")) {
        // A tab character.

        UT_UCS4String string = "\t";
       
        // Write the text that has not been written yet.
        // Otherwise the spaces will appear in the wrong position.
        _flush();
        
        m_pAbiDocument->appendSpan(string.ucs4_str(), string.size());
        
    } else if (!UT_strcmp(pName, "text:table-of-content")) {
        
        _flush ();
        _insureInBlock(NULL);
        
        UT_ASSERT(m_pCurrentTOCParser == NULL);
        
        m_pCurrentTOCParser = new OD_TableOfContent_ListenerState(
            m_pAbiDocument, m_pStyles, m_rElementStack);
            
        rAction.pushState(m_pCurrentTOCParser, false);

    } else if (!UT_strcmp(pName, "text:span")) {
        // Write all text that is between the last element tag and this
        // <text:span>
        _flush ();

        const XML_Char* pStyleName = UT_getAttribute("text:style-name", ppAtts);
        const OD_Style_Style* pStyle;
        
        if (!pStyleName) {
            // I haven't seen default styles for "text" family.
            UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
            
        } else {
            pStyle = m_pStyles->getTextStyle(pStyleName, m_bOnContentStream);
            
            if (pStyle) {
            
                const XML_Char* ppStyAttr[3];
                bool ok;
                UT_UTF8String props;
                
                if (pStyle->isAutomatic()) {
                    pStyle->getAbiPropsAttrString(props);
                    
                    // It goes "hardcoded"
                    ppStyAttr[0] = "props";
                    ppStyAttr[1] = props.utf8_str();
                    ppStyAttr[2] = 0;
                } else {
                    ppStyAttr[0] = "style";
                    ppStyAttr[1] = pStyle->getDisplayName().utf8_str();
                    ppStyAttr[2] = 0;                
                }
                
                _pushInlineFmt(ppStyAttr);
                ok = m_pAbiDocument->appendFmt(&m_vecInlineFmt);
                UT_ASSERT(ok);
                
            } else {
                // We just ignore this <text:span>.
            }
        }
        
    } else if (!UT_strcmp(pName, "text:line-break")) {
        
        m_charData += UCS_LF;
        _flush ();
        
    } else if (!UT_strcmp(pName, "text:a")) {
        
        const XML_Char * xlink_atts[3];
        xlink_atts[0] = "xlink:href";
        xlink_atts[1] = UT_getAttribute("xlink:href", ppAtts);
        xlink_atts[2] = 0;
        m_pAbiDocument->appendObject(PTO_Hyperlink, xlink_atts);
        
    } else if (!UT_strcmp(pName, "text:bookmark")) {
        
        _flush ();
        const XML_Char * pName = UT_getAttribute ("text:name", ppAtts);
        _insertBookmark (pName, "start");
        _insertBookmark (pName, "end");
        
    } else if (!UT_strcmp(pName, "text:date") ||
            !UT_strcmp(pName, "text:time") ||
            !UT_strcmp(pName, "text:page-number") ||
            !UT_strcmp(pName, "text:page-count") ||
            !UT_strcmp(pName, "text:file-name") ||
            !UT_strcmp(pName, "text:paragraph-count") ||
            !UT_strcmp(pName, "text:word-count") ||
            !UT_strcmp(pName, "text:character-count") ||
            !UT_strcmp(pName, "text:initial-creator") ||
            !UT_strcmp(pName, "text:author-name") ||
            !UT_strcmp(pName, "text:description") ||
            !UT_strcmp(pName, "text:keywords") ||
            !UT_strcmp(pName, "text:subject") ||
            !UT_strcmp(pName, "text:title")) {
                
        _flush ();

        const XML_Char * type = "";
        if(!UT_strcmp(pName, "text:date"))
            type = "date_ddmmyy";
        else if(!UT_strcmp(pName, "text:time"))
            type = "time";
        else if(!UT_strcmp(pName, "text:page-number"))
            type = "page_number";
        else if(!UT_strcmp(pName, "text:page-count"))
            type = "page_count";
        else if(!UT_strcmp(pName, "text:file-name"))
            type = "file_name";
        else if(!UT_strcmp(pName, "text:paragraph-count"))
            type = "para_count";
        else if(!UT_strcmp(pName, "text:word-count"))
            type = "word_count";
        else if(!UT_strcmp(pName, "text:character-count"))
            type = "char_count";
        else if(!UT_strcmp(pName, "text:initial-creator") || !UT_strcmp(pName, "text:author-name"))
            type = "meta_creator";
        else if(!UT_strcmp(pName, "text:description"))
            type = "meta_description";
        else if(!UT_strcmp(pName, "text:keywords"))
            type = "meta_keywords";
        else if(!UT_strcmp(pName, "text:subject"))
            type = "meta_subject";
        else if(!UT_strcmp(pName, "text:title"))
            type = "meta_title";

        const XML_Char *field_fmt[3];
        field_fmt[0] = "type";
        field_fmt[1] = type;
        field_fmt[2] = 0;
        m_pAbiDocument->appendObject(PTO_Field, (const XML_Char**)field_fmt);
        m_bAcceptingText = false;
        
    } else if (!UT_strcmp(pName, "style:header") ||
               !UT_strcmp(pName, "style:footer") ||
               !UT_strcmp(pName, "style:header-left") ||
               !UT_strcmp(pName, "style:footer-left")) {
                
        UT_ASSERT(m_elementParsingLevel == 0);
                
        // We are inside a header/footer so, there is already a section defined
        // on the AbiWord document.
        m_bInSection = true;
        m_bOnContentStream = false;
        
    } else if (!UT_strcmp(pName, "office:text")) {
        UT_ASSERT(m_elementParsingLevel == 0);
        m_bOnContentStream = true;
        
    } else if (!UT_strcmp(pName, "text:list")) {
        
        if (m_pCurrentListStyle != NULL) {
            m_listLevel++;
        } else {
            const XML_Char* pVal;
            
            pVal = UT_getAttribute("text:style-name", ppAtts);
            
            
            
            m_pCurrentListStyle = m_pStyles->getList(pVal);
            UT_ASSERT(m_pCurrentListStyle != NULL);
            
            m_listLevel = 1;
        }
        
    } else if (!UT_strcmp(pName, "text:list-item")) {
        m_alreadyDefinedAbiParagraphForList = false;
        
    } else if (!UT_strcmp(pName, "draw:frame")) {
        
        if (!UT_strcmp(m_rElementStack.getStartTag(0)->getName(), "text:p")) {
            // A paragraph anchored frame.
            // It's postponed because AbiWord uses paragraph frames *after* the
            // paragraph itself but OpenDocument uses *before* its content.
            rAction.postponeElementParsing("Frame");
            
        } else if (!UT_strcmp(m_rElementStack.getStartTag(0)->getName(), "office:text")) {
            
            // A page anchored frame.
            // It's postponed because in AbiWord there must be at least one
            // paragraph defined before any frame definition.
            rAction.postponeElementParsing("Frame");
            
        } else {
            UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
            // TODO: Figure out what to do then.
        }
        
    } else if (!UT_strcmp(pName, "draw:text-box")) {
        UT_ASSERT(m_elementParsingLevel == 0);
        
        // We're inside a text-box, parsing its text contents.
        m_bInSection = true;
        m_bOnContentStream = true;
        
    } else if (!UT_strcmp(pName, "table:table")) {
        _insureInSection();
        rAction.pushState("Table");
        
    } else if (!UT_strcmp(pName, "table:table-cell")) {
        UT_ASSERT(m_elementParsingLevel == 0);
        
        // We're inside a table cell, parsing its text contents.
        m_bInSection = true;
        m_bOnContentStream = true;
        
    }  else if (!UT_strcmp(pName, "text:note")) {

        _flush();
        m_bAcceptingText = false;
        
    } else if (!UT_strcmp(pName, "text:note-body")) {

        const XML_Char* ppAtts[10];
        bool ok;
        UT_uint32 id;
        const OD_NotesConfiguration* pNotesConfig;
        const OD_Style_Style* pStyle = NULL;
        const UT_UTF8String* pCitationStyleName;
        UT_uint8 i;
        bool isFootnote;
        const XML_Char* pNoteClass;
        
        pNoteClass = m_rElementStack.getStartTag(0)->getAttributeValue("text:note-class");
        UT_ASSERT(pNoteClass != NULL);
        
        if (!UT_strcmp(pNoteClass, "footnote")) {
            isFootnote = true;
        } else if (!UT_strcmp(pNoteClass, "endnote")) {
            isFootnote = false;
        } else {
            UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
            // Unrecognized note class.
        }
        
        if (isFootnote) {
            id = m_pAbiDocument->getUID(UT_UniqueId::Footnote);
        } else {
            id = m_pAbiDocument->getUID(UT_UniqueId::Endnote);
        }
        UT_UTF8String_sprintf(m_currentNoteId, "%d", id);
        
        pNotesConfig = m_pStyles->getNotesConfiguration(pNoteClass);
        UT_ASSERT(pNotesConfig);
        
        pCitationStyleName = pNotesConfig->getCitationStyleName();
        
        if (!pCitationStyleName->empty()) {
            pStyle = m_pStyles->getTextStyle(pCitationStyleName->utf8_str(),
                        m_bOnContentStream);
        }

        i = 0;
        ppAtts[i++] = "type";
        if (isFootnote) {
            ppAtts[i++] = "footnote_ref";
            ppAtts[i++] = "footnote-id";
        } else {
            ppAtts[i++] = "endnote_ref";
            ppAtts[i++] = "endnote-id";
        }
        ppAtts[i++] = m_currentNoteId.utf8_str();
        if (!pCitationStyleName->empty() && pStyle != NULL) {
            ppAtts[i++] = "style";
            ppAtts[i++] = pStyle->getDisplayName().utf8_str();
        }
        ppAtts[i++] = "props";
        ppAtts[i++] = "text-position:superscript";
        ppAtts[i] = 0;
        
        ok = m_pAbiDocument->appendObject(PTO_Field, ppAtts);
        UT_ASSERT(ok);
        
        if (isFootnote) {
            ppAtts[0] = "footnote-id";
        } else {
            ppAtts[0] = "endnote-id";
        }
        ppAtts[1] = m_currentNoteId.utf8_str();
        ppAtts[2] = 0;
        
        if (isFootnote) {
            ok = m_pAbiDocument->appendStrux(PTX_SectionFootnote, ppAtts);
        } else {
            ok = m_pAbiDocument->appendStrux(PTX_SectionEndnote, ppAtts);
        }
        UT_ASSERT(ok);
        
        m_pendingNoteAnchorInsertion = true;
    }
    
    m_elementParsingLevel++;
}


/**
 * Called when an "end of element" tag is parsed (like <myElementName/>)
 * 
 * @param pName The name of the element
 */
void OD_TextContent_ListenerState::endElement (const XML_Char* pName,
                                               OD_ListenerStateAction& rAction)
{
    UT_ASSERT(m_elementParsingLevel >= 0);
    
    if (!UT_strcmp(pName, "text:table-of-content")) {
        
        m_tablesOfContent.addItem( m_pCurrentTOCParser->getTOCStrux() );
        m_tablesOfContentProps.addItem( new UT_UTF8String(m_pCurrentTOCParser->getProps()) );
        DELETEP(m_pCurrentTOCParser);
        
    } else if (!UT_strcmp(pName, "text:section" )) {

        m_bInSection = false;

    } else if (!UT_strcmp(pName, "text:p" ) || !UT_strcmp(pName, "text:h" )) {
        
        _flush ();
        m_bAcceptingText = false;
        
        if (!m_rElementStack.hasElement("text:note-body")) {
            // Footnotes/endnotes can't have frames.
            
            // Bring back the possible postponed parsing of a <draw:frame>
            rAction.bringUpMostRecentlyPostponedElement("Frame", true);
        }
        
    } else if (!UT_strcmp(pName, "text:span")) {
        
        _flush ();
        _popInlineFmt();
        m_pAbiDocument->appendFmt(&m_vecInlineFmt);
        
    } else if (!UT_strcmp(pName, "text:a")) {
        
        _flush ();
        m_pAbiDocument->appendObject(PTO_Hyperlink, NULL);
        
    } else if (!UT_strcmp(pName, "text:date") ||
        !UT_strcmp(pName, "text:time") ||
        !UT_strcmp(pName, "text:page-number") ||
        !UT_strcmp(pName, "text:page-count") ||
        !UT_strcmp(pName, "text:file-name") ||
        !UT_strcmp(pName, "text:paragraph-count") ||
        !UT_strcmp(pName, "text:word-count") ||
        !UT_strcmp(pName, "text:character-count") ||
        !UT_strcmp(pName, "text:initial-creator") ||
        !UT_strcmp(pName, "text:author-name") ||
        !UT_strcmp(pName, "text:description") ||
        !UT_strcmp(pName, "text:keywords") ||
        !UT_strcmp(pName, "text:subject") ||
        !UT_strcmp(pName, "text:title")) {
            
        m_bAcceptingText = true;
        
    } else if (!UT_strcmp(pName, "office:text")) {

        UT_ASSERT(m_elementParsingLevel == 1);        
        UT_ASSERT(m_bOnContentStream);
        
        // We were inside a <office:text> element.
        
        // That's it, we can't have anymore <text:h> elements.
        // So, let's define the heading styles on all Abi TOCs (<toc> struxs).
        _defineAbiTOCHeadingStyles();
        
        UT_VECTOR_PURGEALL(UT_UTF8String*, m_tablesOfContentProps);
        m_tablesOfContentProps.clear();        
        
        // We can now bring up the postponed parsing (headers/footers and
        // page-anchored frames)
        rAction.bringUpPostponedElements(false);
        
    } else if (!UT_strcmp(pName, "style:header") ||
               !UT_strcmp(pName, "style:footer") ||
               !UT_strcmp(pName, "style:header-left") ||
               !UT_strcmp(pName, "style:footer-left")) {

        UT_ASSERT(m_elementParsingLevel == 1);
        UT_ASSERT(!m_bOnContentStream);
        
        // We were inside a <style:header/footer> element.
        rAction.popState();
        
    } else if (!UT_strcmp(pName, "text:list")) {
        m_listLevel--;
        if (m_listLevel == 0) {
            m_pCurrentListStyle = NULL;
        }
        
    } else if (!UT_strcmp(pName, "draw:text-box")) {
        UT_ASSERT(m_elementParsingLevel == 1);
        
        // We were inside a <draw:text-box> element.
        rAction.popState();
        
    } else if (!UT_strcmp(pName, "table:table-cell")) {
        UT_ASSERT(m_elementParsingLevel == 1);
        
        // We were inside a <table:table-cell> element.
        rAction.popState();
        
    } else if (!UT_strcmp(pName, "text:note-body")) {
        bool ok;
        const XML_Char* pNoteClass;
        
        pNoteClass = m_rElementStack.getStartTag(1)->getAttributeValue("text:note-class");
        UT_ASSERT(pNoteClass != NULL);
        
        if (!UT_strcmp(pNoteClass, "footnote")) {
            ok = m_pAbiDocument->appendStrux(PTX_EndFootnote, NULL);
        } else if (!UT_strcmp(pNoteClass, "endnote")) {
            ok = m_pAbiDocument->appendStrux(PTX_EndEndnote, NULL);
        } else {
            UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
            // Unrecognized note class.
        }
        
        UT_ASSERT(ok);

    } else if (!UT_strcmp(pName, "text:note")) {
        UT_ASSERT(!m_pendingNoteAnchorInsertion);
        
        m_pendingNoteAnchorInsertion = false;
        m_currentNoteId.clear();

        // Back to paragraph text.
        m_bAcceptingText = true;        
    }
    
    m_elementParsingLevel--;
}


/**
 * 
 */
void OD_TextContent_ListenerState::charData (
                            const XML_Char* pBuffer, int length)
{
    if (pBuffer && length && m_bAcceptingText) {
        m_charData += UT_UCS4String (pBuffer, length, true);
    }
}


/**
 * 
 */
void OD_TextContent_ListenerState::_insertBookmark (const XML_Char* pName,
                                             const XML_Char* pType)
{
    const XML_Char* pPropsArray[5];
    pPropsArray[0] = (XML_Char *)"name";
    pPropsArray[1] = pName;
    pPropsArray[2] = (XML_Char *)"type";
    pPropsArray[3] = pType;
    pPropsArray[4] = 0;
    m_pAbiDocument->appendObject (PTO_Bookmark, pPropsArray);
}


/**
 * 
 */
void OD_TextContent_ListenerState::_flush ()
{
    if (m_charData.size () > 0 && m_bAcceptingText) {
        m_pAbiDocument->appendSpan (m_charData.ucs4_str(), m_charData.size ());
        m_charData.clear ();
    } 
}


/**
 * 
 */
bool OD_TextContent_ListenerState::_pushInlineFmt(const XML_Char ** atts)
{
    UT_uint32 start = m_vecInlineFmt.getItemCount()+1;
    UT_uint32 k;
    XML_Char* p;
    
    for (k=0; (atts[k]); k++)
    {
        if (!UT_XML_cloneString(p,atts[k])) {
            return false;
        }
        
        if (m_vecInlineFmt.addItem(p)!=0) {
            return false;
        }
        
    }
    
    if (!m_stackFmtStartIndex.push(start)) {
        return false;
    }
        
    return true;
}


/**
 * 
 */
void OD_TextContent_ListenerState::_popInlineFmt(void)
{
    UT_sint32 start;
    
    if (!m_stackFmtStartIndex.pop(&start))
        return;
        
    UT_uint32 k;
    UT_uint32 end = m_vecInlineFmt.getItemCount();
    const XML_Char* p;
    
    for (k=end; k>=start; k--) {
        
        p = (const XML_Char *)m_vecInlineFmt.getNthItem(k-1);
        m_vecInlineFmt.deleteNthItem(k-1);
        
        if (p)
            free((void *)p);
    }
}


/**
 * Makes sure that an AbiWord section have already been created. Unlike
 * OpenDocument, AbiWord can't have paragraphs without a section to hold them.
 * 
 * @param pProps Properties for the AbiWord section (if a new one will be created).
 */
void OD_TextContent_ListenerState::_insureInSection(const XML_Char * pProps) 
{
    if (m_bInSection) {
      return;
    }
    
    const OD_Style_MasterPage* pMasterPageStyle = NULL;

    if (!m_addedMasterPageSectionProps) {
        // For now we just use the Standard page master. AbiWord doesn't support
        // multiple page formats anyway.
        pMasterPageStyle = m_pStyles->getMasterPageStyle("Standard");
        UT_ASSERT(pMasterPageStyle);
        
        UT_ASSERT(pMasterPageStyle->getPageLayout());
    }
    
    // The AbiWord section properties are taken part from the OpenDocument 
    // page layout (from the master page style) and part from the OpenDocument
    // section properties.
    
    // TODO: What happens if there are duplicated properties on the page layout
    // and on the section?

    UT_UTF8String allProps;
    
    if (!m_addedMasterPageSectionProps) {
        allProps = pMasterPageStyle->getSectionProps();
    }
    
    if (pProps != NULL) {
        if (!allProps.empty()) {
            allProps += "; ";
        }
        
        allProps += pProps;
    }

    
    const XML_Char* atts[20];
    UT_uint8 i = 0;
    atts[i++] = "props";
    atts[i++] = allProps.utf8_str();
 
    if (!m_addedMasterPageSectionProps) {
        if (!pMasterPageStyle->getAWEvenHeaderSectionID().empty()) {
            atts[i++] = "header-even";
            atts[i++] = pMasterPageStyle->getAWEvenHeaderSectionID().utf8_str();
        }
        
        if (!pMasterPageStyle->getAWHeaderSectionID().empty()) {
            atts[i++] = "header";
            atts[i++] = pMasterPageStyle->getAWHeaderSectionID().utf8_str();
        }
        
        if (!pMasterPageStyle->getAWEvenFooterSectionID().empty()) {
            atts[i++] = "footer-even";
            atts[i++] = pMasterPageStyle->getAWEvenFooterSectionID().utf8_str();
        }
        
        if (!pMasterPageStyle->getAWFooterSectionID().empty()) {
            atts[i++] = "footer";
            atts[i++] = pMasterPageStyle->getAWFooterSectionID().utf8_str();
        }
    }
    
    atts[i] = 0; 
   
    m_pAbiDocument->appendStrux(PTX_Section, (const XML_Char**)atts);    

    m_bInSection = true;
    m_bAcceptingText = false;
    
    if (!m_addedMasterPageSectionProps) {
        m_addedMasterPageSectionProps = true;
    }
}


/**
 * 
 */
void OD_TextContent_ListenerState::_insureInBlock(const XML_Char ** atts)
{
    if (m_bAcceptingText)
        return;

    _insureInSection(NULL);

    if (!m_bAcceptingText) {
        m_pAbiDocument->appendStrux(PTX_Block, (const XML_Char**)atts);    
        m_bAcceptingText = true;
    }
}


/**
 * Process <text:p> and <text:h> startElement calls
 */
void OD_TextContent_ListenerState::_startParagraphElement (const XML_Char* pName,
                                          const XML_Char** ppParagraphAtts,
                                          OD_ListenerStateAction& rAction) {
        bool bIsListParagraph = false;
        const XML_Char* pStyleName;
        const XML_Char *ppAtts[50];
        UT_uint8 i;
        XML_Char listLevel[10];
        bool ok;
        UT_UTF8String props;
        const OD_Style_Style* pStyle;
        
        
        if (!UT_strcmp(m_rElementStack.getStartTag(0)->getName(), "text:list-item")) {
            // That's a list paragraph.
            bIsListParagraph = true;
        }

        pStyleName = UT_getAttribute ("text:style-name", ppParagraphAtts);

        if (pStyleName) {
            pStyle = m_pStyles->getParagraphStyle(pStyleName, m_bOnContentStream);
            UT_ASSERT(pStyle);
        } else {
            // Use the default style
            pStyle = m_pStyles->getDefaultParagraphStyle();
        }
        
        // Do we have an AbiWord section?
        _insureInSection();
        
        // Append an empty paragraph with this one char
        if (pStyle->getPageBreakBefore()) {
            m_pAbiDocument->appendStrux(PTX_Block, NULL);
            UT_UCSChar ucs = (pStyle->getColBreakBefore () ? UCS_VTAB : UCS_FF);
            m_pAbiDocument->appendSpan (&ucs, 1);
        }


        i = 0;

        if (bIsListParagraph && !m_alreadyDefinedAbiParagraphForList) {
            OD_ListLevelStyle* pListLevelStyle;
            
            m_alreadyDefinedAbiParagraphForList = true;
            
            pListLevelStyle = m_pCurrentListStyle->getLevelStyle(m_listLevel);
            
            sprintf(listLevel, "%u", m_listLevel);
            
            ppAtts[i++] = "level";
            ppAtts[i++] = listLevel;
            ppAtts[i++] = "listid";
            ppAtts[i++] = pListLevelStyle->getAbiListID()->utf8_str();
            
            // Is this really necessary? Because we already have this info on
            // the <l> tag.
            ppAtts[i++] = "parentid";
            ppAtts[i++] = pListLevelStyle->getAbiListParentID()->utf8_str();
                
            if (pStyle->isAutomatic()) {
                // Automatic styles are not defined on the document, so, we
                // just paste its properties.
                pStyle->getAbiPropsAttrString(props);
                
            } else {
                // We refer to the style
                ppAtts[i++] = "style";
                ppAtts[i++] = pStyle->getDisplayName().utf8_str();
            }


            pListLevelStyle->getAbiProperties(props, pStyle);
                
            ppAtts[i++] = "props";
            ppAtts[i++] = props.utf8_str();
                
            ppAtts[i] = 0; // Marks the end of the attributes list.
            ok = m_pAbiDocument->appendStrux(PTX_Block,
                                             (const XML_Char**)ppAtts);
            UT_ASSERT(ok);
            

            ppAtts[0] = "type";
            ppAtts[1] = "list_label";
            ppAtts[2] = 0;
            ok = m_pAbiDocument->appendObject(PTO_Field, ppAtts);
            UT_ASSERT(ok);
            
            // Inserts a tab character. AbiWord seens to need it in order to
            // implement the space between the list mark (number/bullet) and
            // the list text.
            UT_UCS4String string = "\t";
            _flush();
            m_pAbiDocument->appendSpan(string.ucs4_str(), string.size());
            
        } else if (bIsListParagraph && m_alreadyDefinedAbiParagraphForList) {
            // OpenDocument supports multiples paragraphs on a single list item,
            // But AbiWord works differently. So, we will put a <br/> instead
            // of adding a new paragraph.
            
            UT_UCSChar ucs = UCS_LF;
            m_pAbiDocument->appendSpan(&ucs,1);
            
            
            if (pStyle->isAutomatic()) {
                // Automatic styles are not defined on the document, so, we
                // just paste its properties.
                pStyle->getAbiPropsAttrString(props);
                ppAtts[i++] = "props";
                ppAtts[i++] = props.utf8_str();
            } else {
                // We refer to the style
                ppAtts[i++] = "style";
                ppAtts[i++] = pStyle->getDisplayName().utf8_str();
            }
            ppAtts[i] = 0; // Marks the end of the attributes list.
            
            ok = m_pAbiDocument->appendFmt(ppAtts);
            UT_ASSERT(ok);

        } else {
            
            if (pStyle->isAutomatic()) {
                // Automatic styles are not defined on the document, so, we
                // just paste its properties.
                pStyle->getAbiPropsAttrString(props);
                ppAtts[i++] = "props";
                ppAtts[i++] = props.utf8_str();
            } else {
                // We refer to the style
                ppAtts[i++] = "style";
                ppAtts[i++] = pStyle->getDisplayName().utf8_str();
            }
        
            ppAtts[i] = 0; // Marks the end of the attributes list.
            m_pAbiDocument->appendStrux(PTX_Block, (const XML_Char**)ppAtts);
        }


        if (m_pendingNoteAnchorInsertion) {
            UT_ASSERT(!m_currentNoteId.empty());
            
            const XML_Char* pNoteClass;
        
            pNoteClass = m_rElementStack.getStartTag(1)->
                                getAttributeValue("text:note-class");
            UT_ASSERT(pNoteClass != NULL);
            
            ppAtts[0] = "type";
            if (!UT_strcmp(pNoteClass, "footnote")) {
                ppAtts[1] = "footnote_anchor";
                ppAtts[2] = "footnote-id";
            } else if (!UT_strcmp(pNoteClass, "endnote")) {
                ppAtts[1] = "endnote_anchor";
                ppAtts[2] = "endnote-id";
            } else {
                UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
                // Unrecognized note class.
            }
            ppAtts[3] = m_currentNoteId.utf8_str();
            ppAtts[4] = 0;
            
            ok = m_pAbiDocument->appendObject(PTO_Field, ppAtts);
            UT_ASSERT(ok);
            
            m_pendingNoteAnchorInsertion = false;
        }

        // We now accept text
        m_bAcceptingText = true;
}


/**
 * Defines all toc-source-style* propeties on Abi TOCs (<toc> struxs)
 */
void OD_TextContent_ListenerState::_defineAbiTOCHeadingStyles() {
    UT_uint32 i, j, count;
    pf_Frag_Strux* pTOCStrux;
    UT_UTF8String str;
    UT_UTF8String props;
    const UT_UTF8String* pStyleName;
    bool ok;
    
    count = m_tablesOfContent.getItemCount();
    for (i=0; i<count; i++) {
        pTOCStrux = m_tablesOfContent[i];
        props = *(m_tablesOfContentProps[i]);
        
        for (j=1; j<5; j++) {
            UT_UTF8String_sprintf(str, "%d", j);
            pStyleName = m_headingStyles[str];

            if (pStyleName) {
                UT_UTF8String_sprintf(str, "toc-source-style%d:%s", j,
                                      pStyleName->utf8_str());
                
                if (!props.empty()) {
                    props += "; ";
                }
                props += str;
            }
        }
        
        ok = m_pAbiDocument->changeStruxAttsNoUpdate(
                            (PL_StruxDocHandle) pTOCStrux, "props",
                            props.utf8_str());
        UT_ASSERT(ok);
    }
}
