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 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  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. pList.getExplicit(PR_WRITING_MODE) != null);
  128. //Bind extension properties
  129. widowContentLimit = pList.get(PR_X_WIDOW_CONTENT_LIMIT).getLength();
  130. orphanContentLimit = pList.get(PR_X_ORPHAN_CONTENT_LIMIT).getLength();
  131. if (!blockProgressionDimension.getOptimum(null).isAuto()) {
  132. TableEventProducer eventProducer = TableEventProducer.Provider.get(
  133. getUserAgent().getEventBroadcaster());
  134. eventProducer.nonAutoBPDOnTable(this, getLocator());
  135. // Anyway, the bpd of a table is not used by the layout code
  136. }
  137. if (tableLayout == EN_AUTO) {
  138. getFOValidationEventProducer().unimplementedFeature(this, getName(),
  139. "table-layout=\"auto\"", getLocator());
  140. }
  141. if (!isSeparateBorderModel()) {
  142. if (borderCollapse == EN_COLLAPSE_WITH_PRECEDENCE) {
  143. getFOValidationEventProducer().unimplementedFeature(this, getName(),
  144. "border-collapse=\"collapse-with-precedence\"; defaulting to \"collapse\"", getLocator());
  145. borderCollapse = EN_COLLAPSE;
  146. }
  147. if (getCommonBorderPaddingBackground().hasPadding(
  148. ValidationPercentBaseContext.getPseudoContext())) {
  149. //See "17.6.2 The collapsing border model" in CSS2
  150. TableEventProducer eventProducer = TableEventProducer.Provider.get(
  151. getUserAgent().getEventBroadcaster());
  152. eventProducer.noTablePaddingWithCollapsingBorderModel(this, getLocator());
  153. }
  154. }
  155. /* Store reference to the property list, so
  156. * new lists can be created in case the table has no
  157. * explicit columns
  158. * (see addDefaultColumn())
  159. */
  160. this.propList = pList;
  161. }
  162. /** {@inheritDoc} */
  163. public void startOfNode() throws FOPException {
  164. super.startOfNode();
  165. getFOEventHandler().startTable(this);
  166. }
  167. /**
  168. * {@inheritDoc}
  169. * <br>XSL Content Model: (marker*,table-column*,table-header?,table-footer?,table-body+)
  170. */
  171. protected void validateChildNode(Locator loc, String nsURI, String localName)
  172. throws ValidationException {
  173. if (FO_URI.equals(nsURI)) {
  174. if ("marker".equals(localName)) {
  175. if (tableColumnFound || tableHeaderFound || tableFooterFound
  176. || tableBodyFound) {
  177. nodesOutOfOrderError(loc, "fo:marker",
  178. "(table-column*,table-header?,table-footer?,table-body+)");
  179. }
  180. } else if ("table-column".equals(localName)) {
  181. tableColumnFound = true;
  182. if (tableHeaderFound || tableFooterFound || tableBodyFound) {
  183. nodesOutOfOrderError(loc, "fo:table-column",
  184. "(table-header?,table-footer?,table-body+)");
  185. }
  186. } else if ("table-header".equals(localName)) {
  187. if (tableHeaderFound) {
  188. tooManyNodesError(loc, "table-header");
  189. } else {
  190. tableHeaderFound = true;
  191. if (tableFooterFound || tableBodyFound) {
  192. nodesOutOfOrderError(loc, "fo:table-header",
  193. "(table-footer?,table-body+)");
  194. }
  195. }
  196. } else if ("table-footer".equals(localName)) {
  197. if (tableFooterFound) {
  198. tooManyNodesError(loc, "table-footer");
  199. } else {
  200. tableFooterFound = true;
  201. if (tableBodyFound) {
  202. if (getUserAgent().validateStrictly()) {
  203. nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)", true);
  204. }
  205. if (!isSeparateBorderModel()) {
  206. TableEventProducer eventProducer = TableEventProducer.Provider.get(
  207. getUserAgent().getEventBroadcaster());
  208. eventProducer.footerOrderCannotRecover(this, getName(), getLocator());
  209. }
  210. }
  211. }
  212. } else if ("table-body".equals(localName)) {
  213. tableBodyFound = true;
  214. } else {
  215. invalidChildError(loc, nsURI, localName);
  216. }
  217. }
  218. }
  219. /** {@inheritDoc} */
  220. public void endOfNode() throws FOPException {
  221. super.endOfNode();
  222. getFOEventHandler().endTable(this);
  223. }
  224. /** {@inheritDoc} */
  225. public void finalizeNode() throws FOPException {
  226. if (!tableBodyFound) {
  227. missingChildElementError(
  228. "(marker*,table-column*,table-header?,table-footer?"
  229. + ",table-body+)");
  230. }
  231. if (!hasChildren()) {
  232. getParent().removeChild(this);
  233. return;
  234. }
  235. if (!inMarker()) {
  236. rowGroupBuilder.endTable();
  237. /* clean up */
  238. for (int i = columns.size(); --i >= 0;) {
  239. TableColumn col = (TableColumn) columns.get(i);
  240. if (col != null) {
  241. col.releasePropertyList();
  242. }
  243. }
  244. this.propList = null;
  245. rowGroupBuilder = null;
  246. }
  247. }
  248. /** {@inheritDoc} */
  249. protected void addChildNode(FONode child) throws FOPException {
  250. int childId = child.getNameId();
  251. switch (childId) {
  252. case FO_TABLE_COLUMN:
  253. hasExplicitColumns = true;
  254. if (!inMarker()) {
  255. addColumnNode((TableColumn) child);
  256. } else {
  257. columns.add(child);
  258. }
  259. break;
  260. case FO_TABLE_HEADER:
  261. case FO_TABLE_FOOTER:
  262. case FO_TABLE_BODY:
  263. if (!inMarker() && !columnsFinalized) {
  264. columnsFinalized = true;
  265. if (hasExplicitColumns) {
  266. finalizeColumns();
  267. rowGroupBuilder = new FixedColRowGroupBuilder(this);
  268. } else {
  269. rowGroupBuilder = new VariableColRowGroupBuilder(this);
  270. }
  271. }
  272. switch (childId) {
  273. case FO_TABLE_FOOTER:
  274. tableFooter = (TableFooter) child;
  275. break;
  276. case FO_TABLE_HEADER:
  277. tableHeader = (TableHeader) child;
  278. break;
  279. default:
  280. super.addChildNode(child);
  281. }
  282. break;
  283. default:
  284. super.addChildNode(child);
  285. }
  286. }
  287. private void finalizeColumns() throws FOPException {
  288. for (int i = 0; i < columns.size(); i++) {
  289. if (columns.get(i) == null) {
  290. columns.set(i, createImplicitColumn(i + 1));
  291. }
  292. }
  293. }
  294. /** {@inheritDoc} */
  295. public CommonAccessibility getCommonAccessibility() {
  296. return commonAccessibility;
  297. }
  298. /** {@inheritDoc} */
  299. public Table getTable() {
  300. return this;
  301. }
  302. /**
  303. * Creates the appropriate number of additional implicit columns to match the given
  304. * column number. Used when the table has no explicit column: the number of columns is
  305. * then determined by the row that has the most columns.
  306. *
  307. * @param columnNumber the table must at least have this number of column
  308. * @throws FOPException if there was an error creating the property list for implicit
  309. * columns
  310. */
  311. void ensureColumnNumber(int columnNumber) throws FOPException {
  312. assert !hasExplicitColumns;
  313. for (int i = columns.size() + 1; i <= columnNumber; i++) {
  314. columns.add(createImplicitColumn(i));
  315. }
  316. }
  317. private TableColumn createImplicitColumn(int colNumber)
  318. throws FOPException {
  319. TableColumn implicitColumn = new TableColumn(this, true);
  320. PropertyList pList = new StaticPropertyList(
  321. implicitColumn, this.propList);
  322. implicitColumn.bind(pList);
  323. implicitColumn.setColumnWidth(new TableColLength(1.0, implicitColumn));
  324. implicitColumn.setColumnNumber(colNumber);
  325. if (!isSeparateBorderModel()) {
  326. implicitColumn.setCollapsedBorders(collapsingBorderModel); // TODO
  327. }
  328. return implicitColumn;
  329. }
  330. /**
  331. * Adds a column to the columns List, and updates the columnIndex
  332. * used for determining initial values for column-number
  333. *
  334. * @param col the column to add
  335. */
  336. private void addColumnNode(TableColumn col) {
  337. int colNumber = col.getColumnNumber();
  338. int colRepeat = col.getNumberColumnsRepeated();
  339. /* add nulls for non-occupied indices between
  340. * the last column up to and including the current one
  341. */
  342. while (columns.size() < colNumber + colRepeat - 1) {
  343. columns.add(null);
  344. }
  345. // in case column is repeated:
  346. // for the time being, add the same column
  347. // (colRepeat - 1) times to the columns list
  348. // TODO: need to force the column-number (?)
  349. for (int i = colNumber - 1; i < colNumber + colRepeat - 1; i++) {
  350. columns.set(i, col);
  351. }
  352. columnNumberManager.signalUsedColumnNumbers(colNumber, colNumber + colRepeat - 1);
  353. }
  354. boolean hasExplicitColumns() {
  355. return hasExplicitColumns;
  356. }
  357. /** @return true of table-layout="auto" */
  358. public boolean isAutoLayout() {
  359. return (tableLayout == EN_AUTO);
  360. }
  361. /**
  362. * Returns the list of table-column elements.
  363. *
  364. * @return a list of {@link TableColumn} elements, may contain null elements
  365. */
  366. public List getColumns() {
  367. return columns;
  368. }
  369. /**
  370. * Returns the column at the given index.
  371. *
  372. * @param index index of the column to be retrieved, 0-based
  373. * @return the corresponding column (may be an implicitly created column)
  374. */
  375. public TableColumn getColumn(int index) {
  376. return (TableColumn) columns.get(index);
  377. }
  378. /**
  379. * Returns the number of columns of this table.
  380. *
  381. * @return the number of columns, implicit or explicit, in this table
  382. */
  383. public int getNumberOfColumns() {
  384. return columns.size();
  385. }
  386. /** @return the body for the table-header. */
  387. public TableHeader getTableHeader() {
  388. return tableHeader;
  389. }
  390. /** @return the body for the table-footer. */
  391. public TableFooter getTableFooter() {
  392. return tableFooter;
  393. }
  394. /** @return true if the table-header should be omitted at breaks */
  395. public boolean omitHeaderAtBreak() {
  396. return (this.tableOmitHeaderAtBreak == EN_TRUE);
  397. }
  398. /** @return true if the table-footer should be omitted at breaks */
  399. public boolean omitFooterAtBreak() {
  400. return (this.tableOmitFooterAtBreak == EN_TRUE);
  401. }
  402. /**
  403. * @return the "inline-progression-dimension" FO trait.
  404. */
  405. public LengthRangeProperty getInlineProgressionDimension() {
  406. return inlineProgressionDimension;
  407. }
  408. /**
  409. * @return the "block-progression-dimension" FO trait.
  410. */
  411. public LengthRangeProperty getBlockProgressionDimension() {
  412. return blockProgressionDimension;
  413. }
  414. /**
  415. * @return the Common Margin Properties-Block.
  416. */
  417. public CommonMarginBlock getCommonMarginBlock() {
  418. return commonMarginBlock;
  419. }
  420. /**
  421. * @return the Common Border, Padding, and Background Properties.
  422. */
  423. public CommonBorderPaddingBackground getCommonBorderPaddingBackground() {
  424. return commonBorderPaddingBackground;
  425. }
  426. /** @return the "break-after" FO trait. */
  427. public int getBreakAfter() {
  428. return breakAfter;
  429. }
  430. /** @return the "break-before" FO trait. */
  431. public int getBreakBefore() {
  432. return breakBefore;
  433. }
  434. /** @return the "keep-with-next" FO trait. */
  435. public KeepProperty getKeepWithNext() {
  436. return keepWithNext;
  437. }
  438. /** @return the "keep-with-previous" FO trait. */
  439. public KeepProperty getKeepWithPrevious() {
  440. return keepWithPrevious;
  441. }
  442. /** @return the "keep-together" FO trait. */
  443. public KeepProperty getKeepTogether() {
  444. return keepTogether;
  445. }
  446. /**
  447. * Convenience method to check if a keep-together constraint is specified.
  448. * @return true if keep-together is active.
  449. */
  450. public boolean mustKeepTogether() {
  451. return !getKeepTogether().getWithinPage().isAuto()
  452. || !getKeepTogether().getWithinColumn().isAuto();
  453. }
  454. /** @return the "border-collapse" FO trait. */
  455. public int getBorderCollapse() {
  456. return borderCollapse;
  457. }
  458. /** @return true if the separate border model is active */
  459. public boolean isSeparateBorderModel() {
  460. return (getBorderCollapse() == EN_SEPARATE);
  461. }
  462. /** @return the "border-separation" FO trait. */
  463. public LengthPairProperty getBorderSeparation() {
  464. return borderSeparation;
  465. }
  466. /** {@inheritDoc} */
  467. public Direction getInlineProgressionDirection() {
  468. return writingModeTraits.getInlineProgressionDirection();
  469. }
  470. /** {@inheritDoc} */
  471. public Direction getBlockProgressionDirection() {
  472. return writingModeTraits.getBlockProgressionDirection();
  473. }
  474. /** {@inheritDoc} */
  475. public Direction getColumnProgressionDirection() {
  476. return writingModeTraits.getColumnProgressionDirection();
  477. }
  478. /** {@inheritDoc} */
  479. public Direction getRowProgressionDirection() {
  480. return writingModeTraits.getRowProgressionDirection();
  481. }
  482. /** {@inheritDoc} */
  483. public Direction getShiftDirection() {
  484. return writingModeTraits.getShiftDirection();
  485. }
  486. /** {@inheritDoc} */
  487. public WritingMode getWritingMode() {
  488. return writingModeTraits.getWritingMode();
  489. }
  490. /** {@inheritDoc} */
  491. public boolean getExplicitWritingMode() {
  492. return writingModeTraits.getExplicitWritingMode();
  493. }
  494. /** @return the "fox:widow-content-limit" extension FO trait */
  495. public Length getWidowContentLimit() {
  496. return widowContentLimit;
  497. }
  498. /** @return the "fox:orphan-content-limit" extension FO trait */
  499. public Length getOrphanContentLimit() {
  500. return orphanContentLimit;
  501. }
  502. /** {@inheritDoc} */
  503. public String getLocalName() {
  504. return "table";
  505. }
  506. /**
  507. * {@inheritDoc}
  508. * @return {@link org.apache.fop.fo.Constants#FO_TABLE}
  509. */
  510. public int getNameId() {
  511. return FO_TABLE;
  512. }
  513. /** {@inheritDoc} */
  514. public FONode clone(FONode parent, boolean removeChildren)
  515. throws FOPException {
  516. Table clone = (Table) super.clone(parent, removeChildren);
  517. if (removeChildren) {
  518. clone.columns = new ArrayList();
  519. clone.columnsFinalized = false;
  520. clone.columnNumberManager = new ColumnNumberManager();
  521. clone.tableHeader = null;
  522. clone.tableFooter = null;
  523. clone.rowGroupBuilder = null;
  524. }
  525. return clone;
  526. }
  527. /** {@inheritDoc} */
  528. public ColumnNumberManager getColumnNumberManager() {
  529. return columnNumberManager;
  530. }
  531. RowGroupBuilder getRowGroupBuilder() {
  532. return rowGroupBuilder;
  533. }
  534. @Override
  535. protected Stack<DelimitedTextRange> collectDelimitedTextRanges(Stack<DelimitedTextRange> ranges,
  536. DelimitedTextRange currentRange) {
  537. // header sub-tree
  538. TableHeader header = getTableHeader();
  539. if (header != null) {
  540. ranges = header.collectDelimitedTextRanges(ranges);
  541. }
  542. // footer sub-tree
  543. TableFooter footer = getTableFooter();
  544. if (footer != null) {
  545. ranges = footer.collectDelimitedTextRanges(ranges);
  546. }
  547. // body sub-tree
  548. for (Iterator it = getChildNodes(); (it != null) && it.hasNext();) {
  549. ranges = ((FONode) it.next()).collectDelimitedTextRanges(ranges);
  550. }
  551. return ranges;
  552. }
  553. @Override
  554. protected boolean isBidiBoundary(boolean propagate) {
  555. return getExplicitWritingMode();
  556. }
  557. }