/* * 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$ */ 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 <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 */ private Length widowContentLimit; private Length orphanContentLimit; /** collection of columns in this table */ private List columns = new ArrayList(); private ColumnNumberManager columnNumberManager = new ColumnNumberManager(); /** the table-header and -footer */ private TableHeader tableHeader; private TableFooter tableFooter; /** 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 */ private PropertyList propList; /** * 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} */ 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} */ public void startOfNode() throws FOPException { super.startOfNode(); getFOEventHandler().startTable(this); } /** * {@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} */ public void endOfNode() throws FOPException { super.endOfNode(); getFOEventHandler().endTable(this); } /** {@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} */ 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} */ public CommonAccessibility getCommonAccessibility() { return commonAccessibility; } /** {@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. * * @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 * * @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; } /** @return true of table-layout="auto" */ public boolean isAutoLayout() { return (tableLayout == EN_AUTO); } /** * 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. * * @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. * * @return the number of columns, implicit or explicit, in this table */ public int getNumberOfColumns() { return columns.size(); } /** @return the body for the table-header. */ public TableHeader getTableHeader() { return tableHeader; } /** @return the body for the table-footer. */ public TableFooter getTableFooter() { return tableFooter; } /** @return true if the table-header should be omitted at breaks */ public boolean omitHeaderAtBreak() { return (this.tableOmitHeaderAtBreak == EN_TRUE); } /** @return true if the table-footer should be omitted at breaks */ public boolean omitFooterAtBreak() { return (this.tableOmitFooterAtBreak == EN_TRUE); } /** * @return the "inline-progression-dimension" FO trait. */ public LengthRangeProperty getInlineProgressionDimension() { return inlineProgressionDimension; } /** * @return the "block-progression-dimension" FO trait. */ public LengthRangeProperty getBlockProgressionDimension() { return blockProgressionDimension; } /** * @return the Common Margin Properties-Block. */ public CommonMarginBlock getCommonMarginBlock() { return commonMarginBlock; } /** * @return the Common Border, Padding, and Background Properties. */ public CommonBorderPaddingBackground getCommonBorderPaddingBackground() { return commonBorderPaddingBackground; } /** @return the "break-after" FO trait. */ public int getBreakAfter() { return breakAfter; } /** @return the "break-before" FO trait. */ public int getBreakBefore() { return breakBefore; } /** @return the "keep-with-next" FO trait. */ public KeepProperty getKeepWithNext() { return keepWithNext; } /** @return the "keep-with-previous" FO trait. */ public KeepProperty getKeepWithPrevious() { return keepWithPrevious; } /** @return the "keep-together" FO trait. */ public KeepProperty getKeepTogether() { return keepTogether; } /** * 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(); } /** @return the "border-collapse" FO trait. */ public int getBorderCollapse() { return borderCollapse; } /** @return true if the separate border model is active */ public boolean isSeparateBorderModel() { return (getBorderCollapse() == EN_SEPARATE); } /** @return the "border-separation" FO trait. */ public LengthPairProperty getBorderSeparation() { return borderSeparation; } /** {@inheritDoc} */ public Direction getInlineProgressionDirection() { return writingModeTraits.getInlineProgressionDirection(); } /** {@inheritDoc} */ public Direction getBlockProgressionDirection() { return writingModeTraits.getBlockProgressionDirection(); } /** {@inheritDoc} */ public Direction getColumnProgressionDirection() { return writingModeTraits.getColumnProgressionDirection(); } /** {@inheritDoc} */ public Direction getRowProgressionDirection() { return writingModeTraits.getRowProgressionDirection(); } /** {@inheritDoc} */ public Direction getShiftDirection() { return writingModeTraits.getShiftDirection(); } /** {@inheritDoc} */ public WritingMode getWritingMode() { return writingModeTraits.getWritingMode(); } /** {@inheritDoc} */ public boolean getExplicitWritingMode() { return writingModeTraits.getExplicitWritingMode(); } /** @return the "fox:widow-content-limit" extension FO trait */ public Length getWidowContentLimit() { return widowContentLimit; } /** @return the "fox:orphan-content-limit" extension FO trait */ public Length getOrphanContentLimit() { return orphanContentLimit; } /** {@inheritDoc} */ public String getLocalName() { return "table"; } /** * {@inheritDoc} * @return {@link org.apache.fop.fo.Constants#FO_TABLE} */ public int getNameId() { return FO_TABLE; } /** {@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} */ 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(); } }