|
|
@@ -22,21 +22,11 @@ package org.apache.fop.layoutmgr.table; |
|
|
|
import java.util.Iterator; |
|
|
|
import java.util.LinkedList; |
|
|
|
import java.util.List; |
|
|
|
import java.util.ListIterator; |
|
|
|
|
|
|
|
import org.apache.commons.logging.Log; |
|
|
|
import org.apache.commons.logging.LogFactory; |
|
|
|
import org.apache.fop.fo.FONode; |
|
|
|
import org.apache.fop.fo.FONode.FONodeIterator; |
|
|
|
import org.apache.fop.fo.flow.Marker; |
|
|
|
import org.apache.fop.fo.flow.table.GridUnit; |
|
|
|
import org.apache.fop.fo.flow.table.PrimaryGridUnit; |
|
|
|
import org.apache.fop.fo.flow.table.Table; |
|
|
|
import org.apache.fop.fo.flow.table.TableBody; |
|
|
|
import org.apache.fop.fo.flow.table.TableCell; |
|
|
|
import org.apache.fop.fo.flow.table.TableColumn; |
|
|
|
import org.apache.fop.fo.flow.table.TableRow; |
|
|
|
import org.apache.fop.fo.properties.CommonBorderPaddingBackground; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
@@ -53,78 +43,30 @@ public class TableRowIterator { |
|
|
|
/** Selects the table-footer elements for iteration. */ |
|
|
|
public static final int FOOTER = 2; |
|
|
|
|
|
|
|
/** Logger **/ |
|
|
|
private static Log log = LogFactory.getLog(TableRowIterator.class); |
|
|
|
|
|
|
|
/** The table on which this instance operates. */ |
|
|
|
protected Table table; |
|
|
|
/** Column setup of the operated table. */ |
|
|
|
private ColumnSetup columns; |
|
|
|
|
|
|
|
/** Part of the table over which to iterate. One of BODY, HEADER or FOOTER. */ |
|
|
|
private int tablePart; |
|
|
|
|
|
|
|
/** Holds the currently fetched row (TableCell instances). */ |
|
|
|
private List currentRow = new java.util.ArrayList(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Holds the grid units of cells from the previous row which will span over the |
|
|
|
* current row. Should be read "previous row's spanning cells". List of GridUnit |
|
|
|
* instances. |
|
|
|
*/ |
|
|
|
private List previousRowsSpanningCells = new java.util.ArrayList(); |
|
|
|
|
|
|
|
/** Index of the row currently being fetched. */ |
|
|
|
private int fetchIndex = -1; |
|
|
|
|
|
|
|
/** |
|
|
|
* Number of spans found on the current row which will also span over the next row. |
|
|
|
*/ |
|
|
|
private int pendingRowSpans; |
|
|
|
|
|
|
|
//TODO rows should later be a Jakarta Commons LinkedList so concurrent modifications while |
|
|
|
//using a ListIterator are possible |
|
|
|
/** List of cached rows. This is a list of EffRow elements. */ |
|
|
|
private List fetchedRows = new java.util.ArrayList(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Index of the row that will be returned at the next iteration step. Note that there |
|
|
|
* is no direct relation between this field and {@link |
|
|
|
* TableRowIterator#fetchIndex}. The fetching of rows and the iterating over them are |
|
|
|
* two different processes. Hence the two indices. */ |
|
|
|
private int iteratorIndex = 0; |
|
|
|
|
|
|
|
//prefetch state |
|
|
|
/** |
|
|
|
* Iterator over the requested table's part(s) (header, footer, body). Note that |
|
|
|
* a table may have several table-body children, hence the iterator. |
|
|
|
*/ |
|
|
|
private ListIterator tablePartIterator = null; |
|
|
|
/** Iterator over a part's child elements (either table-rows or table-cells). */ |
|
|
|
private ListIterator tablePartChildIterator = null; |
|
|
|
|
|
|
|
private Iterator rowGroupsIter; |
|
|
|
|
|
|
|
/** |
|
|
|
* Creates a new TableRowIterator. |
|
|
|
* @param table the table to iterate over |
|
|
|
* @param columns the column setup for the table |
|
|
|
* @param tablePart indicates what part of the table to iterate over (HEADER, FOOTER, BODY) |
|
|
|
*/ |
|
|
|
public TableRowIterator(Table table, ColumnSetup columns, int tablePart) { |
|
|
|
public TableRowIterator(Table table, int tablePart) { |
|
|
|
this.table = table; |
|
|
|
this.columns = columns; |
|
|
|
this.tablePart = tablePart; |
|
|
|
switch(tablePart) { |
|
|
|
case HEADER: { |
|
|
|
case HEADER: |
|
|
|
rowGroupsIter = table.getTableHeader().getRowGroups().iterator(); |
|
|
|
break; |
|
|
|
} |
|
|
|
case FOOTER: { |
|
|
|
case FOOTER: |
|
|
|
rowGroupsIter = table.getTableFooter().getRowGroups().iterator(); |
|
|
|
break; |
|
|
|
} |
|
|
|
default: { |
|
|
|
default: |
|
|
|
List rowGroupsList = new LinkedList(); |
|
|
|
// TODO this is ugly |
|
|
|
for (FONodeIterator iter = table.getChildNodes(); iter.hasNext();) { |
|
|
@@ -134,7 +76,6 @@ public class TableRowIterator { |
|
|
|
} |
|
|
|
} |
|
|
|
rowGroupsIter = rowGroupsList.iterator(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@@ -158,396 +99,4 @@ public class TableRowIterator { |
|
|
|
return effRowGroup; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns the row at the given index, fetching rows up to the requested one if |
|
|
|
* necessary. |
|
|
|
* |
|
|
|
* @return the requested row, or null if there is no row at the given index (index |
|
|
|
* < 0 or end of table-part reached) |
|
|
|
*/ |
|
|
|
private EffRow getRow(int index) { |
|
|
|
boolean moreRows = true; |
|
|
|
while (moreRows && fetchedRows.size() <= index) { |
|
|
|
moreRows = prefetchNext(); |
|
|
|
} |
|
|
|
// Whatever the value of index, getCachedRow will handle it nicely |
|
|
|
return getCachedRow(index); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns the next effective row. |
|
|
|
* @return the requested effective row or null if there is no more row. |
|
|
|
*/ |
|
|
|
private EffRow getNextRow() { |
|
|
|
return getRow(iteratorIndex++); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns the row preceding the given row, without moving the iterator. |
|
|
|
* |
|
|
|
* @param row a row in the iterated table part |
|
|
|
* @return the preceding row, or null if there is no such row (the given row is the |
|
|
|
* first one in the table part) |
|
|
|
*/ |
|
|
|
EffRow getPrecedingRow(EffRow row) { |
|
|
|
return getRow(row.getIndex() - 1); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns the row following the given row, without moving the iterator. |
|
|
|
* |
|
|
|
* @param row a row in the iterated table part |
|
|
|
* @return the following row, or null if there is no more row |
|
|
|
*/ |
|
|
|
EffRow getFollowingRow(EffRow row) { |
|
|
|
return getRow(row.getIndex() + 1); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns the first effective row. |
|
|
|
* @return the requested effective row. |
|
|
|
*/ |
|
|
|
EffRow getFirstRow() { |
|
|
|
if (fetchedRows.size() == 0) { |
|
|
|
prefetchNext(); |
|
|
|
} |
|
|
|
return getCachedRow(0); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns the last effective row. |
|
|
|
* <p>Note:This is inefficient for large tables because the whole table |
|
|
|
* if preloaded.</p> |
|
|
|
* @return the requested effective row. |
|
|
|
*/ |
|
|
|
EffRow getLastRow() { |
|
|
|
while (prefetchNext()) { |
|
|
|
//nop |
|
|
|
} |
|
|
|
return getCachedRow(fetchedRows.size() - 1); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns a cached effective row. If the given index points outside the range of rows |
|
|
|
* (negative or greater than the number of already fetched rows), this methods |
|
|
|
* terminates nicely by returning null. |
|
|
|
* |
|
|
|
* @param index index of the row (zero-based) |
|
|
|
* @return the requested effective row or null if (index < 0 || index >= the |
|
|
|
* number of already fetched rows) |
|
|
|
*/ |
|
|
|
private EffRow getCachedRow(int index) { |
|
|
|
if (index < 0 || index >= fetchedRows.size()) { |
|
|
|
return null; |
|
|
|
} else { |
|
|
|
return (EffRow)fetchedRows.get(index); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Fetches the next row. |
|
|
|
* |
|
|
|
* @return true if there was a row to fetch; otherwise, false (the end of the |
|
|
|
* table-part has been reached) |
|
|
|
*/ |
|
|
|
private boolean prefetchNext() { |
|
|
|
boolean firstInTable = false; |
|
|
|
boolean firstInTablePart = false; |
|
|
|
// If we are at the end of the current table part |
|
|
|
if (tablePartChildIterator != null && !tablePartChildIterator.hasNext()) { |
|
|
|
//force skip on to next component |
|
|
|
if (pendingRowSpans > 0) { |
|
|
|
this.currentRow.clear(); |
|
|
|
this.fetchIndex++; |
|
|
|
EffRow gridUnits = buildGridRow(this.currentRow, null); |
|
|
|
log.debug(gridUnits); |
|
|
|
fetchedRows.add(gridUnits); |
|
|
|
return true; |
|
|
|
} |
|
|
|
tablePartChildIterator = null; |
|
|
|
if (fetchedRows.size() > 0) { |
|
|
|
getCachedRow(fetchedRows.size() - 1).setFlagForAllGridUnits( |
|
|
|
GridUnit.LAST_IN_PART, true); |
|
|
|
} |
|
|
|
} |
|
|
|
// If the iterating over the current table-part has not started yet |
|
|
|
if (tablePartChildIterator == null) { |
|
|
|
if (tablePartIterator.hasNext()) { |
|
|
|
tablePartChildIterator = ((TableBody)tablePartIterator.next()).getChildNodes(); |
|
|
|
if (fetchedRows.size() == 0) { |
|
|
|
firstInTable = true; |
|
|
|
} |
|
|
|
firstInTablePart = true; |
|
|
|
} else { |
|
|
|
//no more rows in that part of the table |
|
|
|
if (fetchedRows.size() > 0) { |
|
|
|
getCachedRow(fetchedRows.size() - 1).setFlagForAllGridUnits( |
|
|
|
GridUnit.LAST_IN_PART, true); |
|
|
|
// If the last row is the last of the table |
|
|
|
if (tablePart == FOOTER |
|
|
|
|| (tablePart == BODY && table.getTableFooter() == null)) { |
|
|
|
getCachedRow(fetchedRows.size() - 1).setFlagForAllGridUnits( |
|
|
|
GridUnit.LAST_IN_TABLE, true); |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
Object node = tablePartChildIterator.next(); |
|
|
|
while (node instanceof Marker) { |
|
|
|
node = tablePartChildIterator.next(); |
|
|
|
} |
|
|
|
this.currentRow.clear(); |
|
|
|
this.fetchIndex++; |
|
|
|
TableRow rowFO = null; |
|
|
|
if (node instanceof TableRow) { |
|
|
|
rowFO = (TableRow)node; |
|
|
|
ListIterator cellIterator = rowFO.getChildNodes(); |
|
|
|
while (cellIterator.hasNext()) { |
|
|
|
this.currentRow.add(cellIterator.next()); |
|
|
|
} |
|
|
|
} else if (node instanceof TableCell) { |
|
|
|
this.currentRow.add(node); |
|
|
|
if (!((TableCell)node).endsRow()) { |
|
|
|
while (tablePartChildIterator.hasNext()) { |
|
|
|
TableCell cell = (TableCell)tablePartChildIterator.next(); |
|
|
|
if (cell.startsRow()) { |
|
|
|
//next row already starts here, one step back |
|
|
|
tablePartChildIterator.previous(); |
|
|
|
break; |
|
|
|
} |
|
|
|
this.currentRow.add(cell); |
|
|
|
if (cell.endsRow()) { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
throw new IllegalStateException("Illegal class found: " + node.getClass().getName()); |
|
|
|
} |
|
|
|
EffRow gridUnits = buildGridRow(this.currentRow, rowFO); |
|
|
|
if (firstInTablePart) { |
|
|
|
gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_PART, true); |
|
|
|
} |
|
|
|
if (firstInTable && (tablePart == HEADER || table.getTableHeader() == null) |
|
|
|
&& tablePart != FOOTER) { |
|
|
|
gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_TABLE, true); |
|
|
|
} |
|
|
|
log.debug(gridUnits); |
|
|
|
fetchedRows.add(gridUnits); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Places the given object at the given position in the list, first extending it if |
|
|
|
* necessary with null objects to reach the position. |
|
|
|
* |
|
|
|
* @param list the list in which to place the object |
|
|
|
* @param position index at which the object must be placed (0-based) |
|
|
|
* @param obj the object to place |
|
|
|
*/ |
|
|
|
private void safelySetListItem(List list, int position, Object obj) { |
|
|
|
while (position >= list.size()) { |
|
|
|
list.add(null); |
|
|
|
} |
|
|
|
list.set(position, obj); |
|
|
|
} |
|
|
|
|
|
|
|
private Object safelyGetListItem(List list, int position) { |
|
|
|
if (position >= list.size()) { |
|
|
|
return null; |
|
|
|
} else { |
|
|
|
return list.get(position); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Builds the list of grid units corresponding to the given table row. |
|
|
|
* |
|
|
|
* @param cells list of cells starting at the current row |
|
|
|
* @param rowFO the fo:table-row object containing the row, possibly null |
|
|
|
* @return the list of grid units |
|
|
|
*/ |
|
|
|
private EffRow buildGridRow(List cells, TableRow rowFO) { |
|
|
|
EffRow row = new EffRow(this.fetchIndex, tablePart, null); |
|
|
|
List gridUnits = row.getGridUnits(); |
|
|
|
|
|
|
|
TableBody bodyFO = null; |
|
|
|
|
|
|
|
//Create all row-spanned grid units based on information from the previous row |
|
|
|
int colnum = 1; |
|
|
|
GridUnit[] horzSpan = null; // Grid units horizontally spanned by a single cell |
|
|
|
if (pendingRowSpans > 0) { |
|
|
|
ListIterator spanIter = previousRowsSpanningCells.listIterator(); |
|
|
|
while (spanIter.hasNext()) { |
|
|
|
GridUnit gu = (GridUnit)spanIter.next(); |
|
|
|
if (gu != null) { |
|
|
|
if (gu.getColSpanIndex() == 0) { |
|
|
|
horzSpan = new GridUnit[gu.getCell().getNumberColumnsSpanned()]; |
|
|
|
} |
|
|
|
// GridUnit newGU = gu.createNextRowSpanningGridUnit(); |
|
|
|
GridUnit newGU = null; |
|
|
|
newGU.setRow(rowFO); |
|
|
|
safelySetListItem(gridUnits, colnum - 1, newGU); |
|
|
|
horzSpan[newGU.getColSpanIndex()] = newGU; |
|
|
|
if (newGU.isLastGridUnitColSpan()) { |
|
|
|
//Add the array of row-spanned grid units to the primary grid unit |
|
|
|
newGU.getPrimary().addRow(horzSpan); |
|
|
|
horzSpan = null; |
|
|
|
} |
|
|
|
if (newGU.isLastGridUnitRowSpan()) { |
|
|
|
spanIter.set(null); |
|
|
|
pendingRowSpans--; |
|
|
|
} else { |
|
|
|
spanIter.set(newGU); |
|
|
|
} |
|
|
|
} |
|
|
|
colnum++; |
|
|
|
} |
|
|
|
} |
|
|
|
if (pendingRowSpans < 0) { |
|
|
|
throw new IllegalStateException("pendingRowSpans must not become negative!"); |
|
|
|
} |
|
|
|
|
|
|
|
//Transfer available cells to their slots |
|
|
|
colnum = 1; |
|
|
|
ListIterator iter = cells.listIterator(); |
|
|
|
while (iter.hasNext()) { |
|
|
|
TableCell cell = (TableCell)iter.next(); |
|
|
|
|
|
|
|
colnum = cell.getColumnNumber(); |
|
|
|
|
|
|
|
//TODO: remove the check below??? |
|
|
|
//shouldn't happen here, since |
|
|
|
//overlapping cells already caught in |
|
|
|
//fo.flow.TableCell.bind()... |
|
|
|
GridUnit other = (GridUnit)safelyGetListItem(gridUnits, colnum - 1); |
|
|
|
if (other != null) { |
|
|
|
String err = "A table-cell (" |
|
|
|
+ cell.getContextInfo() |
|
|
|
+ ") is overlapping with another (" |
|
|
|
+ other.getCell().getContextInfo() |
|
|
|
+ ") in column " + colnum; |
|
|
|
throw new IllegalStateException(err |
|
|
|
+ " (this should have been catched by FO tree validation)"); |
|
|
|
} |
|
|
|
TableColumn col = columns.getColumn(colnum); |
|
|
|
|
|
|
|
//Add grid unit for primary grid unit |
|
|
|
PrimaryGridUnit gu = new PrimaryGridUnit(cell, col, colnum - 1, this.fetchIndex); |
|
|
|
safelySetListItem(gridUnits, colnum - 1, gu); |
|
|
|
boolean hasRowSpanningLeft = !gu.isLastGridUnitRowSpan(); |
|
|
|
if (hasRowSpanningLeft) { |
|
|
|
pendingRowSpans++; |
|
|
|
safelySetListItem(previousRowsSpanningCells, colnum - 1, gu); |
|
|
|
} |
|
|
|
|
|
|
|
if (gu.hasSpanning()) { |
|
|
|
//Add grid units on spanned slots if any |
|
|
|
horzSpan = new GridUnit[cell.getNumberColumnsSpanned()]; |
|
|
|
horzSpan[0] = gu; |
|
|
|
for (int j = 1; j < cell.getNumberColumnsSpanned(); j++) { |
|
|
|
colnum++; |
|
|
|
GridUnit guSpan = new GridUnit(gu, columns.getColumn(colnum), colnum - 1, j, 0); |
|
|
|
//TODO: remove the check below??? |
|
|
|
other = (GridUnit)safelyGetListItem(gridUnits, colnum - 1); |
|
|
|
if (other != null) { |
|
|
|
String err = "A table-cell (" |
|
|
|
+ cell.getContextInfo() |
|
|
|
+ ") is overlapping with another (" |
|
|
|
+ other.getCell().getContextInfo() |
|
|
|
+ ") in column " + colnum; |
|
|
|
throw new IllegalStateException(err |
|
|
|
+ " (this should have been catched by FO tree validation)"); |
|
|
|
} |
|
|
|
safelySetListItem(gridUnits, colnum - 1, guSpan); |
|
|
|
if (hasRowSpanningLeft) { |
|
|
|
pendingRowSpans++; |
|
|
|
safelySetListItem(previousRowsSpanningCells, colnum - 1, guSpan); |
|
|
|
} |
|
|
|
horzSpan[j] = guSpan; |
|
|
|
} |
|
|
|
gu.addRow(horzSpan); |
|
|
|
} |
|
|
|
|
|
|
|
//Gather info for empty grid units (used later) |
|
|
|
if (bodyFO == null) { |
|
|
|
bodyFO = gu.getBody(); |
|
|
|
} |
|
|
|
|
|
|
|
colnum++; |
|
|
|
} |
|
|
|
|
|
|
|
//Post-processing the list (looking for gaps and resolve start and end borders) |
|
|
|
// fillEmptyGridUnits(gridUnits, rowFO, bodyFO); |
|
|
|
resolveStartEndBorders(gridUnits); |
|
|
|
|
|
|
|
return row; |
|
|
|
} |
|
|
|
|
|
|
|
// private void fillEmptyGridUnits(List gridUnits, TableRow row, TableBody body) { |
|
|
|
// for (int pos = 1; pos <= gridUnits.size(); pos++) { |
|
|
|
// GridUnit gu = (GridUnit)gridUnits.get(pos - 1); |
|
|
|
// |
|
|
|
// //Empty grid units |
|
|
|
// if (gu == null) { |
|
|
|
// //Add grid unit |
|
|
|
// gu = new EmptyGridUnit(row, columns.getColumn(pos), body, |
|
|
|
// pos - 1); |
|
|
|
// gridUnits.set(pos - 1, gu); |
|
|
|
// } |
|
|
|
// |
|
|
|
// //Set flags |
|
|
|
// gu.setFlag(GridUnit.IN_FIRST_COLUMN, (pos == 1)); |
|
|
|
// gu.setFlag(GridUnit.IN_LAST_COLUMN, (pos == gridUnits.size())); |
|
|
|
// } |
|
|
|
// } |
|
|
|
|
|
|
|
private void resolveStartEndBorders(List gridUnits) { |
|
|
|
for (int pos = 1; pos <= gridUnits.size(); pos++) { |
|
|
|
GridUnit starting = (GridUnit)gridUnits.get(pos - 1); |
|
|
|
|
|
|
|
//Border resolution |
|
|
|
if (table.isSeparateBorderModel()) { |
|
|
|
starting.assignBorderForSeparateBorderModel(); |
|
|
|
} else { |
|
|
|
//Neighbouring grid unit at start edge |
|
|
|
GridUnit start = null; |
|
|
|
int find = pos - 1; |
|
|
|
while (find >= 1) { |
|
|
|
GridUnit candidate = (GridUnit)gridUnits.get(find - 1); |
|
|
|
if (candidate.isLastGridUnitColSpan()) { |
|
|
|
start = candidate; |
|
|
|
break; |
|
|
|
} |
|
|
|
find--; |
|
|
|
} |
|
|
|
|
|
|
|
//Ending grid unit for current cell |
|
|
|
GridUnit ending = null; |
|
|
|
if (starting.getCell() != null) { |
|
|
|
pos += starting.getCell().getNumberColumnsSpanned() - 1; |
|
|
|
} |
|
|
|
ending = (GridUnit)gridUnits.get(pos - 1); |
|
|
|
|
|
|
|
//Neighbouring grid unit at end edge |
|
|
|
GridUnit end = null; |
|
|
|
find = pos + 1; |
|
|
|
while (find <= gridUnits.size()) { |
|
|
|
GridUnit candidate = (GridUnit)gridUnits.get(find - 1); |
|
|
|
if (candidate.isPrimary()) { |
|
|
|
end = candidate; |
|
|
|
break; |
|
|
|
} |
|
|
|
find++; |
|
|
|
} |
|
|
|
starting.resolveBorder(start, |
|
|
|
CommonBorderPaddingBackground.START); |
|
|
|
ending.resolveBorder(end, |
|
|
|
CommonBorderPaddingBackground.END); |
|
|
|
//Only start and end borders here, before and after during layout |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |