@@ -51,8 +51,8 @@ const ELEMENT_TAGS: IHtmlToJsonElementTags = {
5151 THEAD : ( el : HTMLElement ) => ( { type : 'thead' , attrs : { } } ) ,
5252 TBODY : ( el : HTMLElement ) => ( { type : 'tbody' , attrs : { } } ) ,
5353 TR : ( el : HTMLElement ) => ( { type : 'tr' , attrs : { } } ) ,
54- TD : ( el : HTMLElement ) => ( { type : 'td' , attrs : { } } ) ,
55- TH : ( el : HTMLElement ) => ( { type : 'th' , attrs : { } } ) ,
54+ TD : ( el : HTMLElement ) => ( { type : 'td' , attrs : { ... spanningAttrs ( el ) } } ) ,
55+ TH : ( el : HTMLElement ) => ( { type : 'th' , attrs : { ... spanningAttrs ( el ) } } ) ,
5656 // FIGURE: (el: HTMLElement) => ({ type: 'reference', attrs: { default: true, "display-type": "display", "type": "asset" } }),
5757
5858 FIGURE : ( el : HTMLElement ) => {
@@ -220,6 +220,28 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
220220 } else if ( el . nodeName === 'COLGROUP' ) {
221221 return null
222222 }
223+ else if ( el . nodeName === "TABLE" ) {
224+ const tbody = el . querySelector ( 'tbody' )
225+ const thead = el . querySelector ( 'thead' )
226+
227+ if ( ! tbody && ! thead ) {
228+ el . innerHTML += "<tbody></tbody>"
229+ }
230+ }
231+ else if ( [ 'TBODY' , 'THEAD' ] . includes ( el . nodeName ) ) {
232+ const row = el . querySelector ( 'tr' )
233+ if ( ! row ) {
234+ el . innerHTML += "<tr></tr>"
235+ }
236+ }
237+ else if ( el . nodeName === 'TR' ) {
238+ const cell = el . querySelector ( 'th, td' )
239+ if ( ! cell ) {
240+ const cellType = el . parentElement . nodeName === 'THEAD' ? 'th' : 'td'
241+ el . innerHTML += `<${ cellType } ></${ cellType } >`
242+
243+ }
244+ }
223245 const { nodeName } = el
224246 let parent = el
225247 if ( el . nodeName === "BODY" ) {
@@ -628,46 +650,111 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
628650 } )
629651 }
630652 if ( nodeName === 'TABLE' ) {
631- let row = 0
653+ const row = el . querySelectorAll ( 'TR' ) . length
632654 let table_child = [ 'THEAD' , 'TBODY' ]
633655 let cell_type = [ 'TH' , 'TD' ]
634656 let col = 0
635- Array . from ( el . childNodes ) . forEach ( ( child : any ) => {
636- if ( table_child . includes ( child . nodeName ) ) {
637- row += child . childNodes . length
638- }
639- } )
640- let rowElement = el . getElementsByTagName ( 'TR' ) [ 0 ]
641- if ( rowElement )
642- Array . from ( rowElement . childNodes ) . forEach ( ( child : any ) => {
643- if ( cell_type . includes ( child . nodeName ) ) {
644- col += 1
645- }
646- } )
657+
658+ const colElementLength = el . getElementsByTagName ( 'COLGROUP' ) [ 0 ] ?. children ?. length ?? 0
659+ col = Math . max ( ...Array . from ( el . getElementsByTagName ( 'TR' ) ) . map ( ( row : any ) => row . children . length ) , colElementLength )
647660 let colWidths : Array < any > = Array . from ( { length : col } ) . fill ( 250 )
648- if ( el ?. childNodes ?. [ 0 ] ?. nodeName === 'COLGROUP' ) {
649- let colGroupWidth : Array < any > = [ ]
650- let totalWidth = parseFloat ( el . childNodes [ 0 ] . getAttribute ( 'data-width' ) ) || col * 250
651- Array . from ( el . childNodes [ 0 ] . childNodes ) . forEach ( ( child : any ) => {
652- let width = child ?. style ?. width || '250px'
653- if ( width . slice ( width . length - 1 ) === '%' ) {
654- colGroupWidth . push ( ( parseFloat ( width . slice ( 0 , width . length - 1 ) ) * totalWidth ) / 100 )
655- } else if ( width . slice ( width . length - 2 ) === 'px' ) {
656- colGroupWidth . push ( parseFloat ( width . slice ( 0 , width . length - 2 ) ) )
661+
662+ Array . from ( el . childNodes ) . forEach ( ( child : any ) => {
663+ if ( child ?. nodeName === 'COLGROUP' ) {
664+ let colGroupWidth = Array < number > ( col ) . fill ( 250 )
665+ let totalWidth = parseFloat ( child . getAttribute ( 'data-width' ) ) || col * 250
666+ Array . from ( child . children ) . forEach ( ( child : any , index ) => {
667+ if ( child ?. nodeName === 'COL' ) {
668+ let width = child ?. style ?. width ?? '250px'
669+ if ( width . substr ( - 1 ) === '%' ) {
670+ colGroupWidth [ index ] = ( parseFloat ( width . slice ( 0 , width . length - 1 ) ) * totalWidth ) / 100
671+ } else if ( width . substr ( - 2 ) === 'px' ) {
672+ colGroupWidth [ index ] = parseFloat ( width . slice ( 0 , width . length - 2 ) )
673+ }
657674 }
658675 } )
659676 colWidths = colGroupWidth
660677 }
678+ } )
679+ let tableHead : any
680+ let tableBody : any
681+
682+ children . forEach ( ( tableChild : any ) => {
683+ if ( tableChild ?. type === 'thead' ) {
684+ tableHead = tableChild
685+ return
686+ }
687+ if ( tableChild ?. type === 'tbody' ) {
688+ tableBody = tableChild
689+ return
690+ }
691+ } ) ;
692+
693+ let disabledCols = [ ...tableHead ?. attrs ?. disabledCols ?? [ ] , ...tableBody ?. attrs ?. disabledCols ?? [ ] ]
694+ delete tableHead ?. attrs ?. disabledCols
695+ delete tableBody ?. attrs ?. disabledCols
696+
697+ const tableAttrs = {
698+ ...elementAttrs . attrs ,
699+ rows : row ,
700+ cols : col ,
701+ colWidths : colWidths ,
702+ }
703+
704+ if ( ! isEmpty ( disabledCols ) ) {
705+ tableAttrs [ 'disabledCols' ] = Array . from ( new Set ( disabledCols ) )
706+ }
707+
661708 elementAttrs = {
662709 ...elementAttrs ,
663- attrs : {
664- ...elementAttrs . attrs ,
665- rows : row ,
666- cols : col ,
667- colWidths : colWidths
668- }
710+ attrs : tableAttrs
669711 }
670712 }
713+ if ( [ "THEAD" , "TBODY" ] . includes ( nodeName ) ) {
714+ const rows = children as any [ ]
715+ const disabledCols = rows . flatMap ( row => {
716+ const { disabledCols } = row . attrs
717+ delete row [ 'attrs' ] [ 'disabledCols' ]
718+ return disabledCols ?? [ ]
719+ } )
720+ elementAttrs . attrs [ 'disabledCols' ] = disabledCols
721+ }
722+ if ( nodeName === "TBODY" ) {
723+
724+ const rows = children
725+
726+ addVoidCellsAndApplyAttributes ( rows )
727+
728+ children = getTbodyChildren ( rows )
729+ }
730+
731+ if ( nodeName === "TR" ) {
732+
733+ const cells = children . filter ( ( child :any ) => [ 'th' , 'td' ] . includes ( child . type ) ) as any [ ]
734+
735+
736+ const disabledCols = cells . flatMap ( ( cell , cellIndex ) => {
737+ let { colSpan } = cell . attrs
738+ if ( ! colSpan ) return [ ]
739+ colSpan = parseInt ( colSpan )
740+ return Array ( colSpan ) . fill ( 0 ) . map ( ( _ , i ) => cellIndex + i )
741+ } )
742+
743+ if ( disabledCols ?. length )
744+ elementAttrs . attrs [ 'disabledCols' ] = disabledCols
745+
746+
747+ }
748+ if ( [ 'TD' , 'TH' ] . includes ( nodeName ) ) {
749+ const { colSpan = 1 , rowSpan } = elementAttrs ?. [ 'attrs' ]
750+
751+ return [
752+ jsx ( 'element' , elementAttrs , children ) ,
753+ ...Array ( colSpan - 1 )
754+ . fill ( 0 )
755+ . map ( ( _ ) => emptyCell ( nodeName . toLowerCase ( ) , rowSpan ? { inducedRowSpan : rowSpan } : { } ) )
756+ ]
757+ }
671758 if ( nodeName === 'P' ) {
672759 if (
673760 elementAttrs ?. attrs ?. [ "redactor-attributes" ] ?. [ 'data-checked' ] &&
@@ -824,3 +911,84 @@ export const getNestedValueIfAvailable = (value: string) => {
824911 return value ;
825912 }
826913} ;
914+
915+
916+ const spanningAttrs = ( el : HTMLElement ) => {
917+ const attrs = { }
918+ const rowSpan = parseInt ( el . getAttribute ( 'rowspan' ) ?? '1' )
919+ const colSpan = parseInt ( el . getAttribute ( 'colspan' ) ?? '1' )
920+ if ( rowSpan > 1 ) attrs [ 'rowSpan' ] = rowSpan
921+ if ( colSpan > 1 ) attrs [ 'colSpan' ] = colSpan
922+
923+ return attrs
924+ }
925+ const emptyCell = ( cellType : string , attrs = { } ) => {
926+ return jsx ( 'element' , { type : cellType , attrs : { void : true , ...attrs } } , [ { text : '' } ] )
927+ }
928+
929+ const addVoidCellsAndApplyAttributes = ( rows : any [ ] ) => {
930+ rows . forEach ( ( row , currIndex ) => {
931+ const cells = row . children as any [ ]
932+
933+ cells . forEach ( ( cell , cellIndex ) => {
934+ if ( ! cell || ! cell . attrs ) return
935+
936+ const { rowSpan, inducedRowSpan } = cell . attrs
937+
938+ let span = rowSpan ?? inducedRowSpan ?? 0
939+
940+ if ( ! span || span < 2 ) return
941+ const nextRow = rows [ currIndex + 1 ]
942+ if ( ! nextRow ) {
943+ delete cell ?. attrs ?. inducedRowSpan
944+ return
945+ }
946+
947+ // set inducedRowSpan on cell in row at cellIndex
948+ span --
949+ nextRow ?. children ?. splice ( cellIndex , 0 ,
950+ emptyCell ( 'td' ,
951+ ( span > 1 ) ? { inducedRowSpan : span } : { }
952+ ) )
953+
954+ // Include next row in trgrp
955+ nextRow [ 'attrs' ] [ 'included' ] = true
956+
957+ // Make a new trgrp
958+ if ( rowSpan ) {
959+ row [ 'attrs' ] [ 'origin' ] = true
960+ }
961+
962+ delete cell ?. [ 'attrs' ] ?. [ 'inducedRowSpan' ]
963+
964+ } )
965+ } )
966+ }
967+
968+
969+ function getTbodyChildren ( rows : any [ ] ) {
970+ const newTbodyChildren = rows . reduce ( ( tBodyChildren , row , rowIndex ) => {
971+
972+ const { included, origin } = row . attrs
973+ const l = tBodyChildren . length
974+
975+ if ( included || origin ) {
976+
977+ if ( origin && ! included ) {
978+ tBodyChildren . push ( jsx ( 'element' , { type : 'trgrp' } , row ) )
979+ }
980+ if ( included ) {
981+ tBodyChildren [ l - 1 ] . children . push ( row )
982+
983+ }
984+ delete row [ 'attrs' ] [ 'included' ]
985+ delete row [ 'attrs' ] [ 'origin' ]
986+ return tBodyChildren
987+ }
988+
989+ tBodyChildren . push ( row )
990+ return tBodyChildren
991+
992+ } , [ ] )
993+ return newTbodyChildren
994+ }
0 commit comments