Skip to content

Commit d14b639

Browse files
authored
feat(PDTable): implement PDTable parser (#25)
* feat(pd2norg): implement remaining features except for tables * fix(pd2norg): type fixes * fix(nimble): upgrade dependency library versions * feat(PDTable): implement PDTable parser
1 parent c8eca25 commit d14b639

File tree

3 files changed

+169
-12
lines changed

3 files changed

+169
-12
lines changed

minorg.nimble

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ bin = @["minorg"]
1111
# Dependencies
1212

1313
requires "nim >= 2.0.0"
14-
requires "nim_pandoc >= 3.0.2"
14+
requires "nim_pandoc >= 3.0.5"
1515
requires "cligen >= 1.6.16"
1616
requires "regex >= 0.23.0"

nimble.lock

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
"version": 2,
33
"packages": {
44
"nim_pandoc": {
5-
"version": "3.0.2",
6-
"vcsRevision": "8392b35b608215168a6f0477a159fb316143b5ff",
5+
"version": "3.0.5",
6+
"vcsRevision": "e0c7f2c89ffaa596aee0d08bc7fb61e16e585c2d",
77
"url": "https://github.com/pysan3/nim_pandoc",
88
"downloadMethod": "git",
99
"dependencies": [],
1010
"checksums": {
11-
"sha1": "d301797da2041fff8de3e98c8b0cf95ba30ca05d"
11+
"sha1": "e72c1140fda593d85014c098e32bcdaadb9d8b90"
1212
}
1313
},
1414
"unicodedb": {

src/pd2norg.nim

+165-8
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ import std/strutils
66
import std/strformat
77
import std/macros
88
import std/paths
9+
import std/enumerate
910

1011
import regex
1112
import nim_pandoc/pd_attr_h
1213
import nim_pandoc/pd_inline_h
14+
import nim_pandoc/pd_caption_h
1315
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
1419
import nim_pandoc/pd_target_h
1520
import nim_pandoc/pd_types_h
16-
import nim_pandoc/pd_caption_h
1721
import nim_pandoc/pd_block_h
22+
import nim_pandoc/pd_caption_h
1823
import nim_pandoc/pd_inline
1924
import nim_pandoc/pd_block
2025

@@ -44,24 +49,61 @@ type
4449
rawPath: Option[Path]
4550
display: Option[string]
4651
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
4764

4865
func chainReplace*(s: string, replaces: openArray[ReplacePair]): auto =
4966
unpackVarargs(s.multiReplace, replaces.mapIt((it.src, it.dst)))
5067

5168
func escapeSpaces*(s: string): auto =
5269
s.replace(" ", "\\ ")
5370

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+
5481
proc toStr*(self: PDInline): string
5582
proc toStr*(self: PDBlock, indent: int = 0): string
5683

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: "+"
5898
result = s
5999
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()
61101
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()
63103
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()
65107

66108
proc toStr*(self: seq[PDBlock], indent: int = 0): seq[string] =
67109
self.mapIt(it.toStr(indent))
@@ -111,7 +153,7 @@ func linkMaybeMergeable*(tag: string, target: string): (bool, string) =
111153
elif tag.toLower() == noDashes.substr(1).toLower():
112154
return (true, &"# {tag}")
113155
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()) & " "
115157
if noDashes.substr(4).toLower() == tag.toLower():
116158
return (true, headings & tag)
117159
else:
@@ -200,7 +242,9 @@ proc toStr*(self: PDInlineCite): string =
200242

201243
proc toStr*(self: PDInlineSpan): string =
202244
let (attr, inlines) = self.c
203-
attachAttr(&"<{inlines.toStr()}>", attr)
245+
result = inlines.toStr()
246+
if result.len() > 0:
247+
return attachAttr(&"<{result}>", attr)
204248

205249
proc toStr*(self: PDInlineMath, indent: int = 0): string =
206250
let (mathType, text) = self.c
@@ -394,8 +438,121 @@ proc toStr*(self: PDBlockDiv, indent: int = 0): string =
394438
let (attr, blocks) = self.c
395439
blocks.toStr().join("").attachAttr(attr)
396440

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+
397538
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")
399556

400557
proc toStr*(self: PDBlock, indent: int = 0): string =
401558
case self.t:

0 commit comments

Comments
 (0)