You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Table.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fo.flow.table;
  19. import java.util.ArrayList;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.util.Stack;
  23. import org.xml.sax.Locator;
  24. import org.apache.fop.apps.FOPException;
  25. import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
  26. import org.apache.fop.datatypes.Length;
  27. import org.apache.fop.datatypes.ValidationPercentBaseContext;
  28. import org.apache.fop.fo.FONode;
  29. import org.apache.fop.fo.PropertyList;
  30. import org.apache.fop.fo.StaticPropertyList;
  31. import org.apache.fop.fo.ValidationException;
  32. import org.apache.fop.fo.properties.BreakPropertySet;
  33. import org.apache.fop.fo.properties.CommonAccessibility;
  34. import org.apache.fop.fo.properties.CommonAccessibilityHolder;
  35. import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
  36. import org.apache.fop.fo.properties.CommonMarginBlock;
  37. import org.apache.fop.fo.properties.KeepProperty;
  38. import org.apache.fop.fo.properties.LengthPairProperty;
  39. import org.apache.fop.fo.properties.LengthRangeProperty;
  40. import org.apache.fop.fo.properties.TableColLength;
  41. import org.apache.fop.traits.Direction;
  42. import org.apache.fop.traits.WritingMode;
  43. import org.apache.fop.traits.WritingModeTraits;
  44. import org.apache.fop.traits.WritingModeTraitsGetter;
  45. /**
  46. * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_table">
  47. * <code>fo:table</code></a> object.
  48. */
  49. public class Table extends TableFObj implements ColumnNumberManagerHolder, BreakPropertySet, WritingModeTraitsGetter,
  50. CommonAccessibilityHolder {
  51. // The value of FO traits (refined properties) that apply to fo:table.
  52. private CommonAccessibility commonAccessibility;
  53. private CommonBorderPaddingBackground commonBorderPaddingBackground;
  54. private CommonMarginBlock commonMarginBlock;
  55. private LengthRangeProperty blockProgressionDimension;
  56. private int borderCollapse;
  57. private LengthPairProperty borderSeparation;
  58. private int breakAfter;
  59. private int breakBefore;
  60. private LengthRangeProperty inlineProgressionDimension;
  61. private KeepProperty keepTogether;
  62. private KeepProperty keepWithNext;
  63. private KeepProperty keepWithPrevious;
  64. private int tableLayout;
  65. private int tableOmitFooterAtBreak;
  66. private int tableOmitHeaderAtBreak;
  67. private WritingModeTraits writingModeTraits;
  68. // Unused but valid items, commented out for performance:
  69. // private CommonAural commonAural;
  70. // private CommonRelativePosition commonRelativePosition;
  71. // private int intrusionDisplace;
  72. // End of FO trait values
  73. /** extension properties */
  74. private Length widowContentLimit;
  75. private Length orphanContentLimit;
  76. /** collection of columns in this table */
  77. private List columns = new ArrayList();
  78. private ColumnNumberManager columnNumberManager = new ColumnNumberManager();
  79. /** the table-header and -footer */
  80. private TableHeader tableHeader = null;
  81. private TableFooter tableFooter = null;
  82. /** used for validation */
  83. private boolean tableColumnFound = false;
  84. private boolean tableHeaderFound = false;
  85. private boolean tableFooterFound = false;
  86. private boolean tableBodyFound = false;
  87. private boolean hasExplicitColumns = false;
  88. private boolean columnsFinalized = false;
  89. private RowGroupBuilder rowGroupBuilder;
  90. /**
  91. * The table's property list. Used in case the table has
  92. * no explicit columns, as a parent property list to
  93. * internally generated TableColumns
  94. */
  95. private PropertyList propList;
  96. /**
  97. * Construct a Table instance with the given {@link FONode}
  98. * as parent.
  99. *
  100. * @param parent {@link FONode} that is the parent of this object
  101. */
  102. public Table(FONode parent) {
  103. super(parent);
  104. }
  105. /**
  106. * {@inheritDoc}
  107. */
  108. public void bind(PropertyList pList) throws FOPException {
  109. super.bind(pList);
  110. commonAccessibility = CommonAccessibility.getInstance(pList);
  111. commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps();
  112. commonMarginBlock = pList.getMarginBlockProps();
  113. blockProgressionDimension = pList.get(PR_BLOCK_PROGRESSION_DIMENSION).getLengthRange();
  114. borderCollapse = pList.get(PR_BORDER_COLLAPSE).getEnum();
  115. borderSeparation = pList.get(PR_BORDER_SEPARATION).getLengthPair();
  116. breakAfter = pList.get(PR_BREAK_AFTER).getEnum();
  117. breakBefore = pList.get(PR_BREAK_BEFORE).getEnum();
  118. inlineProgressionDimension = pList.get(PR_INLINE_PROGRESSION_DIMENSION).getLengthRange();
  119. keepTogether = pList.get(PR_KEEP_TOGETHER).getKeep();
  120. keepWithNext = pList.get(PR_KEEP_WITH_NEXT).getKeep();
  121. keepWithPrevious = pList.get(PR_KEEP_WITH_PREVIOUS).getKeep();
  122. tableLayout = pList.get(PR_TABLE_LAYOUT).getEnum();
  123. tableOmitFooterAtBreak = pList.get(PR_TABLE_OMIT_FOOTER_AT_BREAK).getEnum();
  124. tableOmitHeaderAtBreak = pList.get(PR_TABLE_OMIT_HEADER_AT_BREAK).getEnum();
  125. writingModeTraits = new WritingModeTraits
  126. ( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()) );
  127. //Bind extension properties
  128. widowContentLimit = pList.get(PR_X_WIDOW_CONTENT_LIMIT).getLength();
  129. orphanContentLimit = pList.get(PR_X_ORPHAN_CONTENT_LIMIT).getLength();
  130. if (!blockProgressionDimension.getOptimum(null).isAuto()) {
  131. TableEventProducer eventProducer = TableEventProducer.Provider.get(
  132. getUserAgent().getEventBroadcaster());
  133. eventProducer.nonAutoBPDOnTable(this, getLocator());
  134. // Anyway, the bpd of a table is not used by the layout code
  135. }
  136. if (tableLayout == EN_AUTO) {
  137. getFOValidationEventProducer().unimplementedFeature(this, getName(),
  138. "table-layout=\"auto\"", getLocator());
  139. }
  140. if (!isSeparateBorderModel()) {
  141. if (borderCollapse == EN_COLLAPSE_WITH_PRECEDENCE) {
  142. getFOValidationEventProducer().unimplementedFeature(this, getName(),
  143. "border-collapse=\"collapse-with-precedence\"; defaulting to \"collapse\"", getLocator());
  144. borderCollapse = EN_COLLAPSE;
  145. }
  146. if (getCommonBorderPaddingBackground().hasPadding(
  147. ValidationPercentBaseContext.getPseudoContext())) {
  148. //See "17.6.2 The collapsing border model" in CSS2
  149. TableEventProducer eventProducer = TableEventProducer.Provider.get(
  150. getUserAgent().getEventBroadcaster());
  151. eventProducer.noTablePaddingWithCollapsingBorderModel(this, getLocator());
  152. }
  153. }
  154. /* Store reference to the property list, so
  155. * new lists can be created in case the table has no
  156. * explicit columns
  157. * (see addDefaultColumn())
  158. */
  159. this.propList = pList;
  160. }
  161. /** {@inheritDoc} */
  162. protected void startOfNode() throws FOPException {
  163. super.startOfNode();
  164. getFOEventHandler().startTable(this);
  165. }
  166. /**
  167. * {@inheritDoc}
  168. * <br>XSL Content Model: (marker*,table-column*,table-header?,table-footer?,table-body+)
  169. */
  170. protected void validateChildNode(Locator loc, String nsURI, String localName)
  171. throws ValidationException {
  172. if (FO_URI.equals(nsURI)) {
  173. if ("marker".equals(localName)) {
  174. if (tableColumnFound || tableHeaderFound || tableFooterFound
  175. || tableBodyFound) {
  176. nodesOutOfOrderError(loc, "fo:marker",
  177. "(table-column*,table-header?,table-footer?,table-body+)");
  178. }
  179. } else if ("table-column".equals(localName)) {
  180. tableColumnFound = true;
  181. if (tableHeaderFound || tableFooterFound || tableBodyFound) {
  182. nodesOutOfOrderError(loc, "fo:table-column",
  183. "(table-header?,table-footer?,table-body+)");
  184. }
  185. } else if ("table-header".equals(localName)) {
  186. if (tableHeaderFound) {
  187. tooManyNodesError(loc, "table-header");
  188. } else {
  189. tableHeaderFound = true;
  190. if (tableFooterFound || tableBodyFound) {
  191. nodesOutOfOrderError(loc, "fo:table-header",
  192. "(table-footer?,table-body+)");
  193. }
  194. }
  195. } else if ("table-footer".equals(localName)) {
  196. if (tableFooterFound) {
  197. tooManyNodesError(loc, "table-footer");
  198. } else {
  199. tableFooterFound = true;
  200. if (tableBodyFound) {
  201. if (getUserAgent().validateStrictly()) {
  202. nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)", true);
  203. }
  204. if (!isSeparateBorderModel()) {
  205. TableEventProducer eventProducer = TableEventProducer.Provider.get(
  206. getUserAgent().getEventBroadcaster());
  207. eventProducer.footerOrderCannotRecover(this, getName(), getLocator());
  208. }
  209. }
  210. }
  211. } else if ("table-body".equals(localName)) {
  212. tableBodyFound = true;
  213. } else {
  214. invalidChildError(loc, nsURI, localName);
  215. }
  216. }
  217. }
  218. /** {@inheritDoc} */
  219. protected void endOfNode() throws FOPException {
  220. super.endOfNode();
  221. getFOEventHandler().endTable(this);
  222. }
  223. /** {@inheritDoc} */
  224. public void finalizeNode() throws FOPException {
  225. if (!tableBodyFound) {
  226. missingChildElementError(
  227. "(marker*,table-column*,table-header?,table-footer?"
  228. + ",table-body+)");
  229. }
  230. if (!hasChildren()) {
  231. getParent().removeChild(this);
  232. return;
  233. }
  234. if (!inMarker()) {
  235. rowGroupBuilder.endTable();
  236. /* clean up */
  237. for (int i = columns.size(); --i >= 0;) {
  238. TableColumn col = (TableColumn) columns.get(i);
  239. if (col != null) {
  240. col.releasePropertyList();
  241. }
  242. }
  243. this.propList = null;
  244. rowGroupBuilder = null;
  245. }
  246. }
  247. /** {@inheritDoc} */
  248. protected void addChildNode(FONode child) throws FOPException {
  249. int childId = child.getNameId();
  250. switch (childId) {
  251. case FO_TABLE_COLUMN:
  252. hasExplicitColumns = true;
  253. if (!inMarker()) {
  254. addColumnNode((TableColumn) child);
  255. } else {
  256. columns.add(child);
  257. }
  258. break;
  259. case FO_TABLE_HEADER:
  260. case FO_TABLE_FOOTER:
  261. case FO_TABLE_BODY:
  262. if (!inMarker() && !columnsFinalized) {
  263. columnsFinalized = true;
  264. if (hasExplicitColumns) {
  265. finalizeColumns();
  266. rowGroupBuilder = new FixedColRowGroupBuilder(this);
  267. } else {
  268. rowGroupBuilder = new VariableColRowGroupBuilder(this);
  269. }
  270. }
  271. switch (childId) {
  272. case FO_TABLE_FOOTER:
  273. tableFooter = (TableFooter) child;
  274. break;
  275. case FO_TABLE_HEADER:
  276. tableHeader = (TableHeader) child;
  277. break;
  278. default:
  279. super.addChildNode(child);
  280. }
  281. break;
  282. default:
  283. super.addChildNode(child);
  284. }
  285. }
  286. private void finalizeColumns() throws FOPException {
  287. for (int i = 0; i < columns.size(); i++) {
  288. if (columns.get(i) == null) {
  289. columns.set(i, createImplicitColumn(i + 1));
  290. }
  291. }
  292. }
  293. /** {@inheritDoc} */
  294. public CommonAccessibility getCommonAccessibility() {
  295. return commonAccessibility;
  296. }
  297. /** {@inheritDoc} */
  298. public Table getTable() {
  299. return this;
  300. }
  301. /**
  302. * Creates the appropriate number of additional implicit columns to match the given
  303. * column number. Used when the table has no explicit column: the number of columns is
  304. * then determined by the row that has the most columns.
  305. *
  306. * @param columnNumber the table must at least have this number of column
  307. * @throws FOPException if there was an error creating the property list for implicit
  308. * columns
  309. */
  310. void ensureColumnNumber(int columnNumber) throws FOPException {
  311. assert !hasExplicitColumns;
  312. for (int i = columns.size() + 1; i <= columnNumber; i++) {
  313. columns.add(createImplicitColumn(i));
  314. }
  315. }
  316. private TableColumn createImplicitColumn(int colNumber)
  317. throws FOPException {
  318. TableColumn implicitColumn = new TableColumn(this, true);
  319. PropertyList pList = new StaticPropertyList(
  320. implicitColumn, this.propList);
  321. implicitColumn.bind(pList);
  322. implicitColumn.setColumnWidth(new TableColLength(1.0, implicitColumn));
  323. implicitColumn.setColumnNumber(colNumber);
  324. if (!isSeparateBorderModel()) {
  325. implicitColumn.setCollapsedBorders(collapsingBorderModel); // TODO
  326. }
  327. return implicitColumn;
  328. }
  329. /**
  330. * Adds a column to the columns List, and updates the columnIndex
  331. * used for determining initial values for column-number
  332. *
  333. * @param col the column to add
  334. */
  335. private void addColumnNode(TableColumn col) {
  336. int colNumber = col.getColumnNumber();
  337. int colRepeat = col.getNumberColumnsRepeated();
  338. /* add nulls for non-occupied indices between
  339. * the last column up to and including the current one
  340. */
  341. while (columns.size() < colNumber + colRepeat - 1) {
  342. columns.add(null);
  343. }
  344. // in case column is repeated:
  345. // for the time being, add the same column
  346. // (colRepeat - 1) times to the columns list
  347. // TODO: need to force the column-number (?)
  348. for (int i = colNumber - 1; i < colNumber + colRepeat - 1; i++) {
  349. columns.set(i, col);
  350. }
  351. columnNumberManager.signalUsedColumnNumbers(colNumber, colNumber + colRepeat - 1);
  352. }
  353. boolean hasExplicitColumns() {
  354. return hasExplicitColumns;
  355. }
  356. /** @return true of table-layout="auto" */
  357. public boolean isAutoLayout() {
  358. return (tableLayout == EN_AUTO);
  359. }
  360. /**
  361. * Returns the list of table-column elements.
  362. *
  363. * @return a list of {@link TableColumn} elements, may contain null elements
  364. */
  365. public List getColumns() {
  366. return columns;
  367. }
  368. /**
  369. * Returns the column at the given index.
  370. *
  371. * @param index index of the column to be retrieved, 0-based
  372. * @return the corresponding column (may be an implicitly created column)
  373. */
  374. public TableColumn getColumn(int index) {
  375. return (TableColumn) columns.get(index);
  376. }
  377. /**
  378. * Returns the number of columns of this table.
  379. *
  380. * @return the number of columns, implicit or explicit, in this table
  381. */
  382. public int getNumberOfColumns() {
  383. return columns.size();
  384. }
  385. /** @return the body for the table-header. */
  386. public TableHeader getTableHeader() {
  387. return tableHeader;
  388. }
  389. /** @return the body for the table-footer. */
  390. public TableFooter getTableFooter() {
  391. return tableFooter;
  392. }
  393. /** @return true if the table-header should be omitted at breaks */
  394. public boolean omitHeaderAtBreak() {
  395. return (this.tableOmitHeaderAtBreak == EN_TRUE);
  396. }
  397. /** @return true if the table-footer should be omitted at breaks */
  398. public boolean omitFooterAtBreak() {
  399. return (this.tableOmitFooterAtBreak == EN_TRUE);
  400. }
  401. /**
  402. * @return the "inline-progression-dimension" FO trait.
  403. */
  404. public LengthRangeProperty getInlineProgressionDimension() {
  405. return inlineProgressionDimension;
  406. }
  407. /**
  408. * @return the "block-progression-dimension" FO trait.
  409. */
  410. public LengthRangeProperty getBlockProgressionDimension() {
  411. return blockProgressionDimension;
  412. }
  413. /**
  414. * @return the Common Margin Properties-Block.
  415. */
  416. public CommonMarginBlock getCommonMarginBlock() {
  417. return commonMarginBlock;
  418. }
  419. /**
  420. * @return the Common Border, Padding, and Background Properties.
  421. */
  422. public CommonBorderPaddingBackground getCommonBorderPaddingBackground() {
  423. return commonBorderPaddingBackground;
  424. }
  425. /** @return the "break-after" FO trait. */
  426. public int getBreakAfter() {
  427. return breakAfter;
  428. }
  429. /** @return the "break-before" FO trait. */
  430. public int getBreakBefore() {
  431. return breakBefore;
  432. }
  433. /** @return the "keep-with-next" FO trait. */
  434. public KeepProperty getKeepWithNext() {
  435. return keepWithNext;
  436. }
  437. /** @return the "keep-with-previous" FO trait. */
  438. public KeepProperty getKeepWithPrevious() {
  439. return keepWithPrevious;
  440. }
  441. /** @return the "keep-together" FO trait. */
  442. public KeepProperty getKeepTogether() {
  443. return keepTogether;
  444. }
  445. /**
  446. * Convenience method to check if a keep-together constraint is specified.
  447. * @return true if keep-together is active.
  448. */
  449. public boolean mustKeepTogether() {
  450. return !getKeepTogether().getWithinPage().isAuto()
  451. || !getKeepTogether().getWithinColumn().isAuto();
  452. }
  453. /** @return the "border-collapse" FO trait. */
  454. public int getBorderCollapse() {
  455. return borderCollapse;
  456. }
  457. /** @return true if the separate border model is active */
  458. public boolean isSeparateBorderModel() {
  459. return (getBorderCollapse() == EN_SEPARATE);
  460. }
  461. /** @return the "border-separation" FO trait. */
  462. public LengthPairProperty getBorderSeparation() {
  463. return borderSeparation;
  464. }
  465. /** {@inheritDoc} */
  466. public Direction getInlineProgressionDirection() {
  467. return writingModeTraits.getInlineProgressionDirection();
  468. }
  469. /** {@inheritDoc} */
  470. public Direction getBlockProgressionDirection() {
  471. return writingModeTraits.getBlockProgressionDirection();
  472. }
  473. /** {@inheritDoc} */
  474. public Direction getColumnProgressionDirection() {
  475. return writingModeTraits.getColumnProgressionDirection();
  476. }
  477. /** {@inheritDoc} */
  478. public Direction getRowProgressionDirection() {
  479. return writingModeTraits.getRowProgressionDirection();
  480. }
  481. /** {@inheritDoc} */
  482. public Direction getShiftDirection() {
  483. return writingModeTraits.getShiftDirection();
  484. }
  485. /** {@inheritDoc} */
  486. public WritingMode getWritingMode() {
  487. return writingModeTraits.getWritingMode();
  488. }
  489. /** @return the "fox:widow-content-limit" extension FO trait */
  490. public Length getWidowContentLimit() {
  491. return widowContentLimit;
  492. }
  493. /** @return the "fox:orphan-content-limit" extension FO trait */
  494. public Length getOrphanContentLimit() {
  495. return orphanContentLimit;
  496. }
  497. /** {@inheritDoc} */
  498. public String getLocalName() {
  499. return "table";
  500. }
  501. /**
  502. * {@inheritDoc}
  503. * @return {@link org.apache.fop.fo.Constants#FO_TABLE}
  504. */
  505. public int getNameId() {
  506. return FO_TABLE;
  507. }
  508. /** {@inheritDoc} */
  509. public FONode clone(FONode parent, boolean removeChildren)
  510. throws FOPException {
  511. Table clone = (Table) super.clone(parent, removeChildren);
  512. if (removeChildren) {
  513. clone.columns = new ArrayList();
  514. clone.columnsFinalized = false;
  515. clone.columnNumberManager = new ColumnNumberManager();
  516. clone.tableHeader = null;
  517. clone.tableFooter = null;
  518. clone.rowGroupBuilder = null;
  519. }
  520. return clone;
  521. }
  522. /** {@inheritDoc} */
  523. public ColumnNumberManager getColumnNumberManager() {
  524. return columnNumberManager;
  525. }
  526. RowGroupBuilder getRowGroupBuilder() {
  527. return rowGroupBuilder;
  528. }
  529. @Override
  530. protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
  531. // header sub-tree
  532. TableHeader header = getTableHeader();
  533. if ( header != null ) {
  534. ranges = header.collectDelimitedTextRanges ( ranges );
  535. }
  536. // footer sub-tree
  537. TableFooter footer = getTableFooter();
  538. if ( footer != null ) {
  539. ranges = footer.collectDelimitedTextRanges ( ranges );
  540. }
  541. // body sub-tree
  542. for ( Iterator it = getChildNodes(); ( it != null ) && it.hasNext();) {
  543. ranges = ( (FONode) it.next() ).collectDelimitedTextRanges ( ranges );
  544. }
  545. return ranges;
  546. }
  547. }