tablewalker.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module table/tablewalker
  7. */
  8. /**
  9. * Table iterator class. It allows to iterate over table cells. For each cell the iterator yields
  10. * {@link module:table/tablewalker~TableWalkerValue} with proper table cell attributes.
  11. */
  12. export default class TableWalker {
  13. /**
  14. * Creates an instance of the table walker.
  15. *
  16. *
  17. * The table walker iterates internally by traversing the table from row index = 0 and column index = 0.
  18. * It walks row by row and column by column in order to output values defined in the constructor.
  19. * By default it will output only those locations that are occupied by a cell. To include also spanned rows and columns,
  20. * pass the `includeSpanned` option to the constructor.
  21. *
  22. * The most important values of the iterator are column and row indexes of a cell.
  23. *
  24. * To iterate over a given row:
  25. *
  26. * const tableWalker = new TableWalker( table, { startRow: 1, endRow: 2 } );
  27. *
  28. * for ( const cellInfo of tableWalker ) {
  29. * console.log( 'A cell at row ' + cellInfo.row + ' and column ' + cellInfo.column );
  30. * }
  31. *
  32. * For instance the code above for the following table:
  33. *
  34. * +----+----+----+----+----+----+
  35. * | 00 | 02 | 03 | 05 |
  36. * | +----+----+----+----+
  37. * | | 12 | 14 | 15 |
  38. * | +----+----+----+----+
  39. * | | 22 |
  40. * |----+----+ +
  41. * | 31 | 32 | |
  42. * +----+----+----+----+----+----+
  43. *
  44. * will log in the console:
  45. *
  46. * 'A cell at row 1 and column 2'
  47. * 'A cell at row 1 and column 4'
  48. * 'A cell at row 1 and column 5'
  49. * 'A cell at row 2 and column 2'
  50. *
  51. * To also iterate over spanned cells:
  52. *
  53. * const tableWalker = new TableWalker( table, { startRow: 1, endRow: 1, includeSpanned: true } );
  54. *
  55. * for ( const cellInfo of tableWalker ) {
  56. * console.log( 'Cell at ' + cellInfo.row + ' x ' + cellInfo.column + ' : ' + ( cellInfo.cell ? 'has data' : 'is spanned' ) );
  57. * }
  58. *
  59. * will log in the console for the table from previous example:
  60. *
  61. * 'Cell at 1 x 0 : is spanned'
  62. * 'Cell at 1 x 1 : is spanned'
  63. * 'Cell at 1 x 2 : has data'
  64. * 'Cell at 1 x 3 : is spanned'
  65. * 'Cell at 1 x 4 : has data'
  66. * 'Cell at 1 x 5 : has data'
  67. *
  68. * @constructor
  69. * @param {module:engine/model/element~Element} table A table over which the walker iterates.
  70. * @param {Object} [options={}] An object with configuration.
  71. * @param {Number} [options.column] A column index for which this iterator will output cells.
  72. * @param {Number} [options.startRow=0] A row index for which this iterator should start.
  73. * @param {Number} [options.endRow] A row index for which this iterator should end.
  74. * @param {Boolean} [options.includeSpanned] Also return values for spanned cells.
  75. */
  76. constructor( table, options = {} ) {
  77. /**
  78. * The walker's table element.
  79. *
  80. * @readonly
  81. * @member {module:engine/model/element~Element}
  82. */
  83. this.table = table;
  84. /**
  85. * A row index on which this iterator will start.
  86. *
  87. * @readonly
  88. * @member {Number}
  89. */
  90. this.startRow = options.startRow || 0;
  91. /**
  92. * A row index on which this iterator will end.
  93. *
  94. * @readonly
  95. * @member {Number}
  96. */
  97. this.endRow = typeof options.endRow == 'number' ? options.endRow : undefined;
  98. /**
  99. * Enables output of spanned cells that are normally not yielded.
  100. *
  101. * @readonly
  102. * @member {Boolean}
  103. */
  104. this.includeSpanned = !!options.includeSpanned;
  105. /**
  106. * If set, the table walker will only output cells of a given column or cells that overlap it.
  107. *
  108. * @readonly
  109. * @member {Number}
  110. */
  111. this.column = typeof options.column == 'number' ? options.column : undefined;
  112. /**
  113. * Row indexes to skip from the iteration.
  114. *
  115. * @readonly
  116. * @member {Set<Number>}
  117. * @private
  118. */
  119. this._skipRows = new Set();
  120. /**
  121. * The current row index.
  122. *
  123. * @readonly
  124. * @member {Number}
  125. * @private
  126. */
  127. this._row = 0;
  128. /**
  129. * The current column index.
  130. *
  131. * @readonly
  132. * @member {Number}
  133. * @private
  134. */
  135. this._column = 0;
  136. /**
  137. * The cell index in a parent row. For spanned cells when {@link #includeSpanned} is set to `true`,
  138. * this represents the index of the next table cell.
  139. *
  140. * @readonly
  141. * @member {Number}
  142. * @private
  143. */
  144. this._cell = 0;
  145. /**
  146. * Holds a map of spanned cells in a table.
  147. *
  148. * @readonly
  149. * @member {Map<Number, Map.<Number, Number>>}
  150. * @private
  151. */
  152. this._spannedCells = new Map();
  153. }
  154. /**
  155. * Iterable interface.
  156. *
  157. * @returns {Iterable.<module:table/tablewalker~TableWalkerValue>}
  158. */
  159. [ Symbol.iterator ]() {
  160. return this;
  161. }
  162. /**
  163. * Gets the next table walker's value.
  164. *
  165. * @returns {module:table/tablewalker~TableWalkerValue} The next table walker's value.
  166. */
  167. next() {
  168. const row = this.table.getChild( this._row );
  169. // Iterator is done when no row (table end) or the row is after #endRow.
  170. if ( !row || this._isOverEndRow() ) {
  171. return { done: true };
  172. }
  173. // Spanned cell location handling.
  174. if ( this._isSpanned( this._row, this._column ) ) {
  175. // Current column must be kept as it will be updated before returning current value.
  176. const currentColumn = this._column;
  177. const outValue = this._formatOutValue( undefined, currentColumn );
  178. // Advance to next column - always.
  179. this._column++;
  180. const skipCurrentValue = !this.includeSpanned || this._shouldSkipRow() || this._shouldSkipColumn( currentColumn, 1 );
  181. // The current value will be returned only if #includedSpanned=true and also current row and column are not skipped.
  182. return skipCurrentValue ? this.next() : outValue;
  183. }
  184. // The cell location is not spanned by other cells.
  185. const cell = row.getChild( this._cell );
  186. if ( !cell ) {
  187. // If there are no more cells left in row advance to next row.
  188. this._row++;
  189. // And reset column & cell indexes.
  190. this._column = 0;
  191. this._cell = 0;
  192. // Return next value.
  193. return this.next();
  194. }
  195. // Read table cell attributes.
  196. const colspan = parseInt( cell.getAttribute( 'colspan' ) || 1 );
  197. const rowspan = parseInt( cell.getAttribute( 'rowspan' ) || 1 );
  198. // Record this cell spans if it's not 1x1 cell.
  199. if ( colspan > 1 || rowspan > 1 ) {
  200. this._recordSpans( this._row, this._column, rowspan, colspan );
  201. }
  202. // Current column must be kept as it will be updated before returning current value.
  203. const currentColumn = this._column;
  204. const outValue = this._formatOutValue( cell, currentColumn, rowspan, colspan );
  205. // Advance to next column before returning value.
  206. this._column++;
  207. // Advance to next cell in a parent row before returning value.
  208. this._cell++;
  209. const skipCurrentValue = this._shouldSkipRow() || this._shouldSkipColumn( currentColumn, colspan );
  210. // The current value will be returned only if current row & column are not skipped.
  211. return skipCurrentValue ? this.next() : outValue;
  212. }
  213. /**
  214. * Marks a row to skip in the next iteration. It will also skip cells from the current row if there are any cells from the current row
  215. * to output.
  216. *
  217. * @param {Number} row Row index to skip.
  218. */
  219. skipRow( row ) {
  220. this._skipRows.add( row );
  221. }
  222. /**
  223. * Checks if the current row is over {@link #endRow}.
  224. *
  225. * @returns {Boolean}
  226. * @private
  227. */
  228. _isOverEndRow() {
  229. // If {@link #endRow) is defined skip all rows above it.
  230. return this.endRow !== undefined && this._row > this.endRow;
  231. }
  232. /**
  233. * A common method for formatting the iterator's output value.
  234. *
  235. * @param {module:engine/model/element~Element|undefined} cell The table cell to output. It might be undefined for spanned cell
  236. * locations.
  237. * @param {Number} column Column index (use the cached value)
  238. * @param {Number} rowspan Rowspan of the current cell.
  239. * @param {Number} colspan Colspan of the current cell.
  240. * @returns {{done: boolean, value: {cell: *, row: Number, column: *, rowspan: *, colspan: *, cellIndex: Number}}}
  241. * @private
  242. */
  243. _formatOutValue( cell, column, rowspan = 1, colspan = 1 ) {
  244. return {
  245. done: false,
  246. value: {
  247. cell,
  248. row: this._row,
  249. column,
  250. rowspan,
  251. colspan,
  252. cellIndex: this._cell
  253. }
  254. };
  255. }
  256. /**
  257. * Checks if the current row should be skipped.
  258. *
  259. * @returns {Boolean}
  260. * @private
  261. */
  262. _shouldSkipRow() {
  263. const rowIsBelowStartRow = this._row < this.startRow;
  264. const rowIsMarkedAsSkipped = this._skipRows.has( this._row );
  265. return rowIsBelowStartRow || rowIsMarkedAsSkipped;
  266. }
  267. /**
  268. * Checks if the current column should be skipped.
  269. *
  270. * @param {Number} column
  271. * @param {Number} colspan
  272. * @returns {Boolean}
  273. * @private
  274. */
  275. _shouldSkipColumn( column, colspan ) {
  276. if ( this.column === undefined ) {
  277. // The {@link #column} is not defined so output all columns.
  278. return false;
  279. }
  280. // When outputting cells from given column we skip:
  281. // - Cells that are not on that column.
  282. const isCurrentColumn = column === this.column;
  283. // - CSells that are before given column and they overlaps given column.
  284. const isPreviousThatOverlapsColumn = column < this.column && column + colspan > this.column;
  285. return !isCurrentColumn && !isPreviousThatOverlapsColumn;
  286. }
  287. /**
  288. * Checks if the current cell location (row x column) is spanned by another cell.
  289. *
  290. * @param {Number} row Row index of a cell location to check.
  291. * @param {Number} column Column index of a cell location to check.
  292. * @returns {Boolean}
  293. * @private
  294. */
  295. _isSpanned( row, column ) {
  296. if ( !this._spannedCells.has( row ) ) {
  297. // No spans for given row.
  298. return false;
  299. }
  300. const rowSpans = this._spannedCells.get( row );
  301. // If spans for given rows has entry for column it means that this location if spanned by other cell.
  302. return rowSpans.has( column );
  303. }
  304. /**
  305. * Updates spanned cells map relative to the current cell location and its span dimensions.
  306. *
  307. * @param {Number} row Row index of a cell.
  308. * @param {Number} column Column index of a cell.
  309. * @param {Number} rowspan Cell height.
  310. * @param {Number} colspan Cell width.
  311. * @private
  312. */
  313. _recordSpans( row, column, rowspan, colspan ) {
  314. // This will update all cell locations after current column - ie a cell has colspan set.
  315. for ( let columnToUpdate = column + 1; columnToUpdate <= column + colspan - 1; columnToUpdate++ ) {
  316. this._markSpannedCell( row, columnToUpdate );
  317. }
  318. // This will update all rows below current up to row's height.
  319. for ( let rowToUpdate = row + 1; rowToUpdate < row + rowspan; rowToUpdate++ ) {
  320. for ( let columnToUpdate = column; columnToUpdate <= column + colspan - 1; columnToUpdate++ ) {
  321. this._markSpannedCell( rowToUpdate, columnToUpdate );
  322. }
  323. }
  324. }
  325. /**
  326. * Marks the cell location as spanned by another cell.
  327. *
  328. * @param {Number} row Row index of the cell location.
  329. * @param {Number} column Column index of the cell location.
  330. * @private
  331. */
  332. _markSpannedCell( row, column ) {
  333. if ( !this._spannedCells.has( row ) ) {
  334. this._spannedCells.set( row, new Map() );
  335. }
  336. const rowSpans = this._spannedCells.get( row );
  337. rowSpans.set( column, true );
  338. }
  339. }
  340. /**
  341. * An object returned by {@link module:table/tablewalker~TableWalker} when traversing table cells.
  342. *
  343. * @typedef {Object} module:table/tablewalker~TableWalkerValue
  344. * @property {module:engine/model/element~Element} [cell] The current table cell. Might be empty if
  345. * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to `true`.
  346. * @property {Number} row The row index of a cell.
  347. * @property {Number} column The column index of a cell. Column index is adjusted to widths and heights of previous cells.
  348. * @property {Number} [colspan] The `colspan` attribute of a cell. It is always defined even if the model attribute is not present. Not
  349. * set if {@link module:table/tablewalker~TableWalker#includeSpanned} is set to `true`.
  350. * @property {Number} [rowspan] The `rowspan` attribute of a cell. It is always defined even if the model attribute is not present. Not
  351. * set if {@link module:table/tablewalker~TableWalker#includeSpanned} is set to `true`.
  352. * @property {Number} cellIndex The index of the current cell in a parent row. When using the `includeSpanned` option it will indicate the
  353. * next child index if #cell is empty (which indicates that the cell is spanned by another cell).
  354. */