/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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: Table.java 1610839 2014-07-15 20:25:58Z vhennebert $ */

package org.apache.fop.fo.flow.table;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import org.xml.sax.Locator;

import org.apache.fop.apps.FOPException;
import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
import org.apache.fop.datatypes.Length;
import org.apache.fop.datatypes.ValidationPercentBaseContext;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.StaticPropertyList;
import org.apache.fop.fo.ValidationException;
import org.apache.fop.fo.properties.BreakPropertySet;
import org.apache.fop.fo.properties.CommonAccessibility;
import org.apache.fop.fo.properties.CommonAccessibilityHolder;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.CommonMarginBlock;
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.fo.properties.LengthPairProperty;
import org.apache.fop.fo.properties.LengthRangeProperty;
import org.apache.fop.fo.properties.TableColLength;
import org.apache.fop.traits.Direction;
import org.apache.fop.traits.WritingMode;
import org.apache.fop.traits.WritingModeTraits;
import org.apache.fop.traits.WritingModeTraitsGetter;

Class modelling the fo:table object.
/** * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_table"> * <code>fo:table</code></a> object. */
public class Table extends TableFObj implements ColumnNumberManagerHolder, BreakPropertySet, WritingModeTraitsGetter, CommonAccessibilityHolder { // The value of FO traits (refined properties) that apply to fo:table. private CommonAccessibility commonAccessibility; private CommonBorderPaddingBackground commonBorderPaddingBackground; private CommonMarginBlock commonMarginBlock; private LengthRangeProperty blockProgressionDimension; private int borderCollapse; private LengthPairProperty borderSeparation; private int breakAfter; private int breakBefore; private LengthRangeProperty inlineProgressionDimension; private KeepProperty keepTogether; private KeepProperty keepWithNext; private KeepProperty keepWithPrevious; private int tableLayout; private int tableOmitFooterAtBreak; private int tableOmitHeaderAtBreak; private WritingModeTraits writingModeTraits; // Unused but valid items, commented out for performance: // private CommonAural commonAural; // private CommonRelativePosition commonRelativePosition; // private int intrusionDisplace; // End of FO trait values
extension properties
/** extension properties */
private Length widowContentLimit; private Length orphanContentLimit;
collection of columns in this table
/** collection of columns in this table */
private List columns = new ArrayList(); private ColumnNumberManager columnNumberManager = new ColumnNumberManager();
the table-header and -footer
/** the table-header and -footer */
private TableHeader tableHeader; private TableFooter tableFooter;
used for validation
/** used for validation */
private boolean tableColumnFound; private boolean tableHeaderFound; private boolean tableFooterFound; private boolean tableBodyFound; private boolean hasExplicitColumns; private boolean columnsFinalized; private RowGroupBuilder rowGroupBuilder;
The table's property list. Used in case the table has no explicit columns, as a parent property list to internally generated TableColumns
/** * The table's property list. Used in case the table has * no explicit columns, as a parent property list to * internally generated TableColumns */
private PropertyList propList;
Construct a Table instance with the given FONode as parent.
Params:
  • parent – FONode that is the parent of this object
/** * Construct a Table instance with the given {@link FONode} * as parent. * * @param parent {@link FONode} that is the parent of this object */
public Table(FONode parent) { super(parent); }
{@inheritDoc}
/** * {@inheritDoc} */
public void bind(PropertyList pList) throws FOPException { super.bind(pList); commonAccessibility = CommonAccessibility.getInstance(pList); commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps(); commonMarginBlock = pList.getMarginBlockProps(); blockProgressionDimension = pList.get(PR_BLOCK_PROGRESSION_DIMENSION).getLengthRange(); borderCollapse = pList.get(PR_BORDER_COLLAPSE).getEnum(); borderSeparation = pList.get(PR_BORDER_SEPARATION).getLengthPair(); breakAfter = pList.get(PR_BREAK_AFTER).getEnum(); breakBefore = pList.get(PR_BREAK_BEFORE).getEnum(); inlineProgressionDimension = pList.get(PR_INLINE_PROGRESSION_DIMENSION).getLengthRange(); keepTogether = pList.get(PR_KEEP_TOGETHER).getKeep(); keepWithNext = pList.get(PR_KEEP_WITH_NEXT).getKeep(); keepWithPrevious = pList.get(PR_KEEP_WITH_PREVIOUS).getKeep(); tableLayout = pList.get(PR_TABLE_LAYOUT).getEnum(); tableOmitFooterAtBreak = pList.get(PR_TABLE_OMIT_FOOTER_AT_BREAK).getEnum(); tableOmitHeaderAtBreak = pList.get(PR_TABLE_OMIT_HEADER_AT_BREAK).getEnum(); writingModeTraits = new WritingModeTraits( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()), pList.getExplicit(PR_WRITING_MODE) != null); //Bind extension properties widowContentLimit = pList.get(PR_X_WIDOW_CONTENT_LIMIT).getLength(); orphanContentLimit = pList.get(PR_X_ORPHAN_CONTENT_LIMIT).getLength(); if (!blockProgressionDimension.getOptimum(null).isAuto()) { TableEventProducer eventProducer = TableEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.nonAutoBPDOnTable(this, getLocator()); // Anyway, the bpd of a table is not used by the layout code } if (tableLayout == EN_AUTO) { getFOValidationEventProducer().unimplementedFeature(this, getName(), "table-layout=\"auto\"", getLocator()); } if (!isSeparateBorderModel()) { if (borderCollapse == EN_COLLAPSE_WITH_PRECEDENCE) { getFOValidationEventProducer().unimplementedFeature(this, getName(), "border-collapse=\"collapse-with-precedence\"; defaulting to \"collapse\"", getLocator()); borderCollapse = EN_COLLAPSE; } if (getCommonBorderPaddingBackground().hasPadding( ValidationPercentBaseContext.getPseudoContext())) { //See "17.6.2 The collapsing border model" in CSS2 TableEventProducer eventProducer = TableEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.noTablePaddingWithCollapsingBorderModel(this, getLocator()); } } /* Store reference to the property list, so * new lists can be created in case the table has no * explicit columns * (see addDefaultColumn()) */ this.propList = pList; }
{@inheritDoc}
/** {@inheritDoc} */
public void startOfNode() throws FOPException { super.startOfNode(); getFOEventHandler().startTable(this); }
{@inheritDoc}
XSL Content Model: (marker*,table-column*,table-header?,table-footer?,table-body+)
/** * {@inheritDoc} * <br>XSL Content Model: (marker*,table-column*,table-header?,table-footer?,table-body+) */
protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { if (FO_URI.equals(nsURI)) { if ("marker".equals(localName)) { if (tableColumnFound || tableHeaderFound || tableFooterFound || tableBodyFound) { nodesOutOfOrderError(loc, "fo:marker", "(table-column*,table-header?,table-footer?,table-body+)"); } } else if ("table-column".equals(localName)) { tableColumnFound = true; if (tableHeaderFound || tableFooterFound || tableBodyFound) { nodesOutOfOrderError(loc, "fo:table-column", "(table-header?,table-footer?,table-body+)"); } } else if ("table-header".equals(localName)) { if (tableHeaderFound) { tooManyNodesError(loc, "table-header"); } else { tableHeaderFound = true; if (tableFooterFound || tableBodyFound) { nodesOutOfOrderError(loc, "fo:table-header", "(table-footer?,table-body+)"); } } } else if ("table-footer".equals(localName)) { if (tableFooterFound) { tooManyNodesError(loc, "table-footer"); } else { tableFooterFound = true; if (tableBodyFound) { if (getUserAgent().validateStrictly()) { nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)", true); } if (!isSeparateBorderModel()) { TableEventProducer eventProducer = TableEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.footerOrderCannotRecover(this, getName(), getLocator()); } } } } else if ("table-body".equals(localName)) { tableBodyFound = true; } else { invalidChildError(loc, nsURI, localName); } } }
{@inheritDoc}
/** {@inheritDoc} */
public void endOfNode() throws FOPException { super.endOfNode(); getFOEventHandler().endTable(this); }
{@inheritDoc}
/** {@inheritDoc} */
public void finalizeNode() throws FOPException { if (!tableBodyFound) { missingChildElementError( "(marker*,table-column*,table-header?,table-footer?" + ",table-body+)"); } if (!hasChildren()) { getParent().removeChild(this); return; } if (!inMarker()) { rowGroupBuilder.endTable(); /* clean up */ for (int i = columns.size(); --i >= 0;) { TableColumn col = (TableColumn) columns.get(i); if (col != null) { col.releasePropertyList(); } } this.propList = null; rowGroupBuilder = null; } }
{@inheritDoc}
/** {@inheritDoc} */
protected void addChildNode(FONode child) throws FOPException { int childId = child.getNameId(); switch (childId) { case FO_TABLE_COLUMN: hasExplicitColumns = true; if (!inMarker()) { addColumnNode((TableColumn) child); } else { columns.add(child); } break; case FO_TABLE_HEADER: case FO_TABLE_FOOTER: case FO_TABLE_BODY: if (!inMarker() && !columnsFinalized) { columnsFinalized = true; if (hasExplicitColumns) { finalizeColumns(); rowGroupBuilder = new FixedColRowGroupBuilder(this); } else { rowGroupBuilder = new VariableColRowGroupBuilder(this); } } switch (childId) { case FO_TABLE_FOOTER: tableFooter = (TableFooter) child; break; case FO_TABLE_HEADER: tableHeader = (TableHeader) child; break; default: super.addChildNode(child); } break; default: super.addChildNode(child); } } private void finalizeColumns() throws FOPException { for (int i = 0; i < columns.size(); i++) { if (columns.get(i) == null) { columns.set(i, createImplicitColumn(i + 1)); } } }
{@inheritDoc}
/** {@inheritDoc} */
public CommonAccessibility getCommonAccessibility() { return commonAccessibility; }
{@inheritDoc}
/** {@inheritDoc} */
public Table getTable() { return this; }
Creates the appropriate number of additional implicit columns to match the given column number. Used when the table has no explicit column: the number of columns is then determined by the row that has the most columns.
Params:
  • columnNumber – the table must at least have this number of column
Throws:
  • FOPException – if there was an error creating the property list for implicit columns
/** * Creates the appropriate number of additional implicit columns to match the given * column number. Used when the table has no explicit column: the number of columns is * then determined by the row that has the most columns. * * @param columnNumber the table must at least have this number of column * @throws FOPException if there was an error creating the property list for implicit * columns */
void ensureColumnNumber(int columnNumber) throws FOPException { assert !hasExplicitColumns; for (int i = columns.size() + 1; i <= columnNumber; i++) { columns.add(createImplicitColumn(i)); } } private TableColumn createImplicitColumn(int colNumber) throws FOPException { TableColumn implicitColumn = new TableColumn(this, true); PropertyList pList = new StaticPropertyList( implicitColumn, this.propList); implicitColumn.bind(pList); implicitColumn.setColumnWidth(new TableColLength(1.0, implicitColumn)); implicitColumn.setColumnNumber(colNumber); if (!isSeparateBorderModel()) { implicitColumn.setCollapsedBorders(collapsingBorderModel); // TODO } return implicitColumn; }
Adds a column to the columns List, and updates the columnIndex used for determining initial values for column-number
Params:
  • col – the column to add
/** * Adds a column to the columns List, and updates the columnIndex * used for determining initial values for column-number * * @param col the column to add */
private void addColumnNode(TableColumn col) { int colNumber = col.getColumnNumber(); int colRepeat = col.getNumberColumnsRepeated(); /* add nulls for non-occupied indices between * the last column up to and including the current one */ while (columns.size() < colNumber + colRepeat - 1) { columns.add(null); } // in case column is repeated: // for the time being, add the same column // (colRepeat - 1) times to the columns list // TODO: need to force the column-number (?) for (int i = colNumber - 1; i < colNumber + colRepeat - 1; i++) { columns.set(i, col); } columnNumberManager.signalUsedColumnNumbers(colNumber, colNumber + colRepeat - 1); } boolean hasExplicitColumns() { return hasExplicitColumns; }
Returns:true of table-layout="auto"
/** @return true of table-layout="auto" */
public boolean isAutoLayout() { return (tableLayout == EN_AUTO); }
Returns the list of table-column elements.
Returns:a list of TableColumn elements, may contain null elements
/** * Returns the list of table-column elements. * * @return a list of {@link TableColumn} elements, may contain null elements */
public List getColumns() { return columns; }
Returns the column at the given index.
Params:
  • index – index of the column to be retrieved, 0-based
Returns:the corresponding column (may be an implicitly created column)
/** * Returns the column at the given index. * * @param index index of the column to be retrieved, 0-based * @return the corresponding column (may be an implicitly created column) */
public TableColumn getColumn(int index) { return (TableColumn) columns.get(index); }
Returns the number of columns of this table.
Returns:the number of columns, implicit or explicit, in this table
/** * Returns the number of columns of this table. * * @return the number of columns, implicit or explicit, in this table */
public int getNumberOfColumns() { return columns.size(); }
Returns:the body for the table-header.
/** @return the body for the table-header. */
public TableHeader getTableHeader() { return tableHeader; }
Returns:the body for the table-footer.
/** @return the body for the table-footer. */
public TableFooter getTableFooter() { return tableFooter; }
Returns:true if the table-header should be omitted at breaks
/** @return true if the table-header should be omitted at breaks */
public boolean omitHeaderAtBreak() { return (this.tableOmitHeaderAtBreak == EN_TRUE); }
Returns:true if the table-footer should be omitted at breaks
/** @return true if the table-footer should be omitted at breaks */
public boolean omitFooterAtBreak() { return (this.tableOmitFooterAtBreak == EN_TRUE); }
Returns:the "inline-progression-dimension" FO trait.
/** * @return the "inline-progression-dimension" FO trait. */
public LengthRangeProperty getInlineProgressionDimension() { return inlineProgressionDimension; }
Returns:the "block-progression-dimension" FO trait.
/** * @return the "block-progression-dimension" FO trait. */
public LengthRangeProperty getBlockProgressionDimension() { return blockProgressionDimension; }
Returns:the Common Margin Properties-Block.
/** * @return the Common Margin Properties-Block. */
public CommonMarginBlock getCommonMarginBlock() { return commonMarginBlock; }
Returns:the Common Border, Padding, and Background Properties.
/** * @return the Common Border, Padding, and Background Properties. */
public CommonBorderPaddingBackground getCommonBorderPaddingBackground() { return commonBorderPaddingBackground; }
Returns:the "break-after" FO trait.
/** @return the "break-after" FO trait. */
public int getBreakAfter() { return breakAfter; }
Returns:the "break-before" FO trait.
/** @return the "break-before" FO trait. */
public int getBreakBefore() { return breakBefore; }
Returns:the "keep-with-next" FO trait.
/** @return the "keep-with-next" FO trait. */
public KeepProperty getKeepWithNext() { return keepWithNext; }
Returns:the "keep-with-previous" FO trait.
/** @return the "keep-with-previous" FO trait. */
public KeepProperty getKeepWithPrevious() { return keepWithPrevious; }
Returns:the "keep-together" FO trait.
/** @return the "keep-together" FO trait. */
public KeepProperty getKeepTogether() { return keepTogether; }
Convenience method to check if a keep-together constraint is specified.
Returns:true if keep-together is active.
/** * Convenience method to check if a keep-together constraint is specified. * @return true if keep-together is active. */
public boolean mustKeepTogether() { return !getKeepTogether().getWithinPage().isAuto() || !getKeepTogether().getWithinColumn().isAuto(); }
Returns:the "border-collapse" FO trait.
/** @return the "border-collapse" FO trait. */
public int getBorderCollapse() { return borderCollapse; }
Returns:true if the separate border model is active
/** @return true if the separate border model is active */
public boolean isSeparateBorderModel() { return (getBorderCollapse() == EN_SEPARATE); }
Returns:the "border-separation" FO trait.
/** @return the "border-separation" FO trait. */
public LengthPairProperty getBorderSeparation() { return borderSeparation; }
{@inheritDoc}
/** {@inheritDoc} */
public Direction getInlineProgressionDirection() { return writingModeTraits.getInlineProgressionDirection(); }
{@inheritDoc}
/** {@inheritDoc} */
public Direction getBlockProgressionDirection() { return writingModeTraits.getBlockProgressionDirection(); }
{@inheritDoc}
/** {@inheritDoc} */
public Direction getColumnProgressionDirection() { return writingModeTraits.getColumnProgressionDirection(); }
{@inheritDoc}
/** {@inheritDoc} */
public Direction getRowProgressionDirection() { return writingModeTraits.getRowProgressionDirection(); }
{@inheritDoc}
/** {@inheritDoc} */
public Direction getShiftDirection() { return writingModeTraits.getShiftDirection(); }
{@inheritDoc}
/** {@inheritDoc} */
public WritingMode getWritingMode() { return writingModeTraits.getWritingMode(); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean getExplicitWritingMode() { return writingModeTraits.getExplicitWritingMode(); }
Returns:the "fox:widow-content-limit" extension FO trait
/** @return the "fox:widow-content-limit" extension FO trait */
public Length getWidowContentLimit() { return widowContentLimit; }
Returns:the "fox:orphan-content-limit" extension FO trait
/** @return the "fox:orphan-content-limit" extension FO trait */
public Length getOrphanContentLimit() { return orphanContentLimit; }
{@inheritDoc}
/** {@inheritDoc} */
public String getLocalName() { return "table"; }
{@inheritDoc}
Returns:Constants.FO_TABLE
/** * {@inheritDoc} * @return {@link org.apache.fop.fo.Constants#FO_TABLE} */
public int getNameId() { return FO_TABLE; }
{@inheritDoc}
/** {@inheritDoc} */
public FONode clone(FONode parent, boolean removeChildren) throws FOPException { Table clone = (Table) super.clone(parent, removeChildren); if (removeChildren) { clone.columns = new ArrayList(); clone.columnsFinalized = false; clone.columnNumberManager = new ColumnNumberManager(); clone.tableHeader = null; clone.tableFooter = null; clone.rowGroupBuilder = null; } return clone; }
{@inheritDoc}
/** {@inheritDoc} */
public ColumnNumberManager getColumnNumberManager() { return columnNumberManager; } RowGroupBuilder getRowGroupBuilder() { return rowGroupBuilder; } @Override protected Stack<DelimitedTextRange> collectDelimitedTextRanges(Stack<DelimitedTextRange> ranges, DelimitedTextRange currentRange) { // header sub-tree TableHeader header = getTableHeader(); if (header != null) { ranges = header.collectDelimitedTextRanges(ranges); } // footer sub-tree TableFooter footer = getTableFooter(); if (footer != null) { ranges = footer.collectDelimitedTextRanges(ranges); } // body sub-tree for (Iterator it = getChildNodes(); (it != null) && it.hasNext();) { ranges = ((FONode) it.next()).collectDelimitedTextRanges(ranges); } return ranges; } @Override protected boolean isBidiBoundary(boolean propagate) { return getExplicitWritingMode(); } }