@@ -6,15 +6,20 @@ import std/strutils
6
6
import std/ strformat
7
7
import std/ macros
8
8
import std/ paths
9
+ import std/ enumerate
9
10
10
11
import regex
11
12
import nim_pandoc/ pd_attr_h
12
13
import nim_pandoc/ pd_inline_h
14
+ import nim_pandoc/ pd_caption_h
13
15
import nim_pandoc/ pd_citation_h
16
+ import nim_pandoc/ pd_format_h
17
+ import nim_pandoc/ pd_list_h
18
+ import nim_pandoc/ pd_table_h
14
19
import nim_pandoc/ pd_target_h
15
20
import nim_pandoc/ pd_types_h
16
- import nim_pandoc/ pd_caption_h
17
21
import nim_pandoc/ pd_block_h
22
+ import nim_pandoc/ pd_caption_h
18
23
import nim_pandoc/ pd_inline
19
24
import nim_pandoc/ pd_block
20
25
44
49
rawPath: Option [Path ]
45
50
display: Option [string ]
46
51
items: seq [string ]
52
+ Coord = tuple
53
+ row: int
54
+ col: int
55
+ Cell = tuple
56
+ start: Coord
57
+ until: Coord
58
+ content: string
59
+ attr: PDAttr
60
+ align: PDAlignment
61
+ CellManager = ref object of RootObj
62
+ t: TableRef [int , seq [bool ]]
63
+ colCount: int
47
64
48
65
func chainReplace * (s: string , replaces: openArray [ReplacePair ]): auto =
49
66
unpackVarargs (s.multiReplace, replaces.mapIt ((it.src, it.dst)))
50
67
51
68
func escapeSpaces * (s: string ): auto =
52
69
s.replace (" " , " \\ " )
53
70
71
+ func lstrip * (s: string ): auto =
72
+ s.strip (leading = true , trailing = false )
73
+
74
+ func rstrip * (s: string ): auto =
75
+ s.strip (leading = false , trailing = true )
76
+
77
+ func iterEnum * [T](arr: openArray [T]): seq [(int , T)] =
78
+ for (i, e) in enumerate (arr):
79
+ result .add ((i, e))
80
+
54
81
proc toStr * (self: PDInline ): string
55
82
proc toStr * (self: PDBlock , indent: int = 0 ): string
56
83
57
- proc attachAttr * (s: string , attr: PDAttr ): string =
84
+ func c * (row, col: int ): Coord =
85
+ (row, col)
86
+
87
+ func `+` * (self, o: Coord ): auto =
88
+ c (self.row + o.row, self.col + o.col)
89
+
90
+ func `==` * (self, o: Coord ): auto =
91
+ self.row == o.row and self.col == o.col
92
+
93
+ func filterJoin * (self: openArray [string ], sep: string = " \n " ): auto =
94
+ self.filterIt (it.len () > 0 ).join (sep)
95
+
96
+ proc attachAttr * (s: string , attr: PDAttr , global: bool = true ): string =
97
+ let tagChar = if global: " #" else : " +"
58
98
result = s
59
99
if attr.identifier.len () > 0 :
60
- defer : result = & " # id { attr.identifier} \n " & result .strip (leading = true , trailing = false )
100
+ defer : result = & " { tagChar } id { attr.identifier} \n " & result .lstrip ( )
61
101
if attr.classes.len () > 0 :
62
- defer : result = " # class " & attr.classes.mapIt (it.strip ().escapeSpaces ()).join (" " ) & " \n " & result
102
+ defer : result = tagChar & " class " & attr.classes.mapIt (it.strip ().escapeSpaces ()).join (" " ) & " \n " & result . lstrip ()
63
103
if attr.pairs.len () > 0 :
64
- defer : result = attr.pairs.mapIt (& " #{ it.key.strip ()} { it.value.strip ().escapeSpaces ()} " ).join (" \n " ) & " \n " & result
104
+ defer :
105
+ let pairs = attr.pairs.mapIt (& " { tagChar} { it.key.strip ()} { it.value.strip ().escapeSpaces ()} " ).join (" \n " )
106
+ result = pairs & " \n " & result .lstrip ()
65
107
66
108
proc toStr * (self: seq [PDBlock ], indent: int = 0 ): seq [string ] =
67
109
self.mapIt (it.toStr (indent))
@@ -111,7 +153,7 @@ func linkMaybeMergeable*(tag: string, target: string): (bool, string) =
111
153
elif tag.toLower () == noDashes.substr (1 ).toLower ():
112
154
return (true , & " # { tag} " )
113
155
elif noDashes.startsWith (re2 " #h[1-6] " ) and noDashes[2 ].isDigit ():
114
- let headings = '*' .repeat (noDashes[2 ..< 3 ].parseInt ()) & " "
156
+ let headings = '*' .repeat (noDashes[2 ..< 3 ].parseInt ()) & " "
115
157
if noDashes.substr (4 ).toLower () == tag.toLower ():
116
158
return (true , headings & tag)
117
159
else :
@@ -200,7 +242,9 @@ proc toStr*(self: PDInlineCite): string =
200
242
201
243
proc toStr * (self: PDInlineSpan ): string =
202
244
let (attr, inlines) = self.c
203
- attachAttr (& " <{ inlines.toStr ()} > " , attr)
245
+ result = inlines.toStr ()
246
+ if result .len () > 0 :
247
+ return attachAttr (& " <{ result } > " , attr)
204
248
205
249
proc toStr * (self: PDInlineMath , indent: int = 0 ): string =
206
250
let (mathType, text) = self.c
@@ -394,8 +438,121 @@ proc toStr*(self: PDBlockDiv, indent: int = 0): string =
394
438
let (attr, blocks) = self.c
395
439
blocks.toStr ().join (" " ).attachAttr (attr)
396
440
441
+ proc calcTableColumnName * (num: int ): string =
442
+ if num < 0 :
443
+ return " "
444
+ const
445
+ a = int ('A' )
446
+ n = UppercaseLetters .len ()
447
+ return calcTableColumnName (num div n - 1 ) & char (num mod n + a)
448
+
449
+ proc calcTableCellName * (coord: Coord ): string =
450
+ calcTableColumnName (coord.col) & $ (coord.row + 1 )
451
+
452
+ proc parseTableAlignment * (alignment: PDAlignment , col: int = - 1 , global: bool = false ): string =
453
+ const tableAlignmentLookup = {
454
+ " AlignLeft" : " left" ,
455
+ " AlignCenter" : " center" ,
456
+ " AlignRight" : " right" ,
457
+ }.toTable ()
458
+ if not tableAlignmentLookup.hasKey (alignment.t):
459
+ return " "
460
+ let tagChar = if global: " #" else : " +"
461
+ let alignWhat = if col < 0 : " align" else : " align.columns"
462
+ & " { tagChar} { alignWhat} { calcTableColumnName (col)} " & tableAlignmentLookup[alignment.t]
463
+
464
+ proc tableShortColSpec (specs: seq [PDColSpec ]): string =
465
+ specs.mapIt (it[0 ]).iterEnum ().mapIt (it[1 ].parseTableAlignment (it[0 ], true )).join (" \n " )
466
+
467
+ proc bootstrapCell * (self: Cell ): string =
468
+ var cellCoord = self.start.calcTableCellName ()
469
+ let content = self.content.strip ()
470
+ if not (self.start == self.until):
471
+ cellCoord.add (" -" & self.until.calcTableCellName ())
472
+ if content.contains ('\n ' ): # multi line cell
473
+ result = [" :: " & cellCoord, content, " ::" ].join (" \n " )
474
+ else : # single line cell
475
+ result = & " : { cellCoord} : { content} "
476
+ let alignText = self.align.parseTableAlignment ()
477
+ if alignText.len () > 0 :
478
+ result = alignText & " \n " & result
479
+ return result .attachAttr (self.attr)
480
+
481
+ proc parseTableCell * (self: PDCell , useCoord: Coord ): (Cell , int , int ) =
482
+ let until = useCoord + c (self[2 ] - 1 , self[3 ] - 1 )
483
+ ((useCoord, until, self[4 ].toStr ().join (" " ), self[0 ], self[1 ]), self[2 ], self[3 ])
484
+
485
+ proc setValue * (self: CellManager , coord: Coord , value: bool ) =
486
+ if coord.col >= self.colCount:
487
+ return
488
+ if not self.t.hasKey (coord.row):
489
+ self.t [coord.row] = newSeq [bool ](self.colCount)
490
+ self.t[coord.row][coord.col] = value
491
+
492
+ proc getValue * (self: CellManager , coord: Coord ): auto =
493
+ if coord.col >= self.colCount:
494
+ return false
495
+ if not self.t.hasKey (coord.row):
496
+ self.t [coord.row] = newSeq [bool ](self.colCount)
497
+ self.t[coord.row][coord.col]
498
+
499
+ proc getFirstEmpty * (self: CellManager , coord: Coord ): auto =
500
+ for i in coord.col ..< self.colCount:
501
+ if not self.getValue (c (coord.row, i)):
502
+ return i
503
+
504
+ proc occupyCells * (self: CellManager , start: Coord , useRows, useCols: int ) =
505
+ if useRows == 1 and useCols == 1 :
506
+ self.setValue (start, true )
507
+ return
508
+ for i in 0 ..< useRows:
509
+ for j in 0 ..< useCols:
510
+ self.setValue (start + c (i, j), true )
511
+
512
+ proc fillTable * (rows: seq [PDRow ], colCount: int , startRow: int = 0 ): seq [Cell ] =
513
+ var cellManager = CellManager (t: newTable [int , seq [bool ]](), colCount: colCount)
514
+ let rowOffset = c (startRow, 0 )
515
+ for (i, row) in enumerate (rows):
516
+ for (j, col) in enumerate (row[1 ]):
517
+ let rowNum = i + startRow
518
+ let coord = c (rowNum, cellManager.getFirstEmpty (c (rowNum, j)))
519
+ let (cell, useRows, useCols) = col.parseTableCell (coord)
520
+ cellManager.occupyCells (coord, useRows, useCols)
521
+ if cell.content.len () > 0 :
522
+ result .add (cell)
523
+
524
+ func shiftSlice * [T](self: Slice [T], shift: T): auto =
525
+ (self.a + shift) .. (self.b + shift)
526
+
527
+ proc parseTableBody * (self: PDTableBody , colCount: int , startRow: int ): string =
528
+ let bodySlice = (0 ..< self[3 ].len ()).shiftSlice (startRow + self[2 ].len ())
529
+ let head = fillTable (self[2 ], colCount, startRow).map (bootstrapCell).join (" \n " )
530
+ let body = fillTable (self[3 ], colCount, bodySlice.a).map (bootstrapCell).join (" \n " )
531
+ if body.len () > 0 :
532
+ result .add (& " #headColumns { bodySlice.a + 1 } " )
533
+ if bodySlice.b > bodySlice.a:
534
+ result .add (& " -{ bodySlice.b + 1 } " )
535
+ result .add (& " { self[1 ]} \n " )
536
+ result .add ([head, body].filterJoin (" \n +hline\n " ))
537
+
397
538
proc toStr * (self: PDBlockTable , indent: int = 0 ): string =
398
- unreachable (& " Table: { self.t= } , { self.c= } " )
539
+ let (attr, caption, col, head, body, foot) = self.c
540
+ let captionString = caption.toStr (indent)
541
+ let colCount = col.len ()
542
+ var rowCount = 0
543
+ defer :
544
+ if captionString.len () > 0 :
545
+ result = [" |caption " & captionString, result .strip (), " |end" ].join (" \n " )
546
+ var accumulator = newSeq [string ]()
547
+ var headCounts = newSeq [int ]()
548
+ result .add (tableShortColSpec (col))
549
+ accumulator.add (fillTable (head[1 ], colCount, rowCount).map (bootstrapCell).join (" \n " ))
550
+ rowCount += head[1 ].len ()
551
+ for tbody in body:
552
+ accumulator.add (tbody.parseTableBody (colCount, rowCount))
553
+ rowCount += tbody[2 ].len () + tbody[3 ].len ()
554
+ defer :
555
+ result = result & " \n " & accumulator.join (" \n +hline2\n " )
399
556
400
557
proc toStr * (self: PDBlock , indent: int = 0 ): string =
401
558
case self.t:
0 commit comments