/************************************************************************ * * 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-2010 by Henrik Just * * All Rights Reserved. * * Version 1.2 (2010-11-22) * */ 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.office.FontDeclaration; import writer2latex.office.OfficeStyle; 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 int nFileIndex; // the file index for the generated content int nOutlineLevel; // the outline level for this heading int[] nOutlineNumber; // the natural outline number for this heading } // 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 // Some (Sony?) EPUB readers have a limit on the file size of individual files // In any case very large files could be a performance problem, hence we do automatic splitting // after this number of characters. TODO: Make configurable. private static final int EPUB_CHARACTER_COUNT_TRESHOLD = 150000; private int nPageBreakSplit = XhtmlConfig.NONE; // Should we split at page breaks? // TODO: Collect soft page breaks between table rows private boolean bPendingPageBreak = false; // We have encountered a page break which should be inserted asap private int nSplit = 0; // The outline level at which to split files (0=no split) private int nRepeatLevels = 5; // The number of levels to repeat when splitting (0=no repeat) private int nLastSplitLevel = 1; // The outline level at which the last split occurred private int nDontSplitLevel = 0; // if > 0 splitting is forbidden boolean bAfterHeading=false; // last element was a top level heading protected Stack<Node> sections = new Stack<Node>(); // stack of nested sections Element[] currentHeading = new Element[7]; // Last headings (repeated when splitting) private int nCharacterCount = 0; // The number of text characters in the current document // Counters for generated numbers private ListCounter outlineNumbering; private Hashtable<String, ListCounter> listCounters = new Hashtable<String, ListCounter>(); private String sCurrentListLabel = null; private ListStyle currentListStyle = null; private int nCurrentListLevel = 0; // Mode used to handle floats (depends on source doc type and config) private int nFloatMode; // Data used for index bookkeeping private Vector<IndexData> indexes = new Vector<IndexData>(); // Data used to handle Alphabetical Index Vector<AlphabeticalEntry> index = new Vector<AlphabeticalEntry>(); // 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<TocEntry> tocEntries = new Vector<TocEntry>(); // 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) private ListCounter naturalOutline = new ListCounter(); // Current "natural" outline number // 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<Node> footnotes = new LinkedList<Node>(); private LinkedList<Node> endnotes = new LinkedList<Node>(); // 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; // Display hidden text? private boolean bDisplayHiddenText = false; public TextConverter(OfficeReader ofr, XhtmlConfig config, Converter converter) { super(ofr,config,converter); nPageBreakSplit = config.pageBreakSplit(); nSplit = config.getXhtmlSplitLevel(); nRepeatLevels = config.getXhtmlRepeatLevels(); 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); } bDisplayHiddenText = config.displayHiddenText(); } /** 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(indexes.get(i)); } // Generate navigation links generateHeaders(); generateFooters(); generatePanels(); } protected int getTocIndex() { return nTocFileIndex; } protected int getAlphabeticalIndex() { return nAlphabeticalIndex; } //////////////////////////////////////////////////////////////////////// // NAVIGATION (fill header, footer and panel with navigation links) //////////////////////////////////////////////////////////////////////// // The header is populated with prev/next navigation private void generateHeaders() { } // The footer is populated with prev/next navigation private void generateFooters() { } // The panel is populated with a minitoc // TODO: Include link to toc and index in appropriate places.. private void generatePanels() { int nLastIndex = converter.getOutFileIndex(); bInToc = true; boolean bHasFrontMatter = false; TocEntry fakeEntry = new TocEntry(); fakeEntry.nOutlineLevel = 0; fakeEntry.nOutlineNumber = new int[11]; int nLen = tocEntries.size(); for (int nIndex=0; nIndex<=nLastIndex; nIndex++) { converter.changeOutFile(nIndex); Element panel = converter.getPanelNode(); if (panel!=null) { // Get the last heading of level <= split level for this file TocEntry entryCurrent = null; for (int i=nLen-1; i>=0; i--) { TocEntry entry = tocEntries.get(i); if (XMLString.TEXT_H.equals(entry.onode.getTagName()) && entry.nFileIndex==nIndex && entry.nOutlineLevel<=nSplit) { entryCurrent = entry; break; } } if (entryCurrent==null) { entryCurrent = fakeEntry; if (nIndex==0) { bHasFrontMatter=true; } } // Determine the maximum outline level to include int nMaxLevel = entryCurrent.nOutlineLevel; if (nMaxLevel<nSplit) { nMaxLevel++; } // Create minitoc with relevant entries if (bHasFrontMatter) { Element inline = createPanelLink(panel, nIndex, 0, 1); inline.appendChild(converter.createTextNode(converter.getL10n().get(L10n.HOME))); } int nPrevFileIndex = 0; for (int i=0; i<nLen; i++) { TocEntry entry = tocEntries.get(i); if (entry.nFileIndex>nPrevFileIndex+1) { // Skipping a file index means we have passed an index for (int k=nPrevFileIndex+1; k<entry.nFileIndex; k++) { createIndexLink(panel,nIndex,k); } } nPrevFileIndex = entry.nFileIndex; String sNodeName = entry.onode.getTagName(); if (XMLString.TEXT_H.equals(sNodeName)) { // Determine wether or not to include this heading // Note that this condition misses the case where // a heading of level n is followed by a heading of // level n+2. This is considered a bug in the document! boolean bInclude = entry.nOutlineLevel<=nMaxLevel; if (bInclude) { // Check that this heading matches the current int nCompareLevels = entry.nOutlineLevel; for (int j=1; j<nCompareLevels; j++) { if (entry.nOutlineNumber[j]!=entryCurrent.nOutlineNumber[j]) { bInclude = false; } } } if (bInclude) { Element inline = createPanelLink(panel, nIndex, entry.nFileIndex, entry.nOutlineLevel); // Add content of heading if (entry.sLabel!=null && entry.sLabel.length()>0) { inline.appendChild(converter.createTextNode(entry.sLabel)); if (!entry.sLabel.endsWith(" ")) { inline.appendChild(converter.createTextNode(" ")); } } traverseInlineText(entry.onode,inline); } } } if (nPrevFileIndex<nLastIndex) { // Trailing index for (int k=nPrevFileIndex+1; k<=nLastIndex; k++) { createIndexLink(panel,nIndex,k); } } } } bInToc = false; converter.changeOutFile(nLastIndex); } private void createIndexLink(Element panel, int nIndex, int nFileIndex) { if (nFileIndex==nTocFileIndex) { Element inline = createPanelLink(panel, nIndex, nTocFileIndex, 1); inline.appendChild(converter.createTextNode(converter.getL10n().get(L10n.CONTENTS))); } else if (nFileIndex==nAlphabeticalIndex) { Element inline = createPanelLink(panel, nIndex, nAlphabeticalIndex, 1); inline.appendChild(converter.createTextNode(converter.getL10n().get(L10n.INDEX))); } } private Element createPanelLink(Element panel, int nCurrentFile, int nLinkFile, int nOutlineLevel) { // Create a link Element p = converter.createElement("p"); p.setAttribute("class","level"+nOutlineLevel); panel.appendChild(p); Element inline; if (nCurrentFile!=nLinkFile) { inline = converter.createElement("a"); inline.setAttribute("href",converter.getOutFileName(nLinkFile,true)); } else { inline = converter.createElement("span"); inline.setAttribute("class","nolink"); } p.appendChild(inline); return inline; } //////////////////////////////////////////////////////////////////////// // 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; } 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(); // Block splitting nDontSplitLevel++; if (OfficeReader.isDrawElement(child)) { getDrawCv().handleDrawElement((Element)child,(Element)hnode,null,nFloatMode); } else if (nodeName.equals(XMLString.TEXT_P)) { StyleWithProperties style = ofr.getParStyle(Misc.getAttribute(child,XMLString.TEXT_STYLE_NAME)); hnode = maybeSplit(hnode, style); nCharacterCount+=OfficeReader.getCharacterCount(child); // is there a block element, we should use? XhtmlStyleMap xpar = config.getXParStyleMap(); String sDisplayName = style!=null ? style.getDisplayName() : null; 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)) { StyleWithProperties style = ofr.getParStyle(Misc.getAttribute(child,XMLString.TEXT_STYLE_NAME)); int nOutlineLevel = getOutlineLevel((Element)child); Node rememberNode = hnode; hnode = maybeSplit(hnode,style,nOutlineLevel); nCharacterCount+=OfficeReader.getCharacterCount(child); handleHeading((Element)child,hnode,rememberNode!=hnode); } else if (nodeName.equals(XMLString.TEXT_LIST) || // oasis nodeName.equals(XMLString.TEXT_UNORDERED_LIST) || // old nodeName.equals(XMLString.TEXT_ORDERED_LIST)) // old { hnode = maybeSplit(hnode,null); if (listIsOnlyHeadings(child)) { nDontSplitLevel--; hnode = handleFakeList(child,nLevel+1,styleName,hnode); nDontSplitLevel++; } else { handleList(child,nLevel+1,styleName,hnode); } } else if (nodeName.equals(XMLString.TABLE_TABLE)) { StyleWithProperties style = ofr.getTableStyle(Misc.getAttribute(child,XMLString.TEXT_STYLE_NAME)); hnode = maybeSplit(hnode,style); getTableCv().handleTable(child,hnode); } else if (nodeName.equals(XMLString.TABLE_SUB_TABLE)) { getTableCv().handleTable(child,hnode); } else if (nodeName.equals(XMLString.TEXT_SECTION)) { hnode = maybeSplit(hnode,null); nDontSplitLevel--; hnode = handleSection(child,hnode); nDontSplitLevel++; } else if (nodeName.equals(XMLString.TEXT_TABLE_OF_CONTENT)) { if (!ofr.getTocReader((Element)child).isByChapter()) { hnode = maybeSplit(hnode,null,1); } 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,null,1); handleAlphabeticalIndex(child,hnode); } else if (nodeName.equals(XMLString.TEXT_BIBLIOGRAPHY)) { hnode = maybeSplit(hnode,null,1); handleBibliography(child,hnode); } else if (nodeName.equals(XMLString.TEXT_SOFT_PAGE_BREAK)) { if (nPageBreakSplit==XhtmlConfig.ALL) { bPendingPageBreak = true; } } else if (nodeName.equals(XMLString.OFFICE_ANNOTATION)) { converter.handleOfficeAnnotation(child,hnode); } else if (nodeName.equals(XMLString.TEXT_SEQUENCE_DECLS)) { //handleSeqeuenceDecls(child); } // Reenable splitting nDontSplitLevel--; // Remember if this was a heading if (nDontSplitLevel==0) { bAfterHeading = nodeName.equals(XMLString.TEXT_H); } } i++; } return hnode; } private boolean getPageBreak(StyleWithProperties style) { if (style!=null && nPageBreakSplit>XhtmlConfig.NONE) { // If we don't consider manual page breaks, we may have to consider the parent style if (style.isAutomatic() && nPageBreakSplit<XhtmlConfig.EXPLICIT) { OfficeStyle parentStyle = style.getParentStyle(); if (parentStyle!=null && parentStyle instanceof StyleWithProperties) { style = (StyleWithProperties) parentStyle; } else { return false; } } // A page break can be a simple page break before or after... if ("page".equals(style.getProperty(XMLString.FO_BREAK_BEFORE))) { return true; } if ("page".equals(style.getProperty(XMLString.FO_BREAK_AFTER))) { bPendingPageBreak = true; return false; } // ...or it can be a new master page String sMasterPage = style.getMasterPageName(); if (sMasterPage!=null && sMasterPage.length()>0) { return true; } } return false; } private Node maybeSplit(Node node, StyleWithProperties style) { return maybeSplit(node,style,-1); } private Node maybeSplit(Node node, StyleWithProperties style, int nLevel) { if (bPendingPageBreak) { return doMaybeSplit(node, 0); } if (getPageBreak(style)) { return doMaybeSplit(node, 0); } if (converter.isOPS() && nCharacterCount>EPUB_CHARACTER_COUNT_TRESHOLD) { return doMaybeSplit(node, 0); } if (nLevel>=0) { return doMaybeSplit(node, nLevel); } else { return node; } } private Node doMaybeSplit(Node node, int nLevel) { if (nDontSplitLevel>1) { // we cannot split due to a nested structure return node; } if (!converter.isOPS() && bAfterHeading && nLevel-nLastSplitLevel<=nRepeatLevels) { // we cannot split because we are right after a heading and the // maximum number of parent headings on the page is not reached // TODO: Something wrong here....nLastSplitLevel is never set??? return node; } if (nSplit>=nLevel && converter.outFileHasContent()) { // No objections, this is a level that causes splitting nCharacterCount = 0; bPendingPageBreak = false; return converter.nextOutFile(); } return node; } /* Process a text:section tag (returns current html node) */ private Node handleSection(Node onode, Node hnode) { // Unlike headings, paragraphs and spans, text:display is not attached to the style: if (!bDisplayHiddenText && "none".equals(Misc.getAttribute(onode,XMLString.TEXT_DISPLAY))) { return 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+"|region"); StyleInfo sectionInfo = new StyleInfo(); getSectionSc().applyStyle(sStyleName,sectionInfo); applyStyle(sectionInfo,div); sections.push(onode); Node newhnode = traverseBlockText(onode, div); sections.pop(); return newhnode.getParentNode(); } private void handleHeading(Element onode, Node hnode, boolean bAfterSplit) { int nListLevel = getOutlineLevel((Element)onode); //boolean bUnNumbered = "true".equals(Misc.getAttribute(onode,XMLString.TEXT_IS_LIST_HEADER)); boolean bRestart = "true".equals(Misc.getAttribute(onode,XMLString.TEXT_RESTART_NUMBERING)); int nStartValue = Misc.getPosInteger(Misc.getAttribute(onode,XMLString.TEXT_START_VALUE),1)-1; handleHeading(onode, hnode, bAfterSplit, ofr.getOutlineStyle(), nListLevel, false, bRestart, nStartValue); } /* * Process a text:h tag */ private void handleHeading(Element onode, Node hnode, boolean bAfterSplit, ListStyle listStyle, int nListLevel, boolean bUnNumbered, boolean bRestart, int nStartValue) { String sStyleName = onode.getAttribute(XMLString.TEXT_STYLE_NAME); StyleWithProperties style = ofr.getParStyle(sStyleName); if (!bDisplayHiddenText && style!=null && "none".equals(style.getProperty(XMLString.TEXT_DISPLAY))) { return; } if (!bUnNumbered) { // If the heading uses a paragraph style which sets an explicit empty list style name, it's unnumbered if (style!=null) { String sListStyleName = style.getListStyleName(); if (sListStyleName!=null && sListStyleName.length()==0) { bUnNumbered = true; } } } // Note: nListLevel may in theory be different from the outline level, // though the ui in OOo does not allow this // Numbering: It is possible to define outline numbering in CSS2 // using counters; but this is not supported in all browsers // TODO: Offer CSS2 solution as an alternative later. // Note: Conditional styles are not supported int nLevel = getOutlineLevel(onode); if (nLevel<=6) { if (nLevel==1) { currentChapter = onode; } // If split output, add headings of higher levels if (bAfterSplit && nSplit>0) { int nFirst = nLevel-nRepeatLevels; if (nFirst<0) { nFirst=0; } for (int i=nFirst; i<nLevel; i++) { if (currentHeading[i]!=null) { hnode.appendChild(converter.importNode(currentHeading[i],true)); } } } // Apply style StyleInfo info = new StyleInfo(); info.sTagName = "h"+nLevel; getHeadingSc().applyStyle(nLevel, sStyleName, info); // add root element Element heading = converter.createElement(info.sTagName); hnode.appendChild(heading); applyStyle(info,heading); traverseFloats(onode,hnode,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(); StyleConverterHelper.applyDirection(style,headInfo); getParSc().applyStyle(headInfo,heading); }*/ // Prepend asapNode prependAsapNode(heading); // Prepend numbering String sLabel=""; if (!bUnNumbered) { ListCounter counter = getListCounter(listStyle); if (bRestart) { counter.restart(nListLevel,nStartValue); } sLabel = counter.step(nListLevel).getLabel(); if (config.zenHack() && nLevel==2) { // Hack for ePub Zen Garden: Special style for the prefix at level 2 // TODO: Replace by some proper style map construct... insertListLabel(listStyle,nListLevel,"SectionNumber",counter.getPrefix(),counter.getLabelAndSuffix(),heading); } else { insertListLabel(listStyle,nListLevel,"SectionNumber",null,sLabel,heading); } } // Add to toc if (!bInToc) { String sTarget = "toc"+(++nTocIndex); converter.addTarget(heading,sTarget); // Add in external content. For single file output we include all level 1 headings + their target // For multi-file output, the included heading levels depends on the split leve, and we don't include targets if (nLevel<=Math.max(nSplit,1)) { converter.addContentEntry(sLabel+(sLabel.length()>0 ? " " : "")+converter.getPlainInlineText(onode), nLevel, nSplit==0 ? sTarget : null); } // Add to real toc TocEntry entry = new TocEntry(); entry.onode = onode; entry.sLabel = sLabel; entry.nFileIndex = converter.getOutFileIndex(); entry.nOutlineLevel = nLevel; entry.nOutlineNumber = naturalOutline.step(nLevel).getValues(); tocEntries.add(entry); } // Convert content StyleInfo innerInfo = new StyleInfo(); getHeadingSc().applyInnerStyle(nLevel, sStyleName, innerInfo); Element content = heading; if (innerInfo.sTagName!=null && innerInfo.sTagName.length()>0) { content = converter.createElement(innerInfo.sTagName); heading.appendChild(content); applyStyle(innerInfo, content); } traverseInlineText(onode,content); // 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) { boolean bIsEmpty = OfficeReader.isWhitespaceContent(onode); if (config.ignoreEmptyParagraphs() && bIsEmpty) { return; } String sStyleName = Misc.getAttribute(onode,XMLString.TEXT_STYLE_NAME); StyleWithProperties style = ofr.getParStyle(sStyleName); if (!bDisplayHiddenText && style!=null && "none".equals(style.getProperty(XMLString.TEXT_DISPLAY))) { return; } 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; entry.nFileIndex = converter.getOutFileIndex(); tocEntries.add(entry); } if (!bIsEmpty) { par = createTextBackground(par, sStyleName); if (config.listFormatting()==XhtmlConfig.HARD_LABELS) { insertListLabel(currentListStyle, nCurrentListLevel, "ItemNumber", null, sCurrentListLabel, par); } sCurrentListLabel = null; traverseInlineText(onode,par); } else { // An empty paragraph (this includes paragraphs that only contains // whitespace) is ignored by the browser, hence we add par.appendChild( converter.createTextNode("\u00A0") ); sCurrentListLabel = null; } } 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; } } /////////////////////////////////////////////////////////////////////////// // LISTS /////////////////////////////////////////////////////////////////////////// // Helper: Get a list counter for a list style private ListCounter getListCounter(ListStyle style) { if (style==ofr.getOutlineStyle()) { // Outline numbering has a special counter return outlineNumbering; } else if (style!=null) { // Get existing or create new counter if (listCounters.containsKey(style.getName())) { return listCounters.get(style.getName()); } else { ListCounter counter = new ListCounter(style); listCounters.put(style.getName(),counter); return counter; } } else { // No style, return a dummy return new ListCounter(); } } // Helper: Insert a list label formatted with a list style private void insertListLabel(ListStyle style, int nLevel, String sDefaultStyle, String sPrefix, String sLabel, Element hnode) { if (sLabel!=null && sLabel.length()>0) { if (sPrefix!=null) { Element prefix = converter.createElement("span"); prefix.setAttribute("class", "chapter-name"); hnode.appendChild(prefix); prefix.appendChild( converter.createTextNode(sPrefix)); } StyleInfo info = new StyleInfo(); if (style!=null) { String sTextStyleName = style.getLevelProperty(nLevel,XMLString.TEXT_STYLE_NAME); getTextSc().applyStyle(sTextStyleName, info); } if (info.sTagName==null) { info.sTagName = "span"; } if (info.sClass==null) { info.sClass = sDefaultStyle; } Element content = converter.createElement(info.sTagName); getTextSc().applyStyle(info, content); hnode.appendChild(content); content.appendChild( converter.createTextNode(sLabel) ); } } // 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; } // TODO: Merge these three methods /* * 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) { ListCounter counter = getListCounter(ofr.getListStyle(styleName)); // Restart numbering, if required if (counter!=null) { boolean bContinueNumbering = "true".equals(Misc.getAttribute(onode,XMLString.TEXT_CONTINUE_NUMBERING)); if (!bContinueNumbering && counter!=null) { counter.restart(nLevel); } if (config.listFormatting()==XhtmlConfig.CSS1_HACK && counter.getValue(nLevel)>0) { hnode.setAttribute("start",Integer.toString(counter.getValue(nLevel)+1)); } } 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.listFormatting()==XhtmlConfig.CSS1_HACK && bIsImmediateNestedList) { traverseListItem(child,nLevel,styleName,hnode); } else { // add an li element sCurrentListLabel = counter.step(nLevel).getLabel(); currentListStyle = ofr.getListStyle(styleName); nCurrentListLevel = nLevel; Element item = converter.createElement("li"); StyleInfo info = new StyleInfo(); getPresentationSc().applyOutlineStyle(nLevel,info); applyStyle(info,item); hnode.appendChild(item); if (config.listFormatting()==XhtmlConfig.CSS1_HACK) { 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()!=XhtmlConfig.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); } /////////////////////////////////////////////////////////////////////////// // FAKE LISTS /////////////////////////////////////////////////////////////////////////// // A fake list is a list which is converted into a sequence of numbered // paragraphs rather than into a list. // Currently this is done for list which only contains headings // Helper: Check to see, if this list contains only headings // (If so, we will ignore the list and apply the numbering to the headings) private boolean listIsOnlyHeadings(Node node) { Node child = node.getFirstChild(); while (child!=null) { if (child.getNodeType() == Node.ELEMENT_NODE) { String nodeName = child.getNodeName(); if (nodeName.equals(XMLString.TEXT_LIST_ITEM)) { if (!itemIsOnlyHeadings(child)) return false; } else if (nodeName.equals(XMLString.TEXT_LIST_HEADER)) { if (!itemIsOnlyHeadings(child)) return false; } } child = child.getNextSibling(); } return true; } private boolean itemIsOnlyHeadings(Node node) { Node child = node.getFirstChild(); while (child!=null) { if (child.getNodeType() == Node.ELEMENT_NODE) { String nodeName = child.getNodeName(); if (nodeName.equals(XMLString.TEXT_LIST)) { if (!listIsOnlyHeadings(child)) return false; } else if (nodeName.equals(XMLString.TEXT_ORDERED_LIST)) { if (!listIsOnlyHeadings(child)) return false; } else if (nodeName.equals(XMLString.TEXT_UNORDERED_LIST)) { if (!listIsOnlyHeadings(child)) return false; } else if(!nodeName.equals(XMLString.TEXT_H)) { return false; } } child = child.getNextSibling(); } return true; } // Splitting may occur inside a fake list, so we return the (new) hnode private Node handleFakeList(Node onode, int nLevel, String sStyleName, Node hnode) { String sStyleName1 = Misc.getAttribute(onode,XMLString.TEXT_STYLE_NAME); if (sStyleName1!=null) { sStyleName = sStyleName1; } return traverseFakeList(onode,hnode,nLevel,sStyleName); } // Traverse a list which is not exported as a list but as a sequence of // numbered headings/paragraphs private Node traverseFakeList (Node onode, Node hnode, int nLevel, String sStyleName) { // Restart numbering? boolean bContinueNumbering ="true".equals( Misc.getAttribute(onode,XMLString.TEXT_CONTINUE_NUMBERING)); if (!bContinueNumbering) { getListCounter(ofr.getListStyle(sStyleName)).restart(nLevel); } Node child = onode.getFirstChild(); while (child!=null) { if (child.getNodeType() == Node.ELEMENT_NODE) { String sNodeName = child.getNodeName(); if (sNodeName.equals(XMLString.TEXT_LIST_ITEM)) { boolean bRestart = "true".equals(Misc.getAttribute(child, XMLString.TEXT_RESTART_NUMBERING)); int nStartValue = Misc.getPosInteger(Misc.getAttribute(child, XMLString.TEXT_START_VALUE),1); hnode = traverseFakeListItem(child, hnode, nLevel, sStyleName, false, bRestart, nStartValue); } else if (sNodeName.equals(XMLString.TEXT_LIST_HEADER)) { hnode = traverseFakeListItem(child, hnode, nLevel, sStyleName, true, false, 0); } } child = child.getNextSibling(); } return hnode; } // Process the contents of a fake list item private Node traverseFakeListItem (Node onode, Node hnode, int nLevel, String sStyleName, boolean bUnNumbered, boolean bRestart, int nStartValue) { Node child = onode.getFirstChild(); while (child!=null) { if (child.getNodeType() == Node.ELEMENT_NODE) { String sNodeName = child.getNodeName(); if (sNodeName.equals(XMLString.TEXT_H)) { nDontSplitLevel++; int nOutlineLevel = getOutlineLevel((Element)onode); Node rememberNode = hnode; StyleWithProperties style = ofr.getParStyle(Misc.getAttribute(child, XMLString.TEXT_STYLE_NAME)); hnode = maybeSplit(hnode,style,nOutlineLevel); handleHeading((Element)child, hnode, rememberNode!=hnode, ofr.getListStyle(sStyleName), nLevel, bUnNumbered, bRestart, nStartValue); nDontSplitLevel--; if (nDontSplitLevel==0) { bAfterHeading=true; } } else if (sNodeName.equals(XMLString.TEXT_P)) { // Currently we only handle fakes lists containing headings } else if (sNodeName.equals(XMLString.TEXT_LIST)) { // oasis return traverseFakeList(child, hnode, nLevel+1, sStyleName); } else if (sNodeName.equals(XMLString.TEXT_ORDERED_LIST)) { // old return traverseFakeList(child, hnode, nLevel+1, sStyleName); } else if (sNodeName.equals(XMLString.TEXT_UNORDERED_LIST)) { // old return traverseFakeList(child, hnode, nLevel+1, sStyleName); } } child = child.getNextSibling(); } return hnode; } ////////////////////////////////////////////////////////////////////////// // INDEXES ////////////////////////////////////////////////////////////////////////// /* Process table of contents */ private void handleTOC(Node onode, Node hnode) { if (!ofr.getTocReader((Element)onode).isByChapter()) { nTocFileIndex = converter.getOutFileIndex(); } converter.setTocFile(null); 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 with generateTOC } 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 = tocEntries.get(i); if (entry.onode==chapter) { nStart=i; break; } } } // Generate entries for (int i=nStart; i<nLen; i++) { TocEntry entry = tocEntries.get(i); String sNodeName = entry.onode.getTagName(); if (XMLString.TEXT_H.equals(sNodeName)) { int nLevel = getOutlineLevel(entry.onode); 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(); converter.setIndexFile(null); 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 = index.get(i); AlphabeticalEntry entryj = 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 = 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 (OfficeReader.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 (OfficeReader.isTextElement(elm)) { // Do not descend into {foot|end}notes if (!OfficeReader.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 (OfficeReader.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.TEXT_SOFT_PAGE_BREAK)) { if (nPageBreakSplit==XhtmlConfig.ALL) { bPendingPageBreak = true; } } 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, but we export and ASCII TAB character, 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("\t")); } else { hnode.appendChild(converter.createTextNode("\t")); } } private void handleSpan(Node onode, Node hnode) { StyleWithProperties style = ofr.getTextStyle(Misc.getAttribute(onode, XMLString.TEXT_STYLE_NAME)); if (!bDisplayHiddenText && style!=null && "none".equals(style.getProperty(XMLString.TEXT_DISPLAY))) { return; } if (!bInToc) { String sStyleName = Misc.getAttribute(onode,XMLString.TEXT_STYLE_NAME); Element span = createInline((Element) hnode,sStyleName); traverseInlineText(onode,span); } else { traverseInlineText(onode,hnode); } } 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 = 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 (n>0) { if (nSplit>0) { hnode = converter.nextOutFile(); } String sHeading = config.getEndnotesHeading(); if (sHeading.length()>0) { Element heading = converter.createElement("h1"); hnode.appendChild(heading); heading.appendChild(converter.createTextNode(sHeading)); // Add to external content. if (nSplit>0) { converter.addContentEntry(sHeading, 1, null); } else { //For single output file we need a target converter.addTarget(heading,"endnotes"); converter.addContentEntry(sHeading, 1, "endnotes"); } } for (int i=0; i<n; i++) { Node endnote = 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 && ofr.hasSequenceRefTo(sName)) { 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 && ofr.hasReferenceRefTo(sName)) { 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)); if (ofr.hasBookmarkRefTo(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; entry.nFileIndex = converter.getOutFileIndex(); 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); } } /////////////////////////////////////////////////////////////////////////// // UTILITY METHODS /////////////////////////////////////////////////////////////////////////// // Methods to query individual formatting properties (no inheritance) // Does this style contain the bold attribute? private boolean isBold(StyleWithProperties style) { String s = style.getProperty(XMLString.FO_FONT_WEIGHT,false); return s!=null && "bold".equals(s); } // Does this style contain the italics/oblique attribute? private boolean isItalics(StyleWithProperties style) { String s = style.getProperty(XMLString.FO_FONT_STYLE,false); return s!=null && !"normal".equals(s); } // Does this style contain a fixed pitch font? private boolean isFixed(StyleWithProperties style) { String s = style.getProperty(XMLString.STYLE_FONT_NAME,false); String s2 = null; String s3 = null; if (s!=null) { FontDeclaration fd = (FontDeclaration) ofr.getFontDeclarations().getStyle(s); if (fd!=null) { s2 = fd.getFontFamilyGeneric(); s3 = fd.getFontPitch(); } } else { s = style.getProperty(XMLString.FO_FONT_FAMILY,false); s2 = style.getProperty(XMLString.STYLE_FONT_FAMILY_GENERIC,false); s3 = style.getProperty(XMLString.STYLE_FONT_PITCH,false); } if ("fixed".equals(s3)) { return true; } if ("modern".equals(s2)) { return true; } return false; } // Does this style specify superscript? private boolean isSuperscript(StyleWithProperties style) { String sPos = style.getProperty(XMLString.STYLE_TEXT_POSITION,false); if (sPos==null) return false; if (sPos.startsWith("sub")) return false; if (sPos.startsWith("-")) return false; if (sPos.startsWith("0%")) return false; return true; } // Does this style specify subscript? private boolean isSubscript(StyleWithProperties style) { String sPos = style.getProperty(XMLString.STYLE_TEXT_POSITION,false); if (sPos==null) return false; if (sPos.startsWith("sub")) return true; if (sPos.startsWith("-")) return true; return false; } // Does this style specify underline? private boolean isUnderline(StyleWithProperties style) { String s; if (ofr.isOpenDocument()) { s = style.getProperty(XMLString.STYLE_TEXT_UNDERLINE_STYLE,false); } else { s = style.getProperty(XMLString.STYLE_TEXT_UNDERLINE,false); } return s!=null && !"none".equals(s); } // Does this style specify overstrike? private boolean isOverstrike(StyleWithProperties style) { String s; if (ofr.isOpenDocument()) { s = style.getProperty(XMLString.STYLE_TEXT_LINE_THROUGH_STYLE,false); } else { s = style.getProperty(XMLString.STYLE_TEXT_CROSSING_OUT,false); } return s!=null && !"none".equals(s); } /* apply hard formatting attribute style maps */ private Element applyAttributes(Element node, StyleWithProperties style) { // Do nothing if we convert hard formatting if (config.xhtmlFormatting()==XhtmlConfig.CONVERT_ALL || config.xhtmlFormatting()==XhtmlConfig.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",isBold(style)); node = applyAttribute(node,"italics",isItalics(style)); node = applyAttribute(node,"fixed",isFixed(style)); node = applyAttribute(node,"superscript",isSuperscript(style)); node = applyAttribute(node,"subscript",isSubscript(style)); node = applyAttribute(node,"underline",isUnderline(style)); node = applyAttribute(node,"overstrike",isOverstrike(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()==XhtmlConfig.IGNORE_ALL || config.xhtmlFormatting()==XhtmlConfig.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)); } private int getOutlineLevel(Element node) { return ofr.isOpenDocument() ? Misc.getPosInteger(node.getAttribute(XMLString.TEXT_OUTLINE_LEVEL),1): Misc.getPosInteger(node.getAttribute(XMLString.TEXT_LEVEL),1); } }