croptable.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module table/tableselection/croptable
  7. */
  8. import { findAncestor } from '../commands/utils';
  9. /**
  10. * Returns cropped table from selected table cells.
  11. *
  12. * This is to be used with table selection
  13. *
  14. * tableSelection.startSelectingFrom( startCell )
  15. * tableSelection.setSelectingFrom( endCell )
  16. *
  17. * const croppedTable = cropTable( tableSelection.getSelectedTableCells );
  18. *
  19. * **Note**: This function is used also by {@link module:table/tableselection~TableSelection#getSelectionAsFragment}
  20. *
  21. * @param {Iterable.<module:engine/model/element~Element>} selectedTableCellsIterator
  22. * @param {module:table/tableutils~TableUtils} tableUtils
  23. * @param {module:engine/model/writer~Writer} writer
  24. * @returns {module:engine/model/element~Element}
  25. */
  26. export default function cropTable( selectedTableCellsIterator, tableUtils, writer ) {
  27. const selectedTableCells = Array.from( selectedTableCellsIterator );
  28. const startElement = selectedTableCells[ 0 ];
  29. const endElement = selectedTableCells[ selectedTableCells.length - 1 ];
  30. const { row: startRow, column: startColumn } = tableUtils.getCellLocation( startElement );
  31. const tableCopy = makeTableCopy( selectedTableCells, startColumn, writer, tableUtils );
  32. const { row: endRow, column: endColumn } = tableUtils.getCellLocation( endElement );
  33. const selectionWidth = endColumn - startColumn + 1;
  34. const selectionHeight = endRow - startRow + 1;
  35. trimTable( tableCopy, selectionWidth, selectionHeight, writer, tableUtils );
  36. const sourceTable = findAncestor( 'table', startElement );
  37. addHeadingsToTableCopy( tableCopy, sourceTable, startRow, startColumn, writer );
  38. return tableCopy;
  39. }
  40. // Creates a table copy from a selected table cells.
  41. //
  42. // It fills "gaps" in copied table - ie when cell outside copied range was spanning over selection.
  43. function makeTableCopy( selectedTableCells, startColumn, writer, tableUtils ) {
  44. const tableCopy = writer.createElement( 'table' );
  45. const rowToCopyMap = new Map();
  46. const copyToOriginalColumnMap = new Map();
  47. for ( const tableCell of selectedTableCells ) {
  48. const row = findAncestor( 'tableRow', tableCell );
  49. if ( !rowToCopyMap.has( row ) ) {
  50. const rowCopy = row._clone();
  51. writer.append( rowCopy, tableCopy );
  52. rowToCopyMap.set( row, rowCopy );
  53. }
  54. const tableCellCopy = tableCell._clone( true );
  55. const { column } = tableUtils.getCellLocation( tableCell );
  56. copyToOriginalColumnMap.set( tableCellCopy, column );
  57. writer.append( tableCellCopy, rowToCopyMap.get( row ) );
  58. }
  59. addMissingTableCells( tableCopy, startColumn, copyToOriginalColumnMap, writer, tableUtils );
  60. return tableCopy;
  61. }
  62. // Fills gaps for spanned cell from outside the selection range.
  63. function addMissingTableCells( tableCopy, startColumn, copyToOriginalColumnMap, writer, tableUtils ) {
  64. for ( const row of tableCopy.getChildren() ) {
  65. for ( const tableCell of Array.from( row.getChildren() ) ) {
  66. const { column } = tableUtils.getCellLocation( tableCell );
  67. const originalColumn = copyToOriginalColumnMap.get( tableCell );
  68. const shiftedColumn = originalColumn - startColumn;
  69. if ( column !== shiftedColumn ) {
  70. for ( let i = 0; i < shiftedColumn - column; i++ ) {
  71. const prepCell = writer.createElement( 'tableCell' );
  72. writer.insert( prepCell, writer.createPositionBefore( tableCell ) );
  73. const paragraph = writer.createElement( 'paragraph' );
  74. writer.insert( paragraph, prepCell, 0 );
  75. writer.insertText( '', paragraph, 0 );
  76. }
  77. }
  78. }
  79. }
  80. }
  81. // Trims table to a given dimensions.
  82. function trimTable( table, width, height, writer, tableUtils ) {
  83. for ( const row of table.getChildren() ) {
  84. for ( const tableCell of row.getChildren() ) {
  85. const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
  86. const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
  87. const { row, column } = tableUtils.getCellLocation( tableCell );
  88. if ( column + colspan > width ) {
  89. const newSpan = width - column;
  90. if ( newSpan > 1 ) {
  91. writer.setAttribute( 'colspan', newSpan, tableCell );
  92. } else {
  93. writer.removeAttribute( 'colspan', tableCell );
  94. }
  95. }
  96. if ( row + rowspan > height ) {
  97. const newSpan = height - row;
  98. if ( newSpan > 1 ) {
  99. writer.setAttribute( 'rowspan', newSpan, tableCell );
  100. } else {
  101. writer.removeAttribute( 'rowspan', tableCell );
  102. }
  103. }
  104. }
  105. }
  106. }
  107. // Sets proper heading attributes to copied table.
  108. function addHeadingsToTableCopy( tableCopy, sourceTable, startRow, startColumn, writer ) {
  109. const headingRows = parseInt( sourceTable.getAttribute( 'headingRows' ) || 0 );
  110. if ( headingRows > 0 ) {
  111. const copiedRows = headingRows - startRow;
  112. writer.setAttribute( 'headingRows', copiedRows, tableCopy );
  113. }
  114. const headingColumns = parseInt( sourceTable.getAttribute( 'headingColumns' ) || 0 );
  115. if ( headingColumns > 0 ) {
  116. const copiedColumns = headingColumns - startColumn;
  117. writer.setAttribute( 'headingColumns', copiedColumns, tableCopy );
  118. }
  119. }