krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgCore/Export/TableEl.swift

ab6ccec56eca03d0dadf2c9332aab10c4490eb62
orgstar/Sources/OrgCore/Export/TableEl.swift history · blame · raw

53 lines · 2794 bytes

 1import Foundation
 2
 3/// A table.el table's cells, read from its drawing: each cell's text lines and the grid rows
 4/// and columns it spans, as `table-generate-source` finds them.
 5enum TableEl {
 6    struct Cell: Equatable {
 7        var row: Int
 8        var lines: [String]
 9        var colspan: Int
10        var rowspan: Int
11    }
12
13    /// The cells in reading order, or nil when the drawing isn't a well-formed table.
14    static func cells(_ text: String) -> [Cell]? {
15        let raw = text.components(separatedBy: "\n").filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
16        let indent = raw.map { $0.prefix { $0 == " " || $0 == "\t" }.count }.min() ?? 0
17        let grid = raw.map { Array($0.dropFirst(indent)) }
18        guard grid.count >= 3 else { return nil }
19        func at(_ r: Int, _ c: Int) -> Character? { r >= 0 && r < grid.count && c >= 0 && c < grid[r].count ? grid[r][c] : nil }
20        func vertical(_ r: Int, _ c: Int) -> Bool { at(r, c) == "|" || at(r, c) == "+" }
21        func horizontal(_ r: Int, _ c: Int) -> Bool { at(r, c) == "-" || at(r, c) == "+" }
22        // Grid lines: rows holding a horizontal border, columns holding a corner.
23        var rows: [Int] = []
24        var columns: Set<Int> = []
25        for (r, line) in grid.enumerated() {
26            for (c, ch) in line.enumerated() where ch == "+" {
27                columns.insert(c)
28                if horizontal(r, c + 1) || horizontal(r, c - 1) { if rows.last != r { rows.append(r) } }
29            }
30        }
31        var cells: [Cell] = []
32        for r in rows {
33            for c in grid[r].indices where grid[r][c] == "+" && at(r, c + 1) == "-" && vertical(r + 1, c) {
34                // Right edge: the next corner along the top with a border below it.
35                var x2 = c + 1
36                while x2 < grid[r].count, !(grid[r][x2] == "+" && vertical(r + 1, x2)) { x2 += 1 }
37                guard x2 < grid[r].count else { return nil }
38                // Bottom edge: the next corner down the left side with a border to its right.
39                var r2 = r + 1
40                while r2 < grid.count, !(at(r2, c) == "+" && horizontal(r2, c + 1)) { r2 += 1 }
41                guard r2 < grid.count, r2 > r + 1 else { return nil }
42                let lines = (r + 1..<r2).map { line -> String in
43                    let row = grid[line]
44                    let end = min(x2, row.count)
45                    return c + 1 < end ? String(row[(c + 1)..<end]).trimmingCharacters(in: .whitespaces) : ""
46                }
47                cells.append(Cell(row: r, lines: lines, colspan: columns.filter { $0 > c && $0 < x2 }.count + 1,
48                                  rowspan: rows.filter { $0 > r && $0 < r2 }.count + 1))
49            }
50        }
51        return cells.isEmpty ? nil : cells
52    }
53}