473 lines
No EOL
19 KiB
Java
473 lines
No EOL
19 KiB
Java
/************************************************************************
|
|
*
|
|
* TableFormatter.java
|
|
*
|
|
* Copyright: 2002-2015 by Henrik Just
|
|
*
|
|
* This file is part of Writer2LaTeX.
|
|
*
|
|
* Writer2LaTeX is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Writer2LaTeX is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Writer2LaTeX. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Version 1.6 (2015-04-15)
|
|
*
|
|
*/
|
|
|
|
package writer2latex.latex;
|
|
|
|
import org.w3c.dom.Element;
|
|
import org.w3c.dom.Node;
|
|
|
|
import writer2latex.util.*;
|
|
import writer2latex.office.*;
|
|
import writer2latex.latex.util.BeforeAfter;
|
|
import writer2latex.latex.util.Context;
|
|
|
|
/**
|
|
* <p>This class converts OOo table styles to LaTeX.</p>
|
|
* <p> In OOo the table style is distributed on table, column and cell styles.
|
|
* <p> In LaTeX we have to rearrange this information slightly, so this class
|
|
* takes care of that.</p>
|
|
*/
|
|
public class TableFormatter extends ConverterHelper {
|
|
|
|
//private boolean bApplyCellFormat;
|
|
private TableReader table;
|
|
private char[][] cAlign;
|
|
private char[] cGlobalAlign;
|
|
private boolean[][] bHBorder;
|
|
private boolean[][] bVBorder;
|
|
private boolean[] bGlobalVBorder;
|
|
private String[] sRowColor;
|
|
private String[][] sCellColor;
|
|
private String[] sColumnWidth;
|
|
private boolean bIsLongtable;
|
|
private boolean bIsSupertabular;
|
|
private boolean bIsTabulary;
|
|
private boolean bIsColortbl;
|
|
private boolean bIsSimple;
|
|
|
|
/** <p>Constructor: Create from a TableReader.</p>
|
|
*/
|
|
public TableFormatter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette,
|
|
TableReader table, boolean bAllowPageBreak, boolean bIsInTable) {
|
|
super(ofr,config,palette);
|
|
this.table = table;
|
|
//bApplyCellFormat = config.formatting()>=LaTeXConfig.CONVERT_MOST;
|
|
int nRowCount = table.getRowCount();
|
|
int nColCount = table.getColCount();
|
|
int nSimpleTableLimit = config.getSimpleTableLimit();
|
|
|
|
// Step 1: Collect alignment and identify simple tables
|
|
bIsSimple = true;
|
|
cAlign = new char[nRowCount][nColCount];
|
|
cGlobalAlign = new char[nColCount];
|
|
// Keep track of characters to be counted
|
|
int[] nPendingChars = new int[nRowCount];
|
|
int[] nPendingColSpan = new int[nRowCount];
|
|
int nTableWidth = 0;
|
|
|
|
for (int nCol=0; nCol<nColCount; nCol++) {
|
|
// Collect chars to be counted in this column
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
Element cell = table.getCell(nRow, nCol);
|
|
if (cell!=null && Misc.isElement(cell, XMLString.TABLE_TABLE_CELL)) {
|
|
// Now we're here: Collect alignment
|
|
if (OfficeReader.isSingleParagraph(cell)) {
|
|
Node par = Misc.getChildByTagName(cell,XMLString.TEXT_P);
|
|
StyleWithProperties style = ofr.getParStyle(Misc.getAttribute(par,XMLString.TEXT_STYLE_NAME));
|
|
cAlign[nRow][nCol] = 'l';
|
|
if (style!=null) {
|
|
String sAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
|
|
if ("center".equals(sAlign)) { cAlign[nRow][nCol] = 'c'; }
|
|
else if ("end".equals(sAlign)) { cAlign[nRow][nCol] = 'r'; }
|
|
}
|
|
|
|
}
|
|
else {
|
|
// Found cell with more than one paragraph
|
|
bIsSimple = false;
|
|
}
|
|
// Collect characters (the cell contains this many characters that should be distributed over that many columns)
|
|
nPendingChars[nRow] = OfficeReader.getCharacterCount(cell);
|
|
nPendingColSpan[nRow] = Misc.getPosInteger(cell.getAttribute(XMLString.TABLE_NUMBER_COLUMNS_SPANNED), 1);
|
|
}
|
|
}
|
|
// Determine the number of characters to count *now* (because they cannot be postponed to next column)
|
|
int nColChars = 0;
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
if (nPendingColSpan[nRow]==1) {
|
|
nColChars = Math.max(nColChars, nPendingChars[nRow]);
|
|
}
|
|
}
|
|
// Reduce pending chars and increase table width
|
|
nTableWidth += nColChars;
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
if (nPendingColSpan[nRow]>=1) {
|
|
nPendingChars[nRow] = Math.max(0, nPendingChars[nRow]-nColChars);
|
|
nPendingColSpan[nRow]--;
|
|
}
|
|
}
|
|
}
|
|
if (nTableWidth>nSimpleTableLimit) bIsSimple = false;
|
|
|
|
// Step 2: Create global alignment
|
|
for (int nCol=0; nCol<nColCount; nCol++) {
|
|
int nCenter = 0;
|
|
int nRight = 0;
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
if (cAlign[nRow][nCol]=='c') { nCenter++; }
|
|
else if (cAlign[nRow][nCol]=='r') { nRight++; }
|
|
}
|
|
cGlobalAlign[nCol] = 'l';
|
|
int nLeft = nColCount-nCenter-nRight;
|
|
if (nCenter>nLeft) {
|
|
if (nRight>nLeft) { cGlobalAlign[nCol] = 'r'; }
|
|
else { cGlobalAlign[nCol] = 'c'; }
|
|
}
|
|
else if (nRight>nLeft) {
|
|
cGlobalAlign[nCol] = 'r';
|
|
}
|
|
}
|
|
|
|
// Step 3: Initialize borders:
|
|
bHBorder = new boolean[nRowCount+1][nColCount];
|
|
for (int nRow=0; nRow<=nRowCount; nRow++) {
|
|
for (int nCol=0; nCol<nColCount; nCol++) {
|
|
bHBorder[nRow][nCol] = false;
|
|
}
|
|
}
|
|
bVBorder = new boolean[nRowCount][nColCount+1];
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
for (int nCol=0; nCol<=nColCount; nCol++) {
|
|
bVBorder[nRow][nCol] = false;
|
|
}
|
|
}
|
|
|
|
// Step 4: Collect borders from cell styles:
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
int nCol = 0;
|
|
while (nCol<nColCount) {
|
|
Node cell = table.getCell(nRow,nCol);
|
|
if (cell!=null) {
|
|
String sStyleName = Misc.getAttribute(cell,XMLString.TABLE_STYLE_NAME);
|
|
StyleWithProperties style = ofr.getCellStyle(sStyleName);
|
|
int nColSpan = Misc.getPosInteger(Misc.getAttribute(cell,
|
|
XMLString.TABLE_NUMBER_COLUMNS_SPANNED),1);
|
|
boolean bLeft = false;
|
|
boolean bRight = false;
|
|
boolean bTop = false;
|
|
boolean bBottom = false;
|
|
if (style!=null) {
|
|
String sBorder = style.getProperty(XMLString.FO_BORDER);
|
|
if (sBorder!=null && !"none".equals(sBorder)) {
|
|
bLeft = true; bRight = true; bTop = true; bBottom = true;
|
|
}
|
|
sBorder = style.getProperty(XMLString.FO_BORDER_LEFT);
|
|
if (sBorder!=null && !"none".equals(sBorder)) {
|
|
bLeft = true;
|
|
}
|
|
sBorder = style.getProperty(XMLString.FO_BORDER_RIGHT);
|
|
if (sBorder!=null && !"none".equals(sBorder)) {
|
|
bRight = true;
|
|
}
|
|
sBorder = style.getProperty(XMLString.FO_BORDER_TOP);
|
|
if (sBorder!=null && !"none".equals(sBorder)) {
|
|
bTop = true;
|
|
}
|
|
sBorder = style.getProperty(XMLString.FO_BORDER_BOTTOM);
|
|
if (sBorder!=null && !"none".equals(sBorder)) {
|
|
bBottom = true;
|
|
}
|
|
}
|
|
bVBorder[nRow][nCol] |= bLeft;
|
|
bVBorder[nRow][nCol+nColSpan] |= bRight;
|
|
do {
|
|
bHBorder[nRow][nCol] |= bTop;
|
|
bHBorder[nRow+1][nCol] |= bBottom;
|
|
nCol++;
|
|
} while (--nColSpan>0);
|
|
}
|
|
else { // Non-existing cell, treat as empty cell with no borders
|
|
nCol++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step 5: Create global vertical borders based on simple majority
|
|
// (in order to minimize the number of \multicolum{1} entries)
|
|
bGlobalVBorder = new boolean[nColCount+1];
|
|
for (int nCol=0; nCol<=nColCount; nCol++) {
|
|
int nBalance = 0;
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
nBalance += bVBorder[nRow][nCol] ? 1 : -1;
|
|
}
|
|
bGlobalVBorder[nCol] = nBalance>0;
|
|
}
|
|
|
|
// Step 6: Get background colors
|
|
sRowColor = new String[nRowCount];
|
|
sCellColor = new String[nRowCount][nColCount];
|
|
if (config.useColortbl()) {
|
|
// Table background
|
|
String sTableColor = null;
|
|
StyleWithProperties tableStyle = ofr.getTableStyle(table.getTableStyleName());
|
|
if (tableStyle!=null) {
|
|
sTableColor = tableStyle.getProperty(XMLString.FO_BACKGROUND_COLOR);
|
|
}
|
|
|
|
// Row background
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
StyleWithProperties rowStyle = ofr.getRowStyle(table.getRow(nRow).getStyleName());
|
|
if (rowStyle!=null) {
|
|
sRowColor[nRow] = rowStyle.getProperty(XMLString.FO_BACKGROUND_COLOR);
|
|
}
|
|
if (sRowColor[nRow]==null) {
|
|
sRowColor[nRow] = sTableColor;
|
|
}
|
|
if (sRowColor[nRow]!=null) {
|
|
bIsColortbl = true;
|
|
}
|
|
}
|
|
|
|
// Cell background
|
|
for (int nRow=0; nRow<nRowCount; nRow++) {
|
|
for (int nCol=0; nCol<nColCount; nCol++) {
|
|
StyleWithProperties cellStyle = ofr.getCellStyle(Misc.getAttribute(table.getCell(nRow,nCol),XMLString.TABLE_STYLE_NAME));
|
|
if (cellStyle!=null) {
|
|
sCellColor[nRow][nCol] = cellStyle.getProperty(XMLString.FO_BACKGROUND_COLOR);
|
|
if (sCellColor[nRow][nCol]!=null) {
|
|
bIsColortbl = true;
|
|
if (sCellColor[nRow][nCol].equals(sRowColor[nRow])) {
|
|
// Avoid redundant cell background
|
|
sCellColor[nRow][nCol] = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Step 7: Read column style information
|
|
sColumnWidth = new String[nColCount];
|
|
for (int nCol=0; nCol<nColCount; nCol++) {
|
|
StyleWithProperties colStyle
|
|
= ofr.getColumnStyle(table.getCol(nCol).getStyleName());
|
|
if (colStyle!=null) {
|
|
sColumnWidth[nCol]
|
|
= colStyle.getProperty(XMLString.STYLE_COLUMN_WIDTH);
|
|
}
|
|
if (sColumnWidth[nCol]==null) { // Emergency! should never happen!
|
|
sColumnWidth[nCol]="2cm";
|
|
}
|
|
}
|
|
|
|
// Step 8: Identify longtable, supertabular or tabulary
|
|
bIsLongtable = false; bIsSupertabular = false; bIsTabulary = false;
|
|
if (!table.isSubTable() && !bIsInTable) {
|
|
String sStyleName = table.getTableStyleName();
|
|
StyleWithProperties style = ofr.getTableStyle(sStyleName);
|
|
boolean bMayBreak = style==null ||
|
|
!"false".equals(style.getProperty(XMLString.STYLE_MAY_BREAK_BETWEEN_ROWS));
|
|
if (config.useLongtable() && bMayBreak && bAllowPageBreak) {
|
|
bIsLongtable = true;
|
|
}
|
|
else if (config.useSupertabular() && bMayBreak && bAllowPageBreak) {
|
|
bIsSupertabular = true;
|
|
}
|
|
else if (!bIsSimple && config.useTabulary()) {
|
|
bIsTabulary = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@Override public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
|
|
}
|
|
|
|
/** is this a longtable? */
|
|
public boolean isLongtable() { return bIsLongtable; }
|
|
|
|
/** is this a supertabular? */
|
|
public boolean isSupertabular() { return bIsSupertabular; }
|
|
|
|
/** is this a tabulary? */
|
|
public boolean isTabulary() { return bIsTabulary; }
|
|
|
|
/** is this a colortbl? */
|
|
public boolean isColortbl() { return bIsColortbl; }
|
|
|
|
/** is this a simple table (lcr columns rather than p{})? */
|
|
public boolean isSimple() { return bIsSimple; }
|
|
|
|
/**
|
|
* <p>Create table environment based on table style.</p>
|
|
* <p>Returns eg. "\begin{longtable}{m{2cm}|m{4cm}}", "\end{longtable}".</p>
|
|
* @param ba the <code>BeforeAfter</code> to contain the table code
|
|
* @param baAlign the <code>BeforeAfter</code> to contain the alignment code, if it's separate
|
|
* @param bInFloat true if the table should be floating
|
|
*/
|
|
public void applyTableStyle(BeforeAfter ba, BeforeAfter baAlign, boolean bInFloat) {
|
|
// Read formatting info from table style
|
|
// Only supported properties are alignment and may-break-between-rows.
|
|
String sStyleName = table.getTableStyleName();
|
|
StyleWithProperties style = ofr.getTableStyle(sStyleName);
|
|
char cAlign = 'c';
|
|
if (style!=null && !table.isSubTable()) {
|
|
String s = style.getProperty(XMLString.TABLE_ALIGN);
|
|
if ("left".equals(s)) { cAlign='l'; }
|
|
else if ("right".equals(s)) { cAlign='r'; }
|
|
}
|
|
String sAlign="center";
|
|
switch (cAlign) {
|
|
case 'c': sAlign="center"; break;
|
|
case 'r': sAlign="flushright"; break;
|
|
case 'l': sAlign="flushleft";
|
|
}
|
|
|
|
// Create table alignment (for supertabular, tabular and tabulary)
|
|
if (!bIsLongtable && !table.isSubTable()) {
|
|
if (bInFloat & !bIsSupertabular) {
|
|
// Inside a float we don't want the extra glue added by the flushleft/center/flushright environment
|
|
switch (cAlign) {
|
|
case 'c' : baAlign.add("\\centering\n", ""); break;
|
|
case 'r' : baAlign.add("\\raggedleft\n", ""); break;
|
|
case 'l' : baAlign.add("\\raggedright\n", "");
|
|
}
|
|
}
|
|
else {
|
|
// But outside floats we do want it
|
|
baAlign.add("\\begin{"+sAlign+"}\n","\\end{"+sAlign+"}\n");
|
|
}
|
|
}
|
|
|
|
// Create table declaration
|
|
if (bIsLongtable) {
|
|
ba.add("\\begin{longtable}["+cAlign+"]", "\\end{longtable}");
|
|
}
|
|
else if (bIsSupertabular) {
|
|
ba.add("\\begin{supertabular}","\\end{supertabular}");
|
|
}
|
|
else if (bIsTabulary) {
|
|
ba.add("\\begin{tabulary}{"+table.getTableWidth()+"}","\\end{tabulary}");
|
|
}
|
|
else if (!table.isSubTable()) {
|
|
ba.add("\\begin{tabular}","\\end{tabular}");
|
|
}
|
|
else { // subtables should occupy the entire width, including padding!
|
|
ba.add("\\hspace*{-\\tabcolsep}\\begin{tabular}",
|
|
"\\end{tabular}\\hspace*{-\\tabcolsep}");
|
|
}
|
|
|
|
// columns
|
|
ba.add("{","");
|
|
if (bGlobalVBorder[0]) { ba.add("|",""); }
|
|
int nColCount = table.getColCount();
|
|
for (int nCol=0; nCol<nColCount; nCol++){
|
|
if (bIsSimple) {
|
|
ba.add(Character.toString(cGlobalAlign[nCol]),"");
|
|
}
|
|
else if (!bIsTabulary) {
|
|
// note: The column width in OOo includes padding, which we subtract
|
|
ba.add("m{"+Calc.add(sColumnWidth[nCol],"-0.2cm")+"}","");
|
|
}
|
|
else {
|
|
ba.add("J","");
|
|
}
|
|
if (bGlobalVBorder[nCol+1]) { ba.add("|",""); }
|
|
}
|
|
ba.add("}","");
|
|
}
|
|
|
|
/** <p>Create interrow material</p> */
|
|
public String getInterrowMaterial(int nRow) {
|
|
int nColCount = table.getColCount();
|
|
int nCount = 0;
|
|
for (int nCol=0; nCol<nColCount; nCol++) {
|
|
if (bHBorder[nRow][nCol]) { nCount++; }
|
|
}
|
|
if (nCount==0) { // no borders at this row
|
|
return "";
|
|
}
|
|
else if (nCount==nColCount) { // complete set of borders
|
|
return "\\hline";
|
|
}
|
|
else { // individual borders for each column
|
|
StringBuilder buf = new StringBuilder();
|
|
buf.append("\\hhline{");
|
|
for (int nCol=0; nCol<nColCount; nCol++) {
|
|
if (bHBorder[nRow][nCol]) { buf.append("-"); }
|
|
else { buf.append("~"); }
|
|
}
|
|
buf.append("}");
|
|
/* TODO: hhline.sty should be optional, and i not used, do as before:
|
|
boolean bInCline = false;
|
|
for (int nCol=0; nCol<nColCount; nCol++) {
|
|
if (bInCline && !bHBorder[nRow][nCol]) { // close \cline
|
|
buf.append(nCol).append("}");
|
|
bInCline = false;
|
|
}
|
|
else if (!bInCline && bHBorder[nRow][nCol]) { // open \cline
|
|
buf.append("\\cline{").append(nCol+1).append("-");
|
|
bInCline = true;
|
|
}
|
|
}
|
|
if (bInCline) { buf.append(nColCount).append("}"); }
|
|
*/
|
|
return buf.toString();
|
|
}
|
|
}
|
|
|
|
/** <p>Get material to put before a table row (background color)
|
|
*/
|
|
public void applyRowStyle(int nRow, BeforeAfter ba, Context context) {
|
|
palette.getColorCv().applyBgColor("\\rowcolor",sRowColor[nRow],ba,context);
|
|
}
|
|
|
|
/** Get material to put before and after a table cell.
|
|
* In case of columnspan or different borders this will contain a \multicolumn command.
|
|
*/
|
|
public void applyCellStyle(int nRow, int nCol, BeforeAfter ba, Context context) {
|
|
Node cell = table.getCell(nRow,nCol);
|
|
int nColSpan = Misc.getPosInteger(Misc.getAttribute(cell,
|
|
XMLString.TABLE_NUMBER_COLUMNS_SPANNED),1);
|
|
// Construct column declaration as needed
|
|
boolean bNeedLeft = (nCol==0) && (bVBorder[nRow][0]!=bGlobalVBorder[0]);
|
|
boolean bNeedRight = bVBorder[nRow][nCol+1]!=bGlobalVBorder[nCol+1];
|
|
boolean bNeedAlign = bIsSimple && cGlobalAlign[nCol]!=cAlign[nRow][nCol];
|
|
// calculate column width
|
|
String sTotalColumnWidth = sColumnWidth[nCol];
|
|
for (int i=nCol+1; i<nCol+nColSpan; i++) {
|
|
sTotalColumnWidth = Calc.add(sTotalColumnWidth,sColumnWidth[i]);
|
|
}
|
|
sTotalColumnWidth = Calc.add(sTotalColumnWidth,"-0.2cm");
|
|
|
|
if (bNeedAlign || bNeedLeft || bNeedRight || nColSpan>1) {
|
|
ba.add("\\multicolumn{"+nColSpan+"}{","");
|
|
if (nCol==0 && bVBorder[nRow][0]) { ba.add("|",""); }
|
|
if (bIsSimple) {
|
|
ba.add(Character.toString(cAlign[nRow][nCol]),"");
|
|
}
|
|
else {
|
|
ba.add("m{"+sTotalColumnWidth+"}","");
|
|
}
|
|
if (bVBorder[nRow][nCol+nColSpan]) { ba.add("|",""); }
|
|
ba.add("}{","}");
|
|
}
|
|
|
|
palette.getColorCv().applyBgColor("\\cellcolor",sCellColor[nRow][nCol],ba,context);
|
|
|
|
}
|
|
|
|
} |