@@ -51,8 +51,8 @@ const ELEMENT_TAGS: IHtmlToJsonElementTags = {
51
51
THEAD : ( el : HTMLElement ) => ( { type : 'thead' , attrs : { } } ) ,
52
52
TBODY : ( el : HTMLElement ) => ( { type : 'tbody' , attrs : { } } ) ,
53
53
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 ) } } ) ,
56
56
// FIGURE: (el: HTMLElement) => ({ type: 'reference', attrs: { default: true, "display-type": "display", "type": "asset" } }),
57
57
58
58
FIGURE : ( el : HTMLElement ) => {
@@ -208,6 +208,28 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
208
208
} else if ( el . nodeName === 'COLGROUP' ) {
209
209
return null
210
210
}
211
+ else if ( el . nodeName === "TABLE" ) {
212
+ const tbody = el . querySelector ( 'tbody' )
213
+ const thead = el . querySelector ( 'thead' )
214
+
215
+ if ( ! tbody && ! thead ) {
216
+ el . innerHTML += "<tbody></tbody>"
217
+ }
218
+ }
219
+ else if ( [ 'TBODY' , 'THEAD' ] . includes ( el . nodeName ) ) {
220
+ const row = el . querySelector ( 'tr' )
221
+ if ( ! row ) {
222
+ el . innerHTML += "<tr></tr>"
223
+ }
224
+ }
225
+ else if ( el . nodeName === 'TR' ) {
226
+ const cell = el . querySelector ( 'th, td' )
227
+ if ( ! cell ) {
228
+ const cellType = el . parentElement . nodeName === 'THEAD' ? 'th' : 'td'
229
+ el . innerHTML += `<${ cellType } ></${ cellType } >`
230
+
231
+ }
232
+ }
211
233
const { nodeName } = el
212
234
let parent = el
213
235
if ( el . nodeName === "BODY" ) {
@@ -613,46 +635,111 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
613
635
} )
614
636
}
615
637
if ( nodeName === 'TABLE' ) {
616
- let row = 0
638
+ const row = el . querySelectorAll ( 'TR' ) . length
617
639
let table_child = [ 'THEAD' , 'TBODY' ]
618
640
let cell_type = [ 'TH' , 'TD' ]
619
641
let col = 0
620
- Array . from ( el . childNodes ) . forEach ( ( child : any ) => {
621
- if ( table_child . includes ( child . nodeName ) ) {
622
- row += child . childNodes . length
623
- }
624
- } )
625
- let rowElement = el . getElementsByTagName ( 'TR' ) [ 0 ]
626
- if ( rowElement )
627
- Array . from ( rowElement . childNodes ) . forEach ( ( child : any ) => {
628
- if ( cell_type . includes ( child . nodeName ) ) {
629
- col += 1
630
- }
631
- } )
642
+
643
+ const colElementLength = el . getElementsByTagName ( 'COLGROUP' ) [ 0 ] ?. children ?. length ?? 0
644
+ col = Math . max ( ...Array . from ( el . getElementsByTagName ( 'TR' ) ) . map ( ( row : any ) => row . children . length ) , colElementLength )
632
645
let colWidths : Array < any > = Array . from ( { length : col } ) . fill ( 250 )
633
- if ( el ?. childNodes ?. [ 0 ] ?. nodeName === 'COLGROUP' ) {
634
- let colGroupWidth : Array < any > = [ ]
635
- let totalWidth = parseFloat ( el . childNodes [ 0 ] . getAttribute ( 'data-width' ) ) || col * 250
636
- Array . from ( el . childNodes [ 0 ] . childNodes ) . forEach ( ( child : any ) => {
637
- let width = child ?. style ?. width || '250px'
638
- if ( width . slice ( width . length - 1 ) === '%' ) {
639
- colGroupWidth . push ( ( parseFloat ( width . slice ( 0 , width . length - 1 ) ) * totalWidth ) / 100 )
640
- } else if ( width . slice ( width . length - 2 ) === 'px' ) {
641
- colGroupWidth . push ( parseFloat ( width . slice ( 0 , width . length - 2 ) ) )
646
+
647
+ Array . from ( el . childNodes ) . forEach ( ( child : any ) => {
648
+ if ( child ?. nodeName === 'COLGROUP' ) {
649
+ let colGroupWidth = Array < number > ( col ) . fill ( 250 )
650
+ let totalWidth = parseFloat ( child . getAttribute ( 'data-width' ) ) || col * 250
651
+ Array . from ( child . children ) . forEach ( ( child : any , index ) => {
652
+ if ( child ?. nodeName === 'COL' ) {
653
+ let width = child ?. style ?. width ?? '250px'
654
+ if ( width . substr ( - 1 ) === '%' ) {
655
+ colGroupWidth [ index ] = ( parseFloat ( width . slice ( 0 , width . length - 1 ) ) * totalWidth ) / 100
656
+ } else if ( width . substr ( - 2 ) === 'px' ) {
657
+ colGroupWidth [ index ] = parseFloat ( width . slice ( 0 , width . length - 2 ) )
658
+ }
642
659
}
643
660
} )
644
661
colWidths = colGroupWidth
645
662
}
663
+ } )
664
+ let tableHead : any
665
+ let tableBody : any
666
+
667
+ children . forEach ( ( tableChild : any ) => {
668
+ if ( tableChild ?. type === 'thead' ) {
669
+ tableHead = tableChild
670
+ return
671
+ }
672
+ if ( tableChild ?. type === 'tbody' ) {
673
+ tableBody = tableChild
674
+ return
675
+ }
676
+ } ) ;
677
+
678
+ let disabledCols = [ ...tableHead ?. attrs ?. disabledCols ?? [ ] , ...tableBody ?. attrs ?. disabledCols ?? [ ] ]
679
+ delete tableHead ?. attrs ?. disabledCols
680
+ delete tableBody ?. attrs ?. disabledCols
681
+
682
+ const tableAttrs = {
683
+ ...elementAttrs . attrs ,
684
+ rows : row ,
685
+ cols : col ,
686
+ colWidths : colWidths ,
687
+ }
688
+
689
+ if ( ! isEmpty ( disabledCols ) ) {
690
+ tableAttrs [ 'disabledCols' ] = Array . from ( new Set ( disabledCols ) )
691
+ }
692
+
646
693
elementAttrs = {
647
694
...elementAttrs ,
648
- attrs : {
649
- ...elementAttrs . attrs ,
650
- rows : row ,
651
- cols : col ,
652
- colWidths : colWidths
653
- }
695
+ attrs : tableAttrs
654
696
}
655
697
}
698
+ if ( [ "THEAD" , "TBODY" ] . includes ( nodeName ) ) {
699
+ const rows = children as any [ ]
700
+ const disabledCols = rows . flatMap ( row => {
701
+ const { disabledCols } = row . attrs
702
+ delete row [ 'attrs' ] [ 'disabledCols' ]
703
+ return disabledCols ?? [ ]
704
+ } )
705
+ elementAttrs . attrs [ 'disabledCols' ] = disabledCols
706
+ }
707
+ if ( nodeName === "TBODY" ) {
708
+
709
+ const rows = children
710
+
711
+ addVoidCellsAndApplyAttributes ( rows )
712
+
713
+ children = getTbodyChildren ( rows )
714
+ }
715
+
716
+ if ( nodeName === "TR" ) {
717
+
718
+ const cells = children . filter ( ( child :any ) => [ 'th' , 'td' ] . includes ( child . type ) ) as any [ ]
719
+
720
+
721
+ const disabledCols = cells . flatMap ( ( cell , cellIndex ) => {
722
+ let { colSpan } = cell . attrs
723
+ if ( ! colSpan ) return [ ]
724
+ colSpan = parseInt ( colSpan )
725
+ return Array ( colSpan ) . fill ( 0 ) . map ( ( _ , i ) => cellIndex + i )
726
+ } )
727
+
728
+ if ( disabledCols ?. length )
729
+ elementAttrs . attrs [ 'disabledCols' ] = disabledCols
730
+
731
+
732
+ }
733
+ if ( [ 'TD' , 'TH' ] . includes ( nodeName ) ) {
734
+ const { colSpan = 1 , rowSpan } = elementAttrs ?. [ 'attrs' ]
735
+
736
+ return [
737
+ jsx ( 'element' , elementAttrs , children ) ,
738
+ ...Array ( colSpan - 1 )
739
+ . fill ( 0 )
740
+ . map ( ( _ ) => emptyCell ( nodeName . toLowerCase ( ) , rowSpan ? { inducedRowSpan : rowSpan } : { } ) )
741
+ ]
742
+ }
656
743
if ( nodeName === 'P' ) {
657
744
if (
658
745
elementAttrs ?. attrs ?. [ "redactor-attributes" ] ?. [ 'data-checked' ] &&
@@ -809,3 +896,84 @@ export const getNestedValueIfAvailable = (value: string) => {
809
896
return value ;
810
897
}
811
898
} ;
899
+
900
+
901
+ const spanningAttrs = ( el : HTMLElement ) => {
902
+ const attrs = { }
903
+ const rowSpan = parseInt ( el . getAttribute ( 'rowspan' ) ?? '1' )
904
+ const colSpan = parseInt ( el . getAttribute ( 'colspan' ) ?? '1' )
905
+ if ( rowSpan > 1 ) attrs [ 'rowSpan' ] = rowSpan
906
+ if ( colSpan > 1 ) attrs [ 'colSpan' ] = colSpan
907
+
908
+ return attrs
909
+ }
910
+ const emptyCell = ( cellType : string , attrs = { } ) => {
911
+ return jsx ( 'element' , { type : cellType , attrs : { void : true , ...attrs } } , [ { text : '' } ] )
912
+ }
913
+
914
+ const addVoidCellsAndApplyAttributes = ( rows : any [ ] ) => {
915
+ rows . forEach ( ( row , currIndex ) => {
916
+ const cells = row . children as any [ ]
917
+
918
+ cells . forEach ( ( cell , cellIndex ) => {
919
+ if ( ! cell || ! cell . attrs ) return
920
+
921
+ const { rowSpan, inducedRowSpan } = cell . attrs
922
+
923
+ let span = rowSpan ?? inducedRowSpan ?? 0
924
+
925
+ if ( ! span || span < 2 ) return
926
+ const nextRow = rows [ currIndex + 1 ]
927
+ if ( ! nextRow ) {
928
+ delete cell ?. attrs ?. inducedRowSpan
929
+ return
930
+ }
931
+
932
+ // set inducedRowSpan on cell in row at cellIndex
933
+ span --
934
+ nextRow ?. children ?. splice ( cellIndex , 0 ,
935
+ emptyCell ( 'td' ,
936
+ ( span > 1 ) ? { inducedRowSpan : span } : { }
937
+ ) )
938
+
939
+ // Include next row in trgrp
940
+ nextRow [ 'attrs' ] [ 'included' ] = true
941
+
942
+ // Make a new trgrp
943
+ if ( rowSpan ) {
944
+ row [ 'attrs' ] [ 'origin' ] = true
945
+ }
946
+
947
+ delete cell ?. [ 'attrs' ] ?. [ 'inducedRowSpan' ]
948
+
949
+ } )
950
+ } )
951
+ }
952
+
953
+
954
+ function getTbodyChildren ( rows : any [ ] ) {
955
+ const newTbodyChildren = rows . reduce ( ( tBodyChildren , row , rowIndex ) => {
956
+
957
+ const { included, origin } = row . attrs
958
+ const l = tBodyChildren . length
959
+
960
+ if ( included || origin ) {
961
+
962
+ if ( origin && ! included ) {
963
+ tBodyChildren . push ( jsx ( 'element' , { type : 'trgrp' } , row ) )
964
+ }
965
+ if ( included ) {
966
+ tBodyChildren [ l - 1 ] . children . push ( row )
967
+
968
+ }
969
+ delete row [ 'attrs' ] [ 'included' ]
970
+ delete row [ 'attrs' ] [ 'origin' ]
971
+ return tBodyChildren
972
+ }
973
+
974
+ tBodyChildren . push ( row )
975
+ return tBodyChildren
976
+
977
+ } , [ ] )
978
+ return newTbodyChildren
979
+ }
0 commit comments