/* * Copyright 1999-2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id$ */ package org.apache.fop.fo.flow; // Java import java.util.BitSet; import java.util.Iterator; import java.util.List; import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.StaticPropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.properties.CommonAccessibility; import org.apache.fop.fo.properties.CommonAural; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.CommonRelativePosition; /** * Class modelling the fo:table-body object. */ public class TableBody extends TableFObj { // The value of properties relevant for fo:table-body. private CommonAccessibility commonAccessibility; private CommonAural commonAural; private CommonBorderPaddingBackground commonBorderPaddingBackground; private CommonRelativePosition commonRelativePosition; private int visibility; // End of property values private PropertyList savedPropertyList; /** * used for validation */ protected boolean tableRowsFound = false; protected boolean tableCellsFound = false; /** * used for initial values of column-number property */ protected List pendingSpans; protected BitSet usedColumnIndices = new BitSet(); private int columnIndex = 1; private boolean firstRow = true; /** * @param parent FONode that is the parent of the object */ public TableBody(FONode parent) { super(parent); } /** * @see FObj#bind(PropertyList) */ public void bind(PropertyList pList) throws FOPException { commonAccessibility = pList.getAccessibilityProps(); commonAural = pList.getAuralProps(); commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps(); commonRelativePosition = pList.getRelativePositionProps(); visibility = pList.get(PR_VISIBILITY).getEnum(); super.bind(pList); //Used by convertCellsToRows() savedPropertyList = pList; } /** * @see org.apache.fop.fo.FONode#startOfNode */ protected void startOfNode() throws FOPException { initPendingSpans(); getFOEventHandler().startBody(this); } /** * @see org.apache.fop.fo.FONode#endOfNode */ protected void endOfNode() throws FOPException { getFOEventHandler().endBody(this); if (!(tableRowsFound || tableCellsFound)) { if (getUserAgent().validateStrictly()) { missingChildElementError("marker* (table-row+|table-cell+)"); } else { getLogger().error("fo:table-body must not be empty. " + "Expected: marker* (table-row+|table-cell+)"); getParent().removeChild(this); } } /* if (tableCellsFound) { convertCellsToRows(); }*/ //release references savedPropertyList = null; pendingSpans = null; usedColumnIndices = null; } /** * @see org.apache.fop.fo.FONode#validateChildNode(Locator, String, String) * XSL Content Model: marker* (table-row+|table-cell+) */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { if (FO_URI.equals(nsURI)) { if (localName.equals("marker")) { if (tableRowsFound || tableCellsFound) { nodesOutOfOrderError(loc, "fo:marker", "(table-row+|table-cell+)"); } } else if (localName.equals("table-row")) { tableRowsFound = true; if (tableCellsFound) { invalidChildError(loc, nsURI, localName, "Either fo:table-rows" + " or fo:table-cells may be children of an " + getName() + " but not both"); } } else if (localName.equals("table-cell")) { tableCellsFound = true; if (tableRowsFound) { invalidChildError(loc, nsURI, localName, "Either fo:table-rows" + " or fo:table-cells may be children of an " + getName() + " but not both"); } } else { invalidChildError(loc, nsURI, localName); } } else { invalidChildError(loc, nsURI, localName); } } /** * @see org.apache.fop.fo.FONode#addChildNode(FONode) */ protected void addChildNode(FONode child) throws FOPException { if (child.getNameId() == FO_TABLE_CELL) { addCellNode( (TableCell) child); } else { super.addChildNode(child); } } /** * Adds a cell to the list of child nodes, and updates the columnIndex * used for determining the initial value of column-number * * @param cell cell to add * @throws FOPException */ private void addCellNode(TableCell cell) throws FOPException { //if firstRow flag is still true, the cell starts a row, //and there was a previous cell that didn't explicitly //end the previous row => set firstRow flag to false if (firstRow && cell.startsRow() && !lastCellEndedRow()) { firstRow = false; } int rowSpan = cell.getNumberRowsSpanned(); int colSpan = cell.getNumberColumnsSpanned(); //if there were no explicit columns, pendingSpans //will not be properly initialized for the first row if (firstRow && getTable().columns == null) { if (pendingSpans == null) { pendingSpans = new java.util.ArrayList(); } for (int i = colSpan; --i >= 0;) { pendingSpans.add(null); } } //if the current cell spans more than one row, //update pending span list for the next row if (rowSpan > 1) { for (int i = colSpan; --i >= 0;) { pendingSpans.set(columnIndex - 1 + i, new PendingSpan(rowSpan)); } } //flag column indices used by this cell, //take into account that possibly not all column-numbers //are used by columns in the parent table (if any), //so a cell spanning three columns, might actually //take up more than three columnIndices... int startIndex = columnIndex - 1; int endIndex = startIndex + colSpan; if (getTable().columns != null) { List cols = getTable().columns; int tmpIndex = endIndex; for (int i = startIndex; i <= tmpIndex; ++i) { if (i < cols.size() && cols.get(i) == null) { endIndex++; } } } for (int i = startIndex; i < endIndex; i++) { usedColumnIndices.set(i); } setNextColumnIndex(); super.addChildNode(cell); if (cell.endsRow()) { if (firstRow) { firstRow = false; } resetColumnIndex(); } } /** * If table-cells are used as direct children of a table-body|header|footer * they are replaced in this method by proper table-rows. * @throws FOPException if there's a problem binding the TableRows properties. */ private void convertCellsToRows() throws FOPException { //getLogger().debug("Converting cells to rows..."); List cells = new java.util.ArrayList(childNodes); childNodes.clear(); Iterator i = cells.iterator(); TableRow row = null; while (i.hasNext()) { TableCell cell = (TableCell)i.next(); if (cell.startsRow() && (row != null)) { childNodes.add(row); row = null; } if (row == null) { row = new TableRow(this); PropertyList pList = new StaticPropertyList(row, savedPropertyList); pList.setWritingMode(); row.bind(pList); } row.addReplacedCell(cell); if (cell.endsRow()) { childNodes.add(row); row = null; } } if (row != null) { childNodes.add(row); } } /** * @return the Common Border, Padding, and Background Properties. */ public CommonBorderPaddingBackground getCommonBorderPaddingBackground() { return commonBorderPaddingBackground; } /** * @see org.apache.fop.fo.FObj#getName() */ public String getName() { return "fo:table-body"; } /** * @see org.apache.fop.fo.FObj#getNameId() */ public int getNameId() { return FO_TABLE_BODY; } /** * @param obj table row in question * @return true if the given table row is the first row of this body. */ public boolean isFirst(TableRow obj) { return (childNodes.size() > 0) && (childNodes.get(0) == obj); } /** * @param obj table row in question * @return true if the given table row is the first row of this body. */ public boolean isLast(TableRow obj) { return (childNodes.size() > 0) && (childNodes.get(childNodes.size() - 1) == obj); } /** * Initializes pending spans list; used for correctly * assigning initial value for column-number for the * cells of following rows * */ protected void initPendingSpans() { if (getTable().columns != null) { List tableCols = getTable().columns; pendingSpans = new java.util.ArrayList(tableCols.size()); for (int i = tableCols.size(); --i >= 0;) { pendingSpans.add(null); } } } /** * Returns the current column index of the TableBody * * @return the next column number to use */ public int getCurrentColumnIndex() { return columnIndex; } /** * Sets the current column index to a specific value * (used by TableCell.bind() in case the column-number * was explicitly specified) * * @param newIndex the new column index */ protected void setCurrentColumnIndex(int newIndex) { columnIndex = newIndex; } /** * Resets the current column index for the TableBody * */ public void resetColumnIndex() { columnIndex = 1; for (int i = 0; i < usedColumnIndices.size(); i++) { usedColumnIndices.clear(i); } PendingSpan pSpan; for (int i = pendingSpans.size(); --i >= 0;) { pSpan = (PendingSpan) pendingSpans.get(i); if (pSpan != null) { pSpan.rowsLeft--; if (pSpan.rowsLeft == 0) { pendingSpans.set(i, null); } } if (pendingSpans.get(i) != null) { usedColumnIndices.set(i); } else { usedColumnIndices.clear(i); } } if (!firstRow) { setNextColumnIndex(); } } /** * Increases columnIndex to the next available value * */ private void setNextColumnIndex() { while (usedColumnIndices.get(columnIndex - 1)) { //increment columnIndex columnIndex++; //if the table has explicit columns, and //the updated index is not assigned to any //column, increment further until the next //index occupied by a column... if (getTable().columns != null) { while (columnIndex <= getTable().columns.size() && !getTable().isColumnNumberUsed(columnIndex) ) { columnIndex++; } } } } /** * Checks whether the previous cell had 'ends-row="true"' * * @return false only if there was a previous cell, which * had ends-row="false" (implicit or explicit) */ public boolean lastCellEndedRow() { if (childNodes != null) { FONode prevNode = (FONode) childNodes.get(childNodes.size() - 1); if (prevNode.getNameId() == FO_TABLE_CELL) { return ((TableCell) prevNode).endsRow(); } } return true; } /** * Checks whether a given column-number is already in use * for the current row (used by TableCell.bind()); * * @param colNr the column-number to check * @return true if column-number is already occupied */ protected boolean isColumnNumberUsed(int colNr) { return usedColumnIndices.get(colNr - 1); } }