/************************************************************************ * * ListConverter.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-2015 by Henrik Just * * All Rights Reserved. * * Version 1.6 (2015-04-14) * */ package writer2latex.latex; import java.util.Hashtable; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import writer2latex.latex.util.BeforeAfter; import writer2latex.latex.util.Context; import writer2latex.office.ListStyle; import writer2latex.office.OfficeReader; import writer2latex.office.XMLString; import writer2latex.util.Misc; public class ListConverter extends StyleConverter { boolean bNeedSaveEnumCounter = false; private Hashtable listStyleLevelNames = new Hashtable(); /** Construct a new ListConverter */ public ListConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) { super(ofr,config,palette); } @Override public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) { if (config.formatting()>=LaTeXConfig.CONVERT_MOST || !styleNames.isEmpty()) { decl.append("% List styles").nl(); // May need an extra counter to handle continued numbering in lists if (bNeedSaveEnumCounter) { decl.append("\\newcounter{saveenum}").nl(); } // If we export formatting, we need some hooks from lists to paragraphs: if (config.formatting()>=LaTeXConfig.CONVERT_MOST) { decl.append("\\newcommand\\writerlistleftskip{}").nl() .append("\\newcommand\\writerlistparindent{}").nl() .append("\\newcommand\\writerlistlabel{}").nl() .append("\\newcommand\\writerlistremovelabel{") .append("\\aftergroup\\let\\aftergroup\\writerlistparindent\\aftergroup\\relax") .append("\\aftergroup\\let\\aftergroup\\writerlistlabel\\aftergroup\\relax}").nl(); } super.appendDeclarations(pack,decl); } } /**

Process a list (text:ordered-lst or text:unordered-list tag)

* @param node The element containing the list * @param ldp the LaTeXDocumentPortion to which * LaTeX code should be added * @param oc the current context */ public void handleList(Element node, LaTeXDocumentPortion ldp, Context oc) { // Set up new context Context ic = (Context) oc.clone(); ic.incListLevel(); if ("true".equals(node.getAttribute(XMLString.TEXT_CONTINUE_NUMBERING))) { ic.setInContinuedList(true); } // Get the style name, if we don't know it already if (ic.getListStyleName()==null) { ic.setListStyleName(node.getAttribute(XMLString.TEXT_STYLE_NAME)); } // Use the style to determine the type of list ListStyle style = ofr.getListStyle(ic.getListStyleName()); boolean bOrdered = style!=null && style.isNumber(ic.getListLevel()); // If the list contains headings, ignore it! if (ic.isIgnoreLists() || listContainsHeadings(node)) { ic.setIgnoreLists(true); traverseList(node,ldp,ic); return; } // Apply the style BeforeAfter ba = new BeforeAfter(); applyListStyle(bOrdered,ba,ic); // Export the list if (ba.getBefore().length()>0) { ldp.append(ba.getBefore()).nl(); } traverseList(node,ldp,ic); if (ba.getAfter().length()>0) { ldp.append(ba.getAfter()).nl(); } } /* * Process the contents of a list */ private void traverseList (Element node, LaTeXDocumentPortion ldp, Context oc) { if (node.hasChildNodes()) { NodeList list = node.getChildNodes(); int nLen = list.getLength(); for (int i = 0; i < nLen; i++) { Node child = list.item(i); if (child.getNodeType() == Node.ELEMENT_NODE) { String nodeName = child.getNodeName(); palette.getInfo().addDebugInfo((Element)child,ldp); if (nodeName.equals(XMLString.TEXT_LIST_ITEM)) { handleListItem((Element)child,ldp,oc); } if (nodeName.equals(XMLString.TEXT_LIST_HEADER)) { handleListItem((Element)child,ldp,oc); } } } } } private void handleListItem(Element node, LaTeXDocumentPortion ldp, Context oc) { // Are we ignoring this list? if (oc.isIgnoreLists()) { palette.getBlockCv().traverseBlockText(node,ldp,oc); return; } // Apply the style BeforeAfter ba = new BeforeAfter(); applyListItemStyle( oc.getListStyleName(), oc.getListLevel(), node.getNodeName().equals(XMLString.TEXT_LIST_HEADER), "true".equals(node.getAttribute(XMLString.TEXT_RESTART_NUMBERING)), Misc.getPosInteger(node.getAttribute(XMLString.TEXT_START_VALUE),1)-1, ba,oc); // export the list item (note the special treatment of lists in tables) if (ba.getBefore().length()>0) { ldp.append(ba.getBefore()); if (config.formatting()>=LaTeXConfig.CONVERT_MOST && !oc.isInTable()) { ldp.nl(); } } palette.getBlockCv().traverseBlockText(node,ldp,oc); if (ba.getAfter().length()>0 || oc.isInTable()) { ldp.append(ba.getAfter()).nl(); } } /* * Helper: Check to see, if this list contains headings * (in that case we will ignore the list!) */ private boolean listContainsHeadings (Node node) { if (node.hasChildNodes()) { NodeList nList = node.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)) { if (listItemContainsHeadings(child)) return true; } if (nodeName.equals(XMLString.TEXT_LIST_HEADER)) { if (listItemContainsHeadings(child)) return true; } } } } return false; } private boolean listItemContainsHeadings(Node node) { if (node.hasChildNodes()) { NodeList nList = node.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)) { return true; } if (nodeName.equals(XMLString.TEXT_LIST)) { if (listContainsHeadings(child)) return true; } if (nodeName.equals(XMLString.TEXT_ORDERED_LIST)) { if (listContainsHeadings(child)) return true; } if (nodeName.equals(XMLString.TEXT_UNORDERED_LIST)) { if (listContainsHeadings(child)) return true; } } } } return false; } // Convert style information /**

Apply a list style to an ordered or unordered list.

*/ private void applyListStyle(boolean bOrdered, BeforeAfter ba, Context oc) { // Step 1. We may have a style map, this always takes precedence String sDisplayName = ofr.getListStyles().getDisplayName(oc.getListStyleName()); if (config.getListStyleMap().contains(sDisplayName)) { ba.add(config.getListStyleMap().getBefore(sDisplayName), config.getListStyleMap().getAfter(sDisplayName)); return; } // Step 2: The list style may not exist, or the user wants to ignore it. // In this case we create default lists ListStyle style = ofr.getListStyle(oc.getListStyleName()); if (style==null || config.formatting()<=LaTeXConfig.IGNORE_MOST) { if (oc.getListLevel()<=4) { if (bOrdered) { ba.add("\\begin{enumerate}","\\end{enumerate}"); } else { ba.add("\\begin{itemize}","\\end{itemize}"); } } return; } // Step 3: Export as default lists, but redefine labels // (for list in tables this is the maximum formatting we export) if (config.formatting()==LaTeXConfig.CONVERT_BASIC || (config.formatting()>=LaTeXConfig.CONVERT_MOST && oc.isInTable())) { if (oc.getListLevel()==1) { if (!listStyleLevelNames.containsKey(oc.getListStyleName())) { createListStyleLabels(oc.getListStyleName()); } ba.add("\\liststyle"+styleNames.getExportName(getDisplayName(oc.getListStyleName()))+"\n",""); } if (oc.getListLevel()<=4) { String sCounterName = listStyleLevelNames.get(oc.getListStyleName())[oc.getListLevel()]; if (oc.isInContinuedList() && style.isNumber(oc.getListLevel())) { bNeedSaveEnumCounter = true; ba.add("\\setcounter{saveenum}{\\value{"+sCounterName+"}}\n",""); } if (bOrdered) { ba.add("\\begin{enumerate}","\\end{enumerate}"); } else { ba.add("\\begin{itemize}","\\end{itemize}"); } if (oc.isInContinuedList() && style.isNumber(oc.getListLevel())) { ba.add("\n\\setcounter{"+sCounterName+"}{\\value{saveenum}}",""); } } return; } // Step 4: Export with formatting, as "Writer style" custom lists if (oc.getListLevel()<=4) { // TODO: Max level should not be fixed if (!styleNames.containsName(getDisplayName(oc.getListStyleName()))) { createListStyle(oc.getListStyleName()); } String sTeXName="list"+styleNames.getExportName(getDisplayName(oc.getListStyleName())) +"level"+Misc.int2roman(oc.getListLevel()); if (!oc.isInContinuedList() && style.isNumber(oc.getListLevel())) { int nStartValue = Misc.getPosInteger(style.getLevelProperty(oc.getListLevel(),XMLString.TEXT_START_VALUE),1)-1; // Note that we need a blank line after certain constructions to get proper indentation ba.add("\n\\setcounter{"+sTeXName+"}{"+Integer.toString(nStartValue)+"}\n",""); } ba.add("\\begin{"+sTeXName+"}","\\end{"+sTeXName+"}"); } } /**

Apply a list style to a list item.

*/ private void applyListItemStyle(String sStyleName, int nLevel, boolean bHeader, boolean bRestart, int nStartValue, BeforeAfter ba, Context oc) { // Step 1. We may have a style map, this always takes precedence String sDisplayName = ofr.getListStyles().getDisplayName(sStyleName); if (config.getListItemStyleMap().contains(sDisplayName)) { ba.add(config.getListItemStyleMap().getBefore(sDisplayName), config.getListItemStyleMap().getAfter(sDisplayName)); return; } // Step 2: The list style may not exist, or the user wants to ignore it. // In this case we create default lists ListStyle style = ofr.getListStyle(sStyleName); if (style==null || config.formatting()<=LaTeXConfig.IGNORE_MOST) { if (nLevel<=4) { if (bHeader) { ba.add("\\item[] ",""); } else { ba.add("\\item ",""); } } return; } // Step 3: Export as default lists (with redefined labels) // (for list in tables this is the maximum formatting we export) if (config.formatting()==LaTeXConfig.CONVERT_BASIC || (config.formatting()>=LaTeXConfig.CONVERT_MOST && oc.isInTable())) { if (nLevel<=4) { if (bHeader) { ba.add("\\item[] ",""); } else if (bRestart && style.isNumber(nLevel)) { ba.add("\n\\setcounter{enum"+Misc.int2roman(nLevel) +"}{"+(nStartValue-1)+"}\n\\item ",""); } else { ba.add("\\item ",""); } } return; } // Step 4: Export with formatting, as "Writer style" custom lists if (nLevel<=4 && !bHeader) { // TODO: Max level should not be fixed String sTeXName="list"+styleNames.getExportName(getDisplayName(sStyleName)) +"level"+Misc.int2roman(nLevel); if (bRestart && style.isNumber(nLevel)) { ba.add("\\setcounter{"+sTeXName+"}{"+(nStartValue-1)+"}\n",""); } ba.add("\\item ",""); } } /**

Create labels for default lists (enumerate/itemize) based on * a List Style */ private void createListStyleLabels(String sStyleName) { String sTeXName = styleNames.getExportName(getDisplayName(sStyleName)); declarations.append("\\newcommand\\liststyle") .append(sTeXName).append("{%").nl(); ListStyle style = ofr.getListStyle(sStyleName); int nEnum = 0; int nItem = 0; String sName[] = new String[5]; for (int i=1; i<=4; i++) { if (style.isNumber(i)) { sName[i]="enum"+Misc.int2roman(++nEnum); } else { sName[i]="item"+Misc.int2roman(++nItem); } } listStyleLevelNames.put(sStyleName, sName); createLabels(style, sName, 4, false, true, false, declarations); declarations.append("}").nl(); } /**

Create "Writer style" lists based on a List Style.

A list in writer is really a sequence of numbered paragraphs, so this is also how we implement it in LaTeX. The enivronment + redefined \item defines three hooks: \writerlistleftskip, \writerlistparindent, \writerlistlabel which are used by exported paragraph styles to apply numbering. */ private void createListStyle(String sStyleName) { ListStyle style = ofr.getListStyle(sStyleName); // Create labels String sTeXName = styleNames.getExportName(getDisplayName(sStyleName)); String[] sLevelName = new String[5]; for (int i=1; i<=4; i++) { sLevelName[i]="list"+sTeXName+"level"+Misc.int2roman(i); } createLabels(style,sLevelName,4,true,false,true,declarations); // Create environments for (int i=1; i<=4; i++) { // The alignment of the label works the same for old and new format String sTextAlign = style.getLevelStyleProperty(i,XMLString.FO_TEXT_ALIGN); String sAlignmentChar = "l"; // start (or left) is default if (sTextAlign!=null) { if ("end".equals(sTextAlign)) { sAlignmentChar="r"; } else if ("right".equals(sTextAlign)) { sAlignmentChar="r"; } else if ("center".equals(sTextAlign)) { sAlignmentChar="c"; } } if (style.isNewType(i)) { // The new type from ODT 1.2 is somewhat weird; we take it step by step // Fist the list style defines a left margin (leftskip) and a first line indent (parindent) // to *replace* the values from the paragraph style String sMarginLeft = style.getLevelStyleProperty(i, XMLString.FO_MARGIN_LEFT); if (sMarginLeft==null) { sMarginLeft = "0cm"; } String sTextIndent = style.getLevelStyleProperty(i, XMLString.FO_TEXT_INDENT); if (sTextIndent==null) { sTextIndent = "0cm"; } // Generate the LaTeX code to replace these values String sDefWriterlistleftskip = "\\def\\writerlistleftskip{\\setlength\\leftskip{"+sMarginLeft+"}}"; String sDefWriterlistparindent = "\\def\\writerlistparindent{\\setlength\\parindent{"+sTextIndent+"}}"; // Next we have three types of label format: listtab, space, nothing String sFormat = style.getLevelStyleProperty(i, XMLString.TEXT_LABEL_FOLLOWED_BY); // Generate LaTeX code to typeset the label, followed by a space character if required String sTheLabel = "\\label"+sLevelName[i]+("space".equals(sFormat) ? "\\ " : ""); if ("listtab".equals(sFormat) || sAlignmentChar=="r") { // In these cases we typeset the label aligned at a zero width box (rather than as an integrated part of the text) sTheLabel = "\\makebox[0cm][" + sAlignmentChar + "]{"+sTheLabel+"}"; if ("listtab".equals(sFormat)) { // In the tab case we must the calculate the hspace to put *after* the zero width box // This defines the position of an additional tab stop, which really means the start position of the text *after* the label String sTabPos = style.getLevelStyleProperty(i, XMLString.TEXT_LIST_TAB_STOP_POSITION); if (sTabPos==null) { sTabPos = "0cm"; } sTheLabel += "\\hspace{"+Misc.sub(sTabPos, Misc.add(sMarginLeft, sTextIndent))+"}"; } } // We are now ready to declare the list style declarations.append("\\newenvironment{").append(sLevelName[i]).append("}{") // Initialize hooks .append(sDefWriterlistleftskip) .append("\\def\\writerlistparindent{}") .append("\\def\\writerlistlabel{}") // Redefine \item .append("\\def\\item{") // The new parindent is the position of the label .append(sDefWriterlistparindent) .append("\\def\\writerlistlabel{"); if (style.isNumber(i)) { declarations.append("\\stepcounter{").append(sLevelName[i]).append("}"); } declarations.append(sTheLabel).append("\\writerlistremovelabel}}}{}").nl(); } else { String sSpaceBefore = getLength(style,i,XMLString.TEXT_SPACE_BEFORE); String sLabelWidth = getLength(style,i,XMLString.TEXT_MIN_LABEL_WIDTH); String sLabelDistance = getLength(style,i,XMLString.TEXT_MIN_LABEL_DISTANCE); declarations .append("\\newenvironment{") .append(sLevelName[i]).append("}{") .append("\\def\\writerlistleftskip{\\addtolength\\leftskip{") .append(Misc.add(sSpaceBefore,sLabelWidth)).append("}}") .append("\\def\\writerlistparindent{}") .append("\\def\\writerlistlabel{}"); // Redefine \item declarations .append("\\def\\item{") .append("\\def\\writerlistparindent{\\setlength\\parindent{") .append("-").append(sLabelWidth).append("}}") .append("\\def\\writerlistlabel{"); if (style.isNumber(i)) { declarations.append("\\stepcounter{") .append(sLevelName[i]).append("}"); } declarations .append("\\makebox[").append(sLabelWidth).append("][") .append(sAlignmentChar).append("]{") .append("\\label").append(sLevelName[i]).append("}") .append("\\hspace{").append(sLabelDistance).append("}") .append("\\writerlistremovelabel}}}{}").nl(); } } } /**

Create LaTeX list labels from an OOo list style. Examples:

*

Bullets:

*
\newcommand\labelliststylei{\textbullet}
	 *  \newcommand\labelliststyleii{*}
	 *  \newcommand\labelliststyleiii{\textstylebullet{>}}
*

Numbering:

*
\newcounter{liststylei}
	 *  \newcounter{liststyleii}[liststylei]
	 *  \newcounter{liststyleiii}[liststyleii]
	 *  \renewcommand\theliststylei{\Roman{liststylei}}
	 *  \renewcommand\theliststyleii{\Roman{liststylei}.\arabic{liststyleii}}
	 *  \renewcommand\theliststyleiii{\alph{liststyleiii}}
	 *  \newcommand\labelliststylei{\textstylelabel{\theliststylei .}}
	 *  \newcommand\labelliststyleii{\textstylelabel{\theliststyleii .}}
	 *  \newcommand\labelliststyleiii{\textstylelabel{\theliststyleiii )}}
* * @param style the OOo list style to use * @param sName an array of label basenames to use * @param nMaxLevel the highest level in this numbering * @param bDeclareCounters true if counters should be declared (they may * exist already, eg. "section", "subsection"... or "enumi", "enumii"... * @param bRenewLabels true if labels should be defined with \renewcommand * @param bUseTextStyle true if labels should be formatted with the associated text style * (rather than \newcommand). * @param ldp the LaTeXDocumentPortion to add LaTeX code to. */ private void createLabels(ListStyle style, String[] sName, int nMaxLevel, boolean bDeclareCounters, boolean bRenewLabels, boolean bUseTextStyle, LaTeXDocumentPortion ldp) { // Declare counters if required (eg. "\newcounter{countername1}[countername2]") if (bDeclareCounters) { int j = 0; for (int i=1; i<=nMaxLevel; i++) { if (style.isNumber(i)) { ldp.append("\\newcounter{").append(sName[i]).append("}"); if (j>0) { ldp.append("[").append(sName[j]).append("]"); } ldp.nl(); j = i; } } } // Create numbering for each level (eg. "\arabic{countername}") String[] sNumFormat = new String[nMaxLevel+1]; for (int i=1; i<=nMaxLevel; i++) { String s = numFormat(style.getLevelProperty(i,XMLString.STYLE_NUM_FORMAT)); if (s==null) { sNumFormat[i]=""; } else { sNumFormat[i] = s + "{" + sName[i] + "}"; } } // Create numberings (ie. define "\thecountername"): for (int i=1; i<=nMaxLevel; i++) { if (style.isNumber(i)) { ldp.append("\\renewcommand\\the").append(sName[i]).append("{"); int nLevels = Misc.getPosInteger(style.getLevelProperty(i,XMLString.TEXT_DISPLAY_LEVELS),1); for (int j=i-nLevels+1; j