tablewalker.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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 a 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 a range iterator. All parameters are optional, but you have to specify either `boundaries` or `startPosition`.
  15. *
  16. * The most important values of iterator values are column & row of a cell.
  17. *
  18. * To iterate over given row:
  19. *
  20. * const tableWalker = new TableWalker( table, { startRow: 1, endRow: 2 } );
  21. *
  22. * for( const cellInfo of tableWalker ) {
  23. * console.log( 'A cell at row ' + cellInfo.row + ' and column ' + cellInfo.column );
  24. * }
  25. *
  26. * For instance the above code for a table:
  27. *
  28. * +----+----+----+----+----+----+
  29. * | 00 | 02 | 03 | 05 |
  30. * | +--- +----+----+----+
  31. * | | 12 | 24 | 25 |
  32. * | +----+----+----+----+
  33. * | | 22 |
  34. * |----+----+ +
  35. * | 31 | 32 | |
  36. * +----+----+----+----+----+----+
  37. *
  38. * will log in the console:
  39. *
  40. * 'A cell at row 1 and column 2'
  41. * 'A cell at row 1 and column 4'
  42. * 'A cell at row 1 and column 5'
  43. * 'A cell at row 2 and column 2'
  44. *
  45. * @constructor
  46. * @param {module:engine/model/element~Element} table A table over which iterate.
  47. * @param {Object} [options={}] Object with configuration.
  48. * @param {Number} [options.startRow=0] A row index for which this iterator should start.
  49. * @param {Number} [options.endRow] A row index for which this iterator should end.
  50. */
  51. constructor( table, options = {} ) {
  52. /**
  53. * The walker's table element.
  54. *
  55. * @readonly
  56. * @member {module:engine/model/element~Element}
  57. */
  58. this.table = table;
  59. /**
  60. * A row index on which this iterator will start.
  61. *
  62. * @readonly
  63. * @member {Number}
  64. */
  65. this.startRow = options.startRow || 0;
  66. /**
  67. * A row index on which this iterator will end.
  68. *
  69. * @readonly
  70. * @member {Number}
  71. */
  72. this.endRow = options.endRow;
  73. /**
  74. * A current row index.
  75. *
  76. * @readonly
  77. * @member {Number}
  78. */
  79. this.row = 0;
  80. /**
  81. * A current cell index in a row.
  82. *
  83. * @readonly
  84. * @member {Number}
  85. */
  86. this.cell = 0;
  87. /**
  88. * A current column index.
  89. *
  90. * @readonly
  91. * @member {Number}
  92. */
  93. this.column = 0;
  94. /**
  95. * The previous cell in a row.
  96. *
  97. * @readonly
  98. * @member {module:engine/model/element~Element}
  99. * @private
  100. */
  101. this._previousCell = undefined;
  102. /**
  103. * Holds information about spanned table cells.
  104. *
  105. * @readonly
  106. * @member {CellSpans}
  107. * @private
  108. */
  109. this._cellSpans = new CellSpans();
  110. /**
  111. * Cached table properties - returned for every yielded value.
  112. *
  113. * @readonly
  114. * @member {{headingRows: Number, headingColumns: Number}}
  115. * @private
  116. */
  117. this._tableData = {
  118. headingRows: this.table.getAttribute( 'headingRows' ) || 0,
  119. headingColumns: this.table.getAttribute( 'headingColumns' ) || 0
  120. };
  121. }
  122. /**
  123. * Iterable interface.
  124. *
  125. * @returns {Iterable.<module:table/tablewalker~TableWalkerValue>}
  126. */
  127. [ Symbol.iterator ]() {
  128. return this;
  129. }
  130. /**
  131. * Gets the next table walker's value.
  132. *
  133. * @returns {module:table/tablewalker~TableWalkerValue} Next table walker's value.
  134. */
  135. next() {
  136. const row = this.table.getChild( this.row );
  137. if ( !row ) {
  138. return { done: true };
  139. }
  140. // The previous cell is defined after the first cell in a row.
  141. if ( this._previousCell ) {
  142. const colspan = this._updateSpans();
  143. // Update the column index by a width of a previous cell.
  144. this.column += colspan;
  145. }
  146. const cell = row.getChild( this.cell );
  147. // If there is no cell then it's end of a row so update spans and reset indexes.
  148. if ( !cell ) {
  149. // Record spans of the previous cell.
  150. this._updateSpans();
  151. // Reset indexes and move to next row.
  152. this.cell = 0;
  153. this.column = 0;
  154. this.row++;
  155. this._previousCell = undefined;
  156. return this.next();
  157. }
  158. // Update the column index if the current column is overlapped by cells from previous rows that have rowspan attribute set.
  159. this.column = this._cellSpans.getAdjustedColumnIndex( this.row, this.column );
  160. // Update the cell indexes before returning value.
  161. this._previousCell = cell;
  162. this.cell++;
  163. if ( this.startRow > this.row || ( this.endRow && this.row > this.endRow ) ) {
  164. return this.next();
  165. }
  166. return {
  167. done: false,
  168. value: {
  169. cell,
  170. row: this.row,
  171. column: this.column,
  172. rowspan: cell.getAttribute( 'rowspan' ) || 1,
  173. colspan: cell.getAttribute( 'colspan' ) || 1,
  174. table: this._tableData
  175. }
  176. };
  177. }
  178. /**
  179. * Updates the cell spans of a previous cell.
  180. *
  181. * @returns {Number}
  182. * @private
  183. */
  184. _updateSpans() {
  185. const colspan = this._previousCell.getAttribute( 'colspan' ) || 1;
  186. const rowspan = this._previousCell.getAttribute( 'rowspan' ) || 1;
  187. this._cellSpans.recordSpans( this.row, this.column, rowspan, colspan );
  188. return colspan;
  189. }
  190. }
  191. // Holds information about spanned table cells.
  192. class CellSpans {
  193. // Creates CellSpans instance.
  194. constructor() {
  195. // Holds table cell spans mapping.
  196. //
  197. // @member {Map<Number, Number>}
  198. // @private
  199. this._spans = new Map();
  200. }
  201. // Returns proper column index if a current cell index is overlapped by other (has a span defined).
  202. //
  203. // @param {Number} row
  204. // @param {Number} column
  205. // @return {Number} Returns current column or updated column index.
  206. getAdjustedColumnIndex( row, column ) {
  207. let span = this._check( row, column ) || 0;
  208. // Offset current table cell columnIndex by spanning cells from rows above.
  209. while ( span ) {
  210. column += span;
  211. span = this._check( row, column );
  212. }
  213. return column;
  214. }
  215. // Updates spans based on current table cell height & width. Spans with height <= 1 will not be recorded.
  216. //
  217. // For instance if a table cell at row 0 and column 0 has height of 3 and width of 2 we're setting spans:
  218. //
  219. // 0 1 2 3 4 5
  220. // 0:
  221. // 1: 2
  222. // 2: 2
  223. // 3:
  224. //
  225. // Adding another spans for a table cell at row 2 and column 1 that has height of 2 and width of 4 will update above to:
  226. //
  227. // 0 1 2 3 4 5
  228. // 0:
  229. // 1: 2
  230. // 2: 2
  231. // 3: 4
  232. //
  233. // The above span mapping was calculated from a table below (cells 03 & 12 were not added as their height is 1):
  234. //
  235. // +----+----+----+----+----+----+
  236. // | 00 | 02 | 03 | 05 |
  237. // | +--- +----+----+----+
  238. // | | 12 | 24 | 25 |
  239. // | +----+----+----+----+
  240. // | | 22 |
  241. // |----+----+ +
  242. // | 31 | 32 | |
  243. // +----+----+----+----+----+----+
  244. //
  245. // @param {Number} rowIndex
  246. // @param {Number} columnIndex
  247. // @param {Number} height
  248. // @param {Number} width
  249. recordSpans( rowIndex, columnIndex, height, width ) {
  250. // This will update all rows below up to row height with value of span width.
  251. for ( let rowToUpdate = rowIndex + 1; rowToUpdate < rowIndex + height; rowToUpdate++ ) {
  252. if ( !this._spans.has( rowToUpdate ) ) {
  253. this._spans.set( rowToUpdate, new Map() );
  254. }
  255. const rowSpans = this._spans.get( rowToUpdate );
  256. rowSpans.set( columnIndex, width );
  257. }
  258. }
  259. // Checks if given table cell is spanned by other.
  260. //
  261. // @param {Number} rowIndex
  262. // @param {Number} columnIndex
  263. // @return {Boolean|Number} Returns false or width of a span.
  264. _check( rowIndex, columnIndex ) {
  265. if ( !this._spans.has( rowIndex ) ) {
  266. return false;
  267. }
  268. const rowSpans = this._spans.get( rowIndex );
  269. return rowSpans.has( columnIndex ) ? rowSpans.get( columnIndex ) : false;
  270. }
  271. }
  272. /**
  273. * Object returned by {@link module:table/tablewalker~TableWalker} when traversing table cells.
  274. *
  275. * @typedef {Object} module:table/tablewalker~TableWalkerValue
  276. * @property {module:engine/model/element~Element} cell Current table cell.
  277. * @property {Number} row The row index of a cell.
  278. * @property {Number} column The column index of a cell. Column index is adjusted to widths & heights of previous cells.
  279. * @property {Number} colspan The colspan attribute of a cell - always defined even if model attribute is not present.
  280. * @property {Number} rowspan The rowspan attribute of a cell - always defined even if model attribute is not present.
  281. * @property {Object} table Table attributes
  282. * @property {Object} table.headingRows The heading rows attribute of a table - always defined even if model attribute is not present.
  283. * @property {Object} table.headingColumns The heading columns attribute of a table - always defined even if model attribute is not present.
  284. */