/************************************************************************
 *
 *  TextConverter.java
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *  Copyright: 2002-2007 by Henrik Just
 *
 *  All Rights Reserved.
 * 
 *  Version 0.5 (2007-08-01)
 *
 */

package writer2latex.xhtml;

import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;
import java.util.LinkedList;
import java.util.Locale;

import java.text.Collator;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;

import writer2latex.util.Misc;
import writer2latex.util.Config;
import writer2latex.office.XMLString;
import writer2latex.office.IndexMark;
import writer2latex.office.ListCounter;
import writer2latex.office.ListStyle;
import writer2latex.office.PropertySet;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.OfficeReader;
import writer2latex.office.TocReader;

// Helper class (a struct) to contain information about an alphabetical
// index entry.
final class AlphabeticalEntry {
    String sWord; // the word for the index
    int nIndex; // the original index of this entry
}

// Helper class (a struct) to contain information about a toc entry
// (ie. a heading, other paragraph or toc-mark)
final class TocEntry {
    Element onode; // the original node
    String sLabel = null; // generated label for the entry
}

// Helper class (a struct) to point back to indexes that should be processed
final class IndexData {
    int nOutFileIndex; // the index of the out file containing the index
    Element onode; // the original node
    Element chapter; // the chapter containing this toc
    Element hnode; // a div node where the index should be added
}

public class TextConverter extends ConverterHelper {

    // Data used to handle splitting over several files
    // TODO: Accessor methods for sections
    int nSplit = 0;  // The outline level at which to split files (0=no split)
    private int nDontSplitLevel = 0; // if > 0 splitting is forbidden
    protected Stack sections = new Stack(); // stack of nested sections
    Element[] currentHeading = new Element[7]; // Last headings (repeated when splitting)

    // Counters for generated numbers
    private ListCounter outlineNumbering;
    private Hashtable listCounters = new Hashtable();
    private String sCurrentListLabel = null;
    
    // Mode used to handle floats (depends on source doc type and config)
    private int nFloatMode; 
	
    // Data used for index bookkeeping
    private Vector indexes = new Vector();

    // Data used to handle Alphabetical Index
    Vector index = new Vector(); // All words for the index
    private int nIndexIndex = -1; // Current index used for id's (of form idxN) 
    private int nAlphabeticalIndex = -1; // File containing alphabetical index

    // Data used to handle Table of Contents
    private Vector tocEntries = new Vector(); // All potential(!) toc items
    private int nTocFileIndex = -1; // file index for main toc
    private Element currentChapter = null; // Node for the current chapter (level 1) heading
    private int nTocIndex = -1; // Current index for id's (of form tocN) 

    // Style names for foot- and endnotes
    private String sFntCitBodyStyle = null;
    private String sFntCitStyle = null;
    private String sEntCitBodyStyle = null;
    private String sEntCitStyle = null;

    // Gather the footnotes and endnotes
    private LinkedList footnotes = new LinkedList();
    private LinkedList endnotes = new LinkedList();

    // Sometimes we have to create an inlinenode in a block context
    // (labels for footnotes and endnotes)
    // We put it here and insert it in the first paragraph/heading to come:
    private Node asapNode = null;

    // When generating toc, a few things should be done differently
    private boolean bInToc = false;

    public TextConverter(OfficeReader ofr, Config config, Converter converter) {
        super(ofr,config,converter);
        nSplit = config.getXhtmlSplitLevel();
        nFloatMode = ofr.isText() && config.xhtmlFloatObjects() ? 
            DrawConverter.FLOATING : DrawConverter.ABSOLUTE;
        outlineNumbering = new ListCounter(ofr.getOutlineStyle());
        // Styles for footnotes and endnotes
        PropertySet notes = ofr.getFootnotesConfiguration();
        if (notes!=null) {
            sFntCitBodyStyle = notes.getProperty(XMLString.TEXT_CITATION_BODY_STYLE_NAME);
            sFntCitStyle = notes.getProperty(XMLString.TEXT_CITATION_STYLE_NAME);
        }
        notes = ofr.getEndnotesConfiguration();
        if (notes!=null) {
            sEntCitBodyStyle = notes.getProperty(XMLString.TEXT_CITATION_BODY_STYLE_NAME);
            sEntCitStyle = notes.getProperty(XMLString.TEXT_CITATION_STYLE_NAME);
        }
    }
	
    /** Converts an office node as a complete text document
     *
     *  @param onode the Office node containing the content to convert
     */
    public void convertTextContent(Element onode) {
        Element hnode = converter.nextOutFile();

        // Create form
        if (nSplit==0) {
            Element form = getDrawCv().createForm();
            if (form!=null) {
                hnode.appendChild(form);
                hnode = form;
            }
        }

        // Convert content
        traverseBlockText(onode,hnode);

        // Generate all indexes
        int nIndexCount = indexes.size();
        for (int i=0; i<nIndexCount; i++) {
            generateToc((IndexData) indexes.get(i));
        }
    }
	
    protected int getTocIndex() { return nTocFileIndex; }
	
    protected int getAlphabeticalIndex() { return nAlphabeticalIndex; }
	
    ////////////////////////////////////////////////////////////////////////
    // BLOCK TEXT (returns current html node at end of block)

    public Node traverseBlockText(Node onode, Node hnode) {
        return traverseBlockText(onode,0,null,hnode);
    } 
	
    private Node traverseBlockText(Node onode, int nLevel, String styleName, Node hnode) {
        if (!onode.hasChildNodes()) { return hnode; }
        boolean bAfterHeading = false;
        NodeList nList = onode.getChildNodes();
        int nLen = nList.getLength();
        int i = 0;
        while (i < nLen) {
            Node child = nList.item(i);
            
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                String nodeName = child.getNodeName();
                // Anything but a section blocks splitting
                if (!nodeName.equals(XMLString.TEXT_SECTION)) { nDontSplitLevel++; }
                
                if (ofr.isDrawElement(child)) {
                    getDrawCv().handleDrawElement((Element)child,(Element)hnode,null,nFloatMode);
                }
                else if (nodeName.equals(XMLString.TEXT_P)) {
                    // is there a block element, we should use?
                    XhtmlStyleMap xpar = config.getXParStyleMap();
                    String sDisplayName = ofr.getParStyles().getDisplayName(Misc.getAttribute(child,XMLString.TEXT_STYLE_NAME));
					
                    if (sDisplayName!=null && xpar.contains(sDisplayName)) {
                        Node curHnode = hnode;
                        String sBlockElement = xpar.getBlockElement(sDisplayName);
                        String sBlockCss = xpar.getBlockCss(sDisplayName);
                        if (xpar.getBlockElement(sDisplayName).length()>0) {
                            Element block = converter.createElement(xpar.getBlockElement(sDisplayName));
                            if (!"(none)".equals(xpar.getBlockCss(sDisplayName))) {
                                block.setAttribute("class",xpar.getBlockCss(sDisplayName));
                            }
                            hnode.appendChild(block);
                            curHnode = block;
                        }
                        boolean bMoreParagraphs = true;
                        do {
                            handleParagraph(child,curHnode);
                            bMoreParagraphs = false;
                            if (++i<nLen) {
                                child = nList.item(i);
                                String cnodeName = child.getNodeName();
                                if (cnodeName.equals(XMLString.TEXT_P)) {
                                    String sCurDisplayName = ofr.getParStyles().getDisplayName(Misc.getAttribute(child,XMLString.TEXT_STYLE_NAME));
                                    if (sCurDisplayName!=null && xpar.contains(sCurDisplayName)) {
                                        if (sBlockElement.equals(xpar.getBlockElement(sCurDisplayName)) &&
	                                        sBlockCss.equals(xpar.getBlockCss(sCurDisplayName))) {
                                            bMoreParagraphs = true;
                                         }
                                    }
                                }
                            }
                        } while (bMoreParagraphs);
                        i--;
                    }
                    else {
                        handleParagraph(child,hnode);
                    }
                }
                else if(nodeName.equals(XMLString.TEXT_H)) {
                    int nOutlineLevel = ofr.isOpenDocument() ?
                        Misc.getPosInteger(Misc.getAttribute(child, XMLString.TEXT_OUTLINE_LEVEL),1) :
                        Misc.getPosInteger(Misc.getAttribute(child, XMLString.TEXT_LEVEL),1);
                    Node rememberNode = hnode;
                    hnode = maybeSplit(hnode,nOutlineLevel,bAfterHeading);
                    handleHeading(child,hnode,rememberNode!=hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_LIST)) { // oasis
                    handleList(child,nLevel+1,styleName,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_UNORDERED_LIST)) { // old
                    handleList(child,nLevel+1,styleName,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_ORDERED_LIST)) { // old
                    handleList(child,nLevel+1,styleName,hnode);
                }
                else if (nodeName.equals(XMLString.TABLE_TABLE)) {
                    getTableCv().handleTable(child,hnode);
                }
                else if (nodeName.equals(XMLString.TABLE_SUB_TABLE)) {
                    getTableCv().handleTable(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_SECTION)) {
                    hnode = handleSection(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_TABLE_OF_CONTENT)) {
                    if (!ofr.getTocReader((Element)child).isByChapter()) {
                        hnode = maybeSplit(hnode,1,bAfterHeading);
                    }
                    handleTOC(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_ILLUSTRATION_INDEX)) {
                    handleLOF(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_TABLE_INDEX)) {
                    handleLOT(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_OBJECT_INDEX)) {
                    handleObjectIndex(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_USER_INDEX)) {
                    handleUserIndex(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_ALPHABETICAL_INDEX)) {
                    hnode = maybeSplit(hnode,1,bAfterHeading);
                    handleAlphabeticalIndex(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_BIBLIOGRAPHY)) {
                    hnode = maybeSplit(hnode,1,bAfterHeading);
                    handleBibliography(child,hnode);
                }
                else if (nodeName.equals(XMLString.TEXT_SEQUENCE_DECLS)) {
                    //handleSeqeuenceDecls(child);
                }
                // Reenable splitting
                if (!nodeName.equals(XMLString.TEXT_SECTION)) { nDontSplitLevel--; }
                // Remember if this was a heading
                bAfterHeading = nodeName.equals(XMLString.TEXT_H);
            }
            i++;
        }
        return hnode;
    }
    
    private Node maybeSplit(Node node, int nLevel, boolean bAfterHeading) {
        if (bAfterHeading || nDontSplitLevel>1) { return node; }
        if (nSplit>=nLevel && converter.outFileHasContent()) { return converter.nextOutFile(); }
        return node;
    }

    /* Process a text:section tag (returns current html node) */
    private Node handleSection(Node onode, Node hnode) {
        String sName = Misc.getAttribute(onode,XMLString.TEXT_NAME);
        String sStyleName = Misc.getAttribute(onode,XMLString.TEXT_STYLE_NAME);
        Element div = converter.createElement("div");
        hnode.appendChild(div);
        converter.addTarget(div,sName+"%7Cregion");
        StyleInfo sectionInfo = new StyleInfo();
        getSectionSc().applyStyle(sStyleName,sectionInfo);
        applyStyle(sectionInfo,div);
        sections.push(onode);
        Node newhnode = traverseBlockText(onode, div);
        sections.pop();
        return newhnode.getParentNode();
    }

    /*
     * Process a text:h tag
     */
    private void handleHeading(Node onode, Node hnode, boolean bAfterSplit) {
        // Numbering: It is possible to define outline numbering in CSS2
        // using counters; but this is not supported by Mozilla 1.0.
        // TODO: Offer CSS2 solution as an alternative later.

        // Note: Conditional styles are not supported
        int nLevel = ofr.isOpenDocument() ?
            Misc.getPosInteger(Misc.getAttribute(onode, XMLString.TEXT_OUTLINE_LEVEL),1) :
            Misc.getPosInteger(Misc.getAttribute(onode, XMLString.TEXT_LEVEL),1);
        if (nLevel<=6) {
            if (nLevel==1) { currentChapter = (Element) onode; }
            // If split output, add headings of higher levels
            if (bAfterSplit && nSplit>0) {
                for (int i=0; i<nLevel; i++) {
                    if (currentHeading[i]!=null) {
                        hnode.appendChild(converter.importNode(currentHeading[i],true));
                    }
                }
            }		

			// add Hx element
            Element heading = converter.createElement("h"+nLevel);
            traverseFloats(onode,hnode,heading);
            hnode.appendChild(heading);
            // Apply writing direction
            String sStyleName = Misc.getAttribute(onode,XMLString.TEXT_STYLE_NAME);
            StyleWithProperties style = ofr.getParStyle(sStyleName);
            if (style!=null) {
                StyleInfo headInfo = new StyleInfo(); 
                getParSc().applyDirection(style,headInfo);
                getParSc().applyStyle(headInfo,heading);
            }

            getParSc().setHeadingStyle(nLevel,sStyleName);
			
            // Prepend asapNode
            prependAsapNode(heading);
			
            // Prepend numbering
            Element span = converter.createElement("span");
            span.setAttribute("class","SectionNumber"); // todo: get from outline
            heading.appendChild(span);
            span.appendChild( converter.createTextNode(outlineNumbering.step(nLevel).getLabel()) );
			
            // Add to toc
            if (!bInToc) {
                converter.addTarget(heading,"toc"+(++nTocIndex));
                TocEntry entry = new TocEntry();
                entry.onode = (Element) onode;
                entry.sLabel = outlineNumbering.getLabel();
                tocEntries.add(entry);
            }

            traverseInlineText(onode,heading);
			
            // Keep track of current headings for split output
            currentHeading[nLevel] = heading;
            for (int i=nLevel+1; i<=6; i++) {
                currentHeading[i] = null;
            }
        }
        else { // beyond h6 - export as ordinary paragraph
            handleParagraph(onode,hnode);
        }
    }

    /*
     * Process a text:p tag
     */
    private void handleParagraph(Node onode, Node hnode) {
        if (config.ignoreEmptyParagraphs() && ofr.isWhitespaceContent(onode)) { return; }
        String sStyleName = Misc.getAttribute(onode,XMLString.TEXT_STYLE_NAME);

        Element par;
        if (ofr.isSpreadsheet()) { // attach inline text directly to parent (always a table cell)
            par = (Element) hnode;
        }
        else {
            // Hack because createParagraph doesn't work the way we need here :-(
            Element temp = converter.createElement("temp");
            par = createParagraph(temp, sStyleName);
            prependAsapNode(par);
            traverseFloats(onode,hnode,par);
            hnode.appendChild(temp.getFirstChild());
        }

        // Maybe add to toc
        if (ofr.isIndexSourceStyle(getParSc().getRealParStyleName(sStyleName))) {
            converter.addTarget(par,"toc"+(++nTocIndex));
            TocEntry entry = new TocEntry();
            entry.onode = (Element) onode;
            entry.sLabel = sCurrentListLabel;  
            tocEntries.add(entry);
        }
        sCurrentListLabel = null;
		
        if (onode.hasChildNodes()) {
            par = createTextBackground(par, sStyleName);
            traverseInlineText(onode,par);
        }
        else {
            //par.appendChild( converter.createTextNode("\u00A0") );
        }        
    }
    
    private void prependAsapNode(Node node) {
        if (asapNode!=null) {
            // May float past a split; check this first
            if (asapNode.getOwnerDocument()!=node.getOwnerDocument()) {
                asapNode = converter.importNode(asapNode,true);
            }
            node.appendChild(asapNode); asapNode = null;
        }
    }
	
    // Helper: Check if a list contains any items
    private boolean hasItems(Node onode) {
        Node child = onode.getFirstChild();
        while (child!=null) {
            if (Misc.isElement(child,XMLString.TEXT_LIST_ITEM) ||
                Misc.isElement(child,XMLString.TEXT_LIST_HEADER)) {
                return true;
            }
            child = child.getNextSibling();
        }
        return false;
    }

    /*
     * Process a text:ordered-list tag.  
     */
    private void handleOL (Node onode, int nLevel, String sStyleName, Node hnode) {
        if (hasItems(onode)) {
            // add an OL element
            Element list = converter.createElement("ol");
            StyleInfo listInfo = new StyleInfo();
            getListSc().applyStyle(nLevel,sStyleName,listInfo);
            applyStyle(listInfo,list);
            hnode.appendChild(list);
            traverseList(onode,nLevel,sStyleName,list);
        }
    }

    /*
     * Process a text:unordered-list tag.  
     */
    private void handleUL (Node onode, int nLevel, String sStyleName, Node hnode) {
        if (hasItems(onode)) {
            // add an UL element
            Element list = converter.createElement("ul");
            StyleInfo listInfo = new StyleInfo();
            getListSc().applyStyle(nLevel,sStyleName,listInfo);
            applyStyle(listInfo,list);
            hnode.appendChild(list);
            traverseList(onode,nLevel,sStyleName,list);
        }
    }
	
    private void handleList(Node onode, int nLevel, String sStyleName, Node hnode) {
        // In OpenDocument, we should use the style to determine the type of list
        String sStyleName1 = Misc.getAttribute(onode,XMLString.TEXT_STYLE_NAME);
        if (sStyleName1!=null) { sStyleName = sStyleName1; }
        ListStyle style = ofr.getListStyle(sStyleName);
        if (style!=null && style.isNumber(nLevel)) {
            handleOL(onode,nLevel,sStyleName,hnode);
        }
        else {
            handleUL(onode,nLevel,sStyleName,hnode);
        }
    }

    /*
     * Process the contents of a list (Changed as suggested by Nick Bower)
     * The option xhtml_use_list_hack triggers some *invalid* code:
     * - the attribute start on ol (is valid in html 4 transitional)
     * - the attribute value on li (is valid in html 4 transitional)
     * (these attributes are supposed to be replaced by css, but browsers
     * generally don't support that)
     * - generates <ol><ol><li>...</li></ol></ol> instead of
     *   <ol><li><ol><li>...</li></ol></li></ol> in case the first child of
     *   a list item is a new list. This occurs when a list is *continued* at
     *   level 2 or higher. This hack seems to be the only solution that
     *   actually produces correct results in browsers :-(
     */
    private void traverseList (Node onode, int nLevel, String styleName, Element hnode) {
        // Get the counter, if possible
        ListCounter counter = null;
        if (styleName!=null) {
            if (listCounters.containsKey(styleName)) {
                counter = (ListCounter) listCounters.get(styleName);
            }
            else {
                ListStyle style = ofr.getListStyle(styleName);
                if (style!=null) {
                    counter = new ListCounter(style);
                    listCounters.put(styleName,counter);
                }
            }
        }
		
        // Restart numbering, if required
        if (counter!=null) {
            boolean bContinueNumbering = "true".equals(Misc.getAttribute(onode,XMLString.TEXT_CONTINUE_NUMBERING));
            if (bContinueNumbering) {
                if (config.xhtmlUseListHack()) {
                    hnode.setAttribute("start",Integer.toString(counter.getValue(nLevel)+1));
                }
            }
            else if (counter!=null) {
                counter.restart(nLevel);
            }
        }

        if (onode.hasChildNodes()) {
            NodeList nList = onode.getChildNodes();
            int len = nList.getLength();
            
            for (int i = 0; i < len; i++) {
                Node child = nList.item(i);
                
                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    String nodeName = child.getNodeName();
                    
                    if (nodeName.equals(XMLString.TEXT_LIST_ITEM)) {
                        // Check to see if first child is a new list
                        boolean bIsImmediateNestedList = false;
                        Element child1 = Misc.getFirstChildElement(child);
                        if (child1.getTagName().equals(XMLString.TEXT_ORDERED_LIST) || // old
                            child1.getTagName().equals(XMLString.TEXT_UNORDERED_LIST) || // old
                            child1.getTagName().equals(XMLString.TEXT_LIST)) { // oasis
                            bIsImmediateNestedList = true;
                        }

                        if (config.xhtmlUseListHack() && bIsImmediateNestedList) {
                            traverseListItem(child,nLevel,styleName,hnode);
                        }
                        else {
                            // add an li element
                            sCurrentListLabel = counter.step(nLevel).getLabel();
                            Element item = converter.createElement("li");
                            StyleInfo info = new StyleInfo();
                            getPresentationSc().applyOutlineStyle(nLevel,info);
                            applyStyle(info,item);
                            hnode.appendChild(item);
                            if (config.xhtmlUseListHack()) {
                                boolean bRestart = "true".equals(Misc.getAttribute(child,
                                    XMLString.TEXT_RESTART_NUMBERING));
                                int nStartValue = Misc.getPosInteger(Misc.getAttribute(child,
                                    XMLString.TEXT_START_VALUE),1);
                                if (bRestart) {
                                    item.setAttribute("value",Integer.toString(nStartValue));
                                    if (counter!=null) {
                                        sCurrentListLabel = counter.restart(nLevel,nStartValue).getLabel();
                                    }
                                }
                            }
                            traverseListItem(child,nLevel,styleName,item);
                        }
                    }
                    if (nodeName.equals(XMLString.TEXT_LIST_HEADER)) {
                        // add an li element
                        Element item = converter.createElement("li");
                        hnode.appendChild(item);
                        item.setAttribute("style","list-style-type:none");
                        traverseListItem(child,nLevel,styleName,item);
                    }
                }
            }
        }
    }
    
    
    /*
     * Process the contents of a list item
     * (a list header should only contain paragraphs, but we don't care)
     */
    private void traverseListItem (Node onode, int nLevel, String styleName, Node hnode) { 
        // First check if we have a single paragraph to be omitted
        // This should happen if we ignore styles and have no style-map
        // for the paragraph style used        
        if (config.xhtmlFormatting()!=Config.CONVERT_ALL && onode.hasChildNodes()) {
            NodeList list = onode.getChildNodes();
            int nLen = list.getLength();
            int nParCount = 0;
            boolean bNoPTag = true;
            for (int i=0; i<nLen; i++) {
                if (list.item(i).getNodeType()==Node.ELEMENT_NODE) {
                    if (list.item(i).getNodeName().equals(XMLString.TEXT_P)) {
                        nParCount++;
                        if (bNoPTag) {
                            String sDisplayName = ofr.getParStyles().getDisplayName(Misc.getAttribute(list.item(0),XMLString.TEXT_STYLE_NAME));
                            if (config.getXParStyleMap().contains(sDisplayName)) {
                                bNoPTag = false;
                            }
                        }
                    }
                    else { // found non-text:p element
                        bNoPTag=false;
                    }
                }
            }
            if (bNoPTag && nParCount<=1) {
                // traverse the list
                for (int i = 0; i < nLen; i++) {
                    Node child = list.item(i);
          
                    if (child.getNodeType() == Node.ELEMENT_NODE) {
                        String nodeName = child.getNodeName();
                    
                        if (nodeName.equals(XMLString.TEXT_P)) {
                            traverseInlineText(child,hnode);
                        }
                        if (nodeName.equals(XMLString.TEXT_LIST)) { // oasis
                            handleList(child,nLevel+1,styleName,hnode);
                        }
                        if (nodeName.equals(XMLString.TEXT_ORDERED_LIST)) { // old
                            handleOL(child,nLevel+1,styleName,hnode);
                        }
                        if (nodeName.equals(XMLString.TEXT_UNORDERED_LIST)) { // old
                            handleUL(child,nLevel+1,styleName,hnode);
                        }
                    }
                }
                return;
            }
        }
        // Still here? - traverse block text as usual!
        traverseBlockText(onode,nLevel,styleName,hnode);
    }
		    
    /* Process table of contents
     */
    private void handleTOC(Node onode, Node hnode) {
        if (!ofr.getTocReader((Element)onode).isByChapter()) { 
            nTocFileIndex = converter.getOutFileIndex(); 
        }

        Element div = converter.createElement("div");
        hnode.appendChild(div);

        IndexData data = new IndexData();
        data.nOutFileIndex = converter.getOutFileIndex();
        data.onode = (Element) onode;
        data.chapter = currentChapter;
        data.hnode = (Element) div;
        indexes.add(data); // to be processed later...
    }

    private void generateToc(IndexData data) {
        Element onode = data.onode;
        Element chapter = data.chapter;
        Element div = data.hnode;

        int nSaveOutFileIndex = converter.getOutFileIndex();
        converter.changeOutFile(data.nOutFileIndex);
 
        bInToc = true;
        TocReader tocReader = ofr.getTocReader(onode);

        StyleInfo sectionInfo = new StyleInfo();
        getSectionSc().applyStyle(tocReader.getStyleName(),sectionInfo);
        applyStyle(sectionInfo,div);

        if (tocReader.getName()!=null) { converter.addTarget(div,tocReader.getName()); }
        // Generate title
        Element title = tocReader.getIndexTitleTemplate();
        if (title!=null) {
            String sStyleName = Misc.getAttribute(title,XMLString.TEXT_STYLE_NAME);
            Element p = createParagraph(div,sStyleName);
            traversePCDATA(title,p);
        }
			
        // TODO: Read the entire content of the entry templates!
        String[] sEntryStyleName = new String[11];
        for (int i=1; i<=10; i++) {
            Element entryTemplate = tocReader.getTocEntryTemplate(i);
            if (entryTemplate!=null) {
                sEntryStyleName[i] = Misc.getAttribute(entryTemplate,XMLString.TEXT_STYLE_NAME);
            }
        }

        int nStart = 0;
        int nLen = tocEntries.size();

        // Find the chapter
        if (tocReader.isByChapter() && chapter!=null) {
            for (int i=0; i<nLen; i++) {
                TocEntry entry = (TocEntry) tocEntries.get(i);
                if (entry.onode==chapter) { nStart=i; break; }
            }
            
        }

        // Generate entries
        for (int i=nStart; i<nLen; i++) {
            TocEntry entry = (TocEntry) tocEntries.get(i);
            String sNodeName = entry.onode.getTagName();
            if (XMLString.TEXT_H.equals(sNodeName)) {
                int nLevel = ofr.isOpenDocument() ?
                   Misc.getPosInteger(entry.onode.getAttribute(XMLString.TEXT_OUTLINE_LEVEL),1):
                   Misc.getPosInteger(entry.onode.getAttribute(XMLString.TEXT_LEVEL),1);

                if (nLevel==1 && tocReader.isByChapter() && entry.onode!=chapter) { break; }
                if (tocReader.useOutlineLevel() && nLevel<=tocReader.getOutlineLevel()) {
                    Element p = createParagraph(div,sEntryStyleName[nLevel]);
                    if (entry.sLabel!=null) {
                        Element span = converter.createElement("span");
                        p.appendChild(span);
                        span.setAttribute("class","SectionNumber");
                        span.appendChild(converter.createTextNode(entry.sLabel));
                    }
                    Element a = converter.createLink("toc"+i);
                    p.appendChild(a);
                    traverseInlineText(entry.onode,a);
                }
                else {
                    String sStyleName = getParSc().getRealParStyleName(entry.onode.getAttribute(XMLString.TEXT_STYLE_NAME));
                    nLevel = tocReader.getIndexSourceStyleLevel(sStyleName);
                    if (tocReader.useIndexSourceStyles() && 1<=nLevel && nLevel<=tocReader.getOutlineLevel()) {
                        Element p = createParagraph(div,sEntryStyleName[nLevel]);
                        if (entry.sLabel!=null) {
                            p.appendChild(converter.createTextNode(entry.sLabel));
                        }
                        Element a = converter.createLink("toc"+i);
                        p.appendChild(a);
                        traverseInlineText(entry.onode,a);
                    }
                }
            }
            else if (XMLString.TEXT_P.equals(sNodeName)) {
                String sStyleName = getParSc().getRealParStyleName(entry.onode.getAttribute(XMLString.TEXT_STYLE_NAME));
                int nLevel = tocReader.getIndexSourceStyleLevel(sStyleName);
                if (tocReader.useIndexSourceStyles() && 1<=nLevel && nLevel<=tocReader.getOutlineLevel()) {
                    Element p = createParagraph(div,sEntryStyleName[nLevel]);
                    if (entry.sLabel!=null) {
                        p.appendChild(converter.createTextNode(entry.sLabel));
                    }
                    Element a = converter.createLink("toc"+i);
                    p.appendChild(a);
                    traverseInlineText(entry.onode,a);
                }
            }
            else if (XMLString.TEXT_TOC_MARK.equals(sNodeName)) {
                int nLevel = Misc.getPosInteger(entry.onode.getAttribute(XMLString.TEXT_OUTLINE_LEVEL),1);
                if (tocReader.useIndexMarks() && nLevel<=tocReader.getOutlineLevel()) {
                    Element p = createParagraph(div,sEntryStyleName[nLevel]);
                    Element a = converter.createLink("toc"+i);
                    p.appendChild(a);
                    a.appendChild(converter.createTextNode(IndexMark.getIndexValue(entry.onode)));
                }
            }
            else if (XMLString.TEXT_TOC_MARK_START.equals(sNodeName)) {
                int nLevel = Misc.getPosInteger(entry.onode.getAttribute(XMLString.TEXT_OUTLINE_LEVEL),1);
                if (tocReader.useIndexMarks() && nLevel<=tocReader.getOutlineLevel()) {
                    Element p = createParagraph(div,sEntryStyleName[nLevel]);
                    Element a = converter.createLink("toc"+i);
                    p.appendChild(a);
                    a.appendChild(converter.createTextNode(IndexMark.getIndexValue(entry.onode)));
                }
            }
        }
        bInToc = false;
		
        converter.changeOutFile(nSaveOutFileIndex);
    }

    /*
     * Process list of illustrations
     */
    private void handleLOF (Node onode, Node hnode) {
        // later
    }

    /*
     * Process list of tables
     */
    private void handleLOT (Node onode, Node hnode) {
        // later
    }

    /*
     * Process Object index
     */
    private void handleObjectIndex (Node onode, Node hnode) {
        // later
    }

    /*
     * Process User index
     */
    private void handleUserIndex (Node onode, Node hnode) {
        // later
    }

    /*
     * Process Alphabetical index
     */
    private void handleAlphabeticalIndex (Node onode, Node hnode) {
        nAlphabeticalIndex = converter.getOutFileIndex();
        Node source = Misc.getChildByTagName(onode,XMLString.TEXT_ALPHABETICAL_INDEX_SOURCE);
        if (source!=null) {
            Element div = converter.createElement("div");
            converter.addTarget(div,"alphabeticalindex");
            hnode.appendChild(div);
            // Generate title
            Node title = Misc.getChildByTagName(source,XMLString.TEXT_INDEX_TITLE_TEMPLATE);
            if (title!=null) {
                String sStyleName = Misc.getAttribute(title,XMLString.TEXT_STYLE_NAME);
                Element p = createParagraph(div,sStyleName);
                traversePCDATA(title,p);
            }
            // Collect style name for entries
            // TODO: Should read the entire template
            String sEntryStyleName = null;
            if (source.hasChildNodes()) {
                NodeList nl = source.getChildNodes();
                int nLen = nl.getLength();
                for (int i = 0; i < nLen; i++) {
                    Node child = nl.item(i);
                    if (child.getNodeType() == Node.ELEMENT_NODE
                        && child.getNodeName().equals(XMLString.TEXT_ALPHABETICAL_INDEX_ENTRY_TEMPLATE)) {
		                // Note: There are actually three outline-levels: separator, 1, 2 and 3
                        int nLevel = Misc.getPosInteger(Misc.getAttribute(child,XMLString.TEXT_OUTLINE_LEVEL),1);
                        if (nLevel==1) {
                            sEntryStyleName = Misc.getAttribute(child,XMLString.TEXT_STYLE_NAME);
                        }
                    }	                        
                }
            }
            // Sort the index entries
            Collator collator;
            String sLanguage = Misc.getAttribute(source,XMLString.FO_LANGUAGE);
            if (sLanguage==null) { // use default locale
                collator = Collator.getInstance();
            }
            else {
                String sCountry = Misc.getAttribute(source,XMLString.FO_COUNTRY);
                if (sCountry==null) { sCountry=""; }
                collator = Collator.getInstance(new Locale(sLanguage,sCountry));
            }
            for (int i = 0; i<=nIndexIndex; i++) {
                for (int j = i+1; j<=nIndexIndex ; j++) {
                    AlphabeticalEntry entryi = (AlphabeticalEntry) index.get(i);
                    AlphabeticalEntry entryj = (AlphabeticalEntry) index.get(j);
                    if (collator.compare(entryi.sWord, entryj.sWord) > 0) {
                        index.set(i,entryj);
                        index.set(j,entryi);
                    }
                }
            }
            // Generate the index
            Element table = converter.createElement("table");
            table.setAttribute("style","width:100%");
            div.appendChild(table);
            Element tr = converter.createElement("tr");
            table.appendChild(tr);
            Element[] td = new Element[4];
            for (int i=0; i<4; i++) {
                td[i] = converter.createElement("td");
                td[i].setAttribute("style","vertical-align:top");
                tr.appendChild(td[i]);
            }
            int nColEntries = nIndexIndex/4+1;
            int nColIndex = -1;
            for (int i=0; i<=nIndexIndex; i++) {
                if (i%nColEntries==0) { nColIndex++; } 
                AlphabeticalEntry entry = (AlphabeticalEntry) index.get(i);
                Element p = createParagraph(td[nColIndex],sEntryStyleName);
                Element a = converter.createLink("idx"+entry.nIndex);
                p.appendChild(a);
                a.appendChild(converter.createTextNode(entry.sWord));
            }
        }
        
    }

    /*
     * Process Bibliography
     */
    private void handleBibliography (Node onode, Node hnode) {
        // Use the content, not the template
        // This is a temp. solution. Later we want to be able to create
        // hyperlinks from the bib-item to the actual entry in the bibliography,
        // so we have to recreate the bibliography from the template.
        Node body = Misc.getChildByTagName(onode,XMLString.TEXT_INDEX_BODY);
        if (body!=null) {
            Element div = converter.createElement("div");
            converter.addTarget(div,"bibliography");
            hnode.appendChild(div);
            //asapNode = converter.createTarget("bibliography");
            Node title = Misc.getChildByTagName(body,XMLString.TEXT_INDEX_TITLE);
            if (title!=null) { traverseBlockText(title,div); }
            traverseBlockText(body,div);
        }     
    }

    ////////////////////////////////////////////////////////////////////////
    // INLINE TEXT
	
    /* Process floating frames bound to this inline text (ie. paragraph) */
    private void traverseFloats(Node onode, Node hnodeBlock, Node hnodeInline) {
        Node child = onode.getFirstChild();
        while (child!=null) {
            if (child.getNodeType()==Node.ELEMENT_NODE) {
                Element elm = (Element) child;
                String sTag = elm.getTagName();
                if (ofr.isDrawElement(elm)) {
                    elm = getDrawCv().getRealDrawElement(elm);
                    if (elm!=null) {
                        String sAnchor = elm.getAttribute(XMLString.TEXT_ANCHOR_TYPE);
                        // Convert only floating frames; text-boxes must always float
                        if (!"as-char".equals(sAnchor)) {
                            getDrawCv().handleDrawElement(elm,(Element)hnodeBlock,
                                (Element)hnodeInline,nFloatMode);
                        }
                        else if (XMLString.DRAW_TEXT_BOX.equals(sTag)) {
                            getDrawCv().handleDrawElement(elm,(Element)hnodeBlock,
                                (Element)hnodeInline,DrawConverter.INLINE);
                        }
                    }
                }
                else if (ofr.isTextElement(elm)) {
                    // Do not descend into {foot|end}notes
                    if (!ofr.isNoteElement(elm)) {
                        traverseFloats(elm,hnodeBlock,hnodeInline);
                    }
                }
            }
            child = child.getNextSibling();
        }
    }

    /*
     * Process inline text
     */
    private void traverseInlineText (Node onode,Node hnode) {        
        String styleName = Misc.getAttribute(onode, XMLString.TEXT_STYLE_NAME);
                              
        if (onode.hasChildNodes()) {
            NodeList nList = onode.getChildNodes();
            int nLen = nList.getLength();
                       
            for (int i = 0; i < nLen; i++) {
                
                Node child = nList.item(i);
                short nodeType = child.getNodeType();
               
                switch (nodeType) {
                    case Node.TEXT_NODE:
                        String s = child.getNodeValue();
                        if (s.length() > 0) {
                            hnode.appendChild( converter.createTextNode(s) );
                        }
                        break;
                        
                    case Node.ELEMENT_NODE:
                        String sName = child.getNodeName();
                        if (ofr.isDrawElement(child)) {
                            Element elm = getDrawCv().getRealDrawElement((Element)child);
                            if (elm!=null) {
                                String sAnchor = (elm.getAttribute(XMLString.TEXT_ANCHOR_TYPE));
                                if ("as-char".equals(sAnchor)) {
                                    getDrawCv().handleDrawElement(elm,null,(Element)hnode,DrawConverter.INLINE);
                                }
                            }
                        }
                        else if (child.getNodeName().equals(XMLString.TEXT_S)) {
                            if (config.ignoreDoubleSpaces()) {
                                hnode.appendChild( converter.createTextNode(" ") );
                            }
                            else {
                                int count= Misc.getPosInteger(Misc.getAttribute(child,XMLString.TEXT_C),1);
                                for ( ; count > 0; count--) {
                                    hnode.appendChild( converter.createTextNode("\u00A0") );
                                }
                            }
                        }
                        else if (sName.equals(XMLString.TEXT_TAB_STOP)) {
                            handleTabStop(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_TAB)) { // oasis
                            handleTabStop(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_LINE_BREAK)) {
                            if (!config.ignoreHardLineBreaks()) {
                                hnode.appendChild( converter.createElement("br") );
                            }
                        }
                        else if (sName.equals(XMLString.TEXT_SPAN)) {
                            handleSpan(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_A)) {
                            handleAnchor(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_FOOTNOTE)) {
                            handleFootnote(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_ENDNOTE)) {
                            handleEndnote(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_NOTE)) { // oasis
                            if ("endnote".equals(Misc.getAttribute(child,XMLString.TEXT_NOTE_CLASS))) {
                                handleEndnote(child,hnode);
                            }
                            else {
                                handleFootnote(child,hnode);
                            }
                        }
                        else if (sName.equals(XMLString.TEXT_SEQUENCE)) {
	                        handleSequence(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_PAGE_NUMBER)) {
	                        handlePageNumber(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_PAGE_COUNT)) {
	                        handlePageCount(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_SEQUENCE_REF)) {
	                        handleSequenceRef(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_FOOTNOTE_REF)) {
	                        handleNoteRef(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_ENDNOTE_REF)) {
	                        handleNoteRef(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_NOTE_REF)) { // oasis
	                        handleNoteRef(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_REFERENCE_MARK)) {
	                        handleReferenceMark(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_REFERENCE_MARK_START)) {
	                        handleReferenceMark(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_REFERENCE_REF)) {
	                        handleReferenceRef(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_BOOKMARK)) {
	                        handleBookmark(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_BOOKMARK_START)) {
	                        handleBookmark(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_BOOKMARK_REF)) {
	                        handleBookmarkRef(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_ALPHABETICAL_INDEX_MARK)) {
	                        handleAlphabeticalIndexMark(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_ALPHABETICAL_INDEX_MARK_START)) {
	                        handleAlphabeticalIndexMarkStart(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_TOC_MARK)) {
	                        handleTocMark(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_TOC_MARK_START)) {
	                        handleTocMark(child,hnode);
                        }
                        else if (sName.equals(XMLString.TEXT_BIBLIOGRAPHY_MARK)) {
	                        handleBibliographyMark(child,hnode);
                        }
                        else if (sName.equals(XMLString.OFFICE_ANNOTATION)) {
                            converter.handleOfficeAnnotation(child,hnode);
                        }
						else if (sName.startsWith("text:")) {
							 traverseInlineText(child,hnode);
						}
                        // other tags are ignored;
                        break;
                    default:
                        // Do nothing
                }
            }
        }
    }
	
    private void handleTabStop(Node onode, Node hnode) {
        // xhtml does not have tab stops, we export a space, which the
        // user may choose to format
        if (config.getXhtmlTabstopStyle().length()>0) {
            Element span = converter.createElement("span");
            hnode.appendChild(span);
            span.setAttribute("class",config.getXhtmlTabstopStyle());
            span.appendChild(converter.createTextNode(" "));
        }
        else {
            hnode.appendChild(converter.createTextNode(" "));
        }
    }
	
    private void handleSpan(Node onode, Node hnode) {
        String sStyleName = Misc.getAttribute(onode,XMLString.TEXT_STYLE_NAME);
        Element span = createInline((Element) hnode,sStyleName);
        traverseInlineText (onode,span);
    }

    private void traversePCDATA(Node onode, Node hnode) {
        if (onode.hasChildNodes()) {
            NodeList nl = onode.getChildNodes();
            int nLen = nl.getLength();
            for (int i=0; i<nLen; i++) {
                if (nl.item(i).getNodeType()==Node.TEXT_NODE) {
                    hnode.appendChild( converter.createTextNode(nl.item(i).getNodeValue()) );
                }
            }
        }
    }
    
    protected void handleAnchor(Node onode, Node hnode) {
        Element anchor = converter.createLink((Element)onode);
        hnode.appendChild(anchor);
        traverseInlineText(onode,anchor);
    }

    /* Process a footnote */
    private void handleFootnote(Node onode, Node hnode) {
        String sId = Misc.getAttribute(onode,XMLString.TEXT_ID);
		Element span = createInline((Element) hnode,sFntCitBodyStyle);
        // Create target and back-link
        Element link = converter.createLink(sId);
        converter.addTarget(link,"body"+sId);
		span.appendChild(link);
        Node citation = Misc.getChildByTagName(onode,XMLString.TEXT_FOOTNOTE_CITATION);
        if (citation==null) { // try oasis
            citation = Misc.getChildByTagName(onode,XMLString.TEXT_NOTE_CITATION);
        }
        traversePCDATA(citation,link);
        footnotes.add(onode);
	} 
	
    public void insertFootnotes(Node hnode) {
        int n = footnotes.size();
        for (int i=0; i<n; i++) {
            Node footnote = (Node) footnotes.get(i);
            String sId = Misc.getAttribute(footnote,XMLString.TEXT_ID); 
            Node citation = Misc.getChildByTagName(footnote,XMLString.TEXT_FOOTNOTE_CITATION);
            if (citation==null) { // try oasis
                citation = Misc.getChildByTagName(footnote,XMLString.TEXT_NOTE_CITATION);
            }
            Node body = Misc.getChildByTagName(footnote,XMLString.TEXT_FOOTNOTE_BODY);
            if (body==null) { // try oasis
                body = Misc.getChildByTagName(footnote,XMLString.TEXT_NOTE_BODY);
            }
            traverseNoteBody(sId,sFntCitStyle,citation,body,hnode);
        }
        footnotes.clear();
    }

    /* Process an endnote */
    private void handleEndnote(Node onode, Node hnode) {
        String sId = Misc.getAttribute(onode,XMLString.TEXT_ID);
		Element span = createInline((Element) hnode,sEntCitBodyStyle);
        // Create target and back-link
        Element link = converter.createLink(sId);
        converter.addTarget(link,"body"+sId);
		span.appendChild(link);
        Node citation = Misc.getChildByTagName(onode,XMLString.TEXT_ENDNOTE_CITATION);
        if (citation==null) { // try oasis
            citation = Misc.getChildByTagName(onode,XMLString.TEXT_NOTE_CITATION);
        }
        traversePCDATA(citation,link);
        endnotes.add(onode);
	} 

    public void insertEndnotes(Node hnode) {
        int n = endnotes.size();
        if (nSplit>0 && n>0) { hnode = converter.nextOutFile(); }
        for (int i=0; i<n; i++) {
            Node endnote = (Node) endnotes.get(i);
            String sId = Misc.getAttribute(endnote,XMLString.TEXT_ID); 
            Node citation = Misc.getChildByTagName(endnote,XMLString.TEXT_ENDNOTE_CITATION);
            if (citation==null) { // try oasis
                citation = Misc.getChildByTagName(endnote,XMLString.TEXT_NOTE_CITATION);
            }
            Node body = Misc.getChildByTagName(endnote,XMLString.TEXT_ENDNOTE_BODY);
            if (body==null) { // try oasis
                body = Misc.getChildByTagName(endnote,XMLString.TEXT_NOTE_BODY);
            }
            traverseNoteBody(sId,sEntCitStyle,citation,body,hnode);
        }
    }

	/*
     * Process the contents of a footnote or endnote
     */
    private void traverseNoteBody (String sId, String sCitStyle, Node citation,Node onode, Node hnode) {
        // Create the anchor/footnote symbol:
        // Create target and link
        Element link = converter.createLink("body"+sId);
        converter.addTarget(link,sId);
        StyleInfo linkInfo = new StyleInfo();
        getTextSc().applyStyle(sCitStyle,linkInfo);
        applyStyle(linkInfo,link);
        traversePCDATA(citation,link);
        // Add a space and save it for later insertion:
        Element span = converter.createElement("span");
        span.appendChild(link);
        span.appendChild(converter.createTextNode(" "));
        asapNode = span;
		
        traverseBlockText(onode,hnode);

        /*if (onode.hasChildNodes()) {
            NodeList nList = onode.getChildNodes();
            int len = nList.getLength();
            
            for (int i = 0; i < len; i++) {
                Node child = nList.item(i);
                
                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    String nodeName = child.getNodeName();
                    
                    if (nodeName.equals(XMLString.TEXT_H)) {
                        handleHeading(child,hnode);
                    }

                    if (nodeName.equals(XMLString.TEXT_P)) {
                        handleParagraph(child,hnode);
                    }
                    
                    if (nodeName.equals(XMLString.TEXT_ORDERED_LIST)) {
                        handleOL(child,0,null,hnode);
                    }
                    
                    if (nodeName.equals(XMLString.TEXT_UNORDERED_LIST)) {
                        handleUL(child,0,null,hnode);
                    }
                }
            }
        }*/
    }

    private void handlePageNumber(Node onode, Node hnode) {
        // doesn't make any sense...
        hnode.appendChild( converter.createTextNode("(Page number)") );
    }
    
    private void handlePageCount(Node onode, Node hnode) {
       // also no sense
        hnode.appendChild( converter.createTextNode("(Page count)") );
    }

    private void handleSequence(Node onode, Node hnode) {
        // Use current value, but turn references into hyperlinks
        String sName = Misc.getAttribute(onode,XMLString.TEXT_REF_NAME);
        if (sName!=null && !bInToc) {
            Element anchor = converter.createTarget("seq"+sName);
            hnode.appendChild(anchor);
            traversePCDATA(onode,anchor);
        }
        else {
            traversePCDATA(onode,hnode);
        }        
    }
	
    private void createReference(Node onode, Node hnode, String sPrefix) {
        // Turn reference into hyperlink
        String sFormat = Misc.getAttribute(onode,XMLString.TEXT_REFERENCE_FORMAT);
        String sName = Misc.getAttribute(onode,XMLString.TEXT_REF_NAME);
        Element anchor = converter.createLink(sPrefix+sName);
        hnode.appendChild(anchor);
        if ("page".equals(sFormat)) { // all page numbers are 1 :-)
            anchor.appendChild( converter.createTextNode("1") );
        }
        else { // in other cases use current value
            traversePCDATA(onode,anchor);
        }
    }
		
    private void handleSequenceRef(Node onode, Node hnode) {
        createReference(onode,hnode,"seq");
    } 

    private void handleNoteRef(Node onode, Node hnode) {
        createReference(onode,hnode,"");
    } 
        
    private void handleReferenceMark(Node onode, Node hnode) {
        String sName = Misc.getAttribute(onode,XMLString.TEXT_NAME);
        if (sName!=null && !bInToc) {
            hnode.appendChild(converter.createTarget("ref"+sName));
        }
    }
	
    private void handleReferenceRef(Node onode, Node hnode) {
        createReference(onode,hnode,"ref");
    } 

    private void handleBookmark(Node onode, Node hnode) {
        // Note: Two targets (may be the target of a hyperlink or a reference)
        String sName = Misc.getAttribute(onode,XMLString.TEXT_NAME);
        if (sName!=null && !bInToc) {
            hnode.appendChild(converter.createTarget(sName));
            hnode.appendChild(converter.createTarget("bkm"+sName));
        }
    }
	
    private void handleBookmarkRef(Node onode, Node hnode) {
        createReference(onode,hnode,"bkm");
    } 
	
    private void handleAlphabeticalIndexMark(Node onode, Node hnode) {
        if (bInToc) { return; }
        String sWord = Misc.getAttribute(onode,XMLString.TEXT_STRING_VALUE);
        if (sWord==null) { return; }
        AlphabeticalEntry entry = new AlphabeticalEntry();
        entry.sWord = sWord; entry.nIndex = ++nIndexIndex; 
        index.add(entry);
        hnode.appendChild(converter.createTarget("idx"+nIndexIndex));
    }

    private void handleAlphabeticalIndexMarkStart(Node onode, Node hnode) {
        if (bInToc) { return; }
        String sWord = IndexMark.getIndexValue(onode);
        if (sWord==null) { return; }
        AlphabeticalEntry entry = new AlphabeticalEntry();
        entry.sWord = sWord; entry.nIndex = ++nIndexIndex; 
        index.add(entry);
        hnode.appendChild(converter.createTarget("idx"+nIndexIndex));
    }
	
    private void handleTocMark(Node onode, Node hnode) {
        hnode.appendChild(converter.createTarget("toc"+(++nTocIndex)));
        TocEntry entry = new TocEntry();
        entry.onode = (Element) onode;
        tocEntries.add(entry);
    }
	
    private void handleBibliographyMark(Node onode, Node hnode) {
        if (bInToc) {
            traversePCDATA(onode,hnode);
        }
        else {
            Element anchor = converter.createLink("bibliography");
            hnode.appendChild(anchor);
            traversePCDATA(onode,anchor);
        }
    }
	
    /* apply hard formatting attribute style maps */
    private Element applyAttributes(Element node, StyleWithProperties style) {
        // Do nothing if we convert hard formatting
        if (config.xhtmlFormatting()==Config.CONVERT_ALL || config.xhtmlFormatting()==Config.IGNORE_STYLES) { return node; }
        // Do nothing if this is not an automatic style
        if (style==null) { return node; }
        if (!style.isAutomatic()) { return node; }
        node = applyAttribute(node,"bold",getTextSc().isBold(style));
        node = applyAttribute(node,"italics",getTextSc().isItalics(style));
        node = applyAttribute(node,"fixed",getTextSc().isFixed(style));
        node = applyAttribute(node,"superscript",getTextSc().isSuperscript(style));
        node = applyAttribute(node,"subscript",getTextSc().isSubscript(style));
        return node;
    }
	
    /* apply hard formatting attribute style maps */
    private Element applyAttribute(Element node, String sAttr, boolean bApply) {
        if (!bApply) { return node; }
        XhtmlStyleMap xattr = config.getXAttrStyleMap();
        if (!xattr.contains(sAttr)) { return node; }
        Element attr = converter.createElement(xattr.getElement(sAttr));
        if (!"(none)".equals(xattr.getCss(sAttr))) {
            attr.setAttribute("class",xattr.getCss(sAttr));
        }
        node.appendChild(attr);
        return attr;
    }
	
    /* Create a styled paragraph node */
    private Element createParagraph(Element node, String sStyleName) {
        StyleInfo info = new StyleInfo();
        getParSc().applyStyle(sStyleName,info);
        Element par = converter.createElement(info.sTagName);
        node.appendChild(par);
        applyStyle(info,par);
        StyleWithProperties style = ofr.getParStyle(sStyleName);
        if (style!=null && style.isAutomatic()) {
            return applyAttributes(par,style);
        }
        else {
            return par;
        }
    }
	
    /* Create an inline node with background style from paragraph style */
    private Element createTextBackground(Element node, String sStyleName) {
        if (config.xhtmlFormatting()==Config.IGNORE_ALL || config.xhtmlFormatting()==Config.IGNORE_HARD) {
            return node;
        } 
        String sBack = getParSc().getTextBackground(sStyleName);
        if (sBack.length()>0) {
            Element span = converter.createElement("span");
            span.setAttribute("style",sBack);
            node.appendChild(span);
            return span;
        }
        else {
            return node;
        }
    }
		
    /* Create a styled inline node */
    private Element createInline(Element node, String sStyleName) {
        StyleInfo info = new StyleInfo();
        getTextSc().applyStyle(sStyleName,info);
        Element newNode = node;
        if (info.hasAttributes() || !"span".equals(info.sTagName)) {
            // We need to create a new element
            newNode = converter.createElement(info.sTagName);
            node.appendChild(newNode);
            applyStyle(info,newNode);
       }
       return applyAttributes(newNode,ofr.getTextStyle(sStyleName));
    }


	
}


