1package org
2
3import (
4	"regexp"
5	"strconv"
6	"strings"
7	"unicode/utf8"
8)
9
10type Table struct {
11	Rows             []Row
12	ColumnInfos      []ColumnInfo
13	SeparatorIndices []int
14}
15
16type Row struct {
17	Columns   []Column
18	IsSpecial bool
19}
20
21type Column struct {
22	Children []Node
23	*ColumnInfo
24}
25
26type ColumnInfo struct {
27	Align      string
28	Len        int
29	DisplayLen int
30}
31
32var tableSeparatorRegexp = regexp.MustCompile(`^(\s*)(\|[+-|]*)\s*$`)
33var tableRowRegexp = regexp.MustCompile(`^(\s*)(\|.*)`)
34
35var columnAlignAndLengthRegexp = regexp.MustCompile(`^<(l|c|r)?(\d+)?>$`)
36
37func lexTable(line string) (token, bool) {
38	if m := tableSeparatorRegexp.FindStringSubmatch(line); m != nil {
39		return token{"tableSeparator", len(m[1]), m[2], m}, true
40	} else if m := tableRowRegexp.FindStringSubmatch(line); m != nil {
41		return token{"tableRow", len(m[1]), m[2], m}, true
42	}
43	return nilToken, false
44}
45
46func (d *Document) parseTable(i int, parentStop stopFn) (int, Node) {
47	rawRows, separatorIndices, start := [][]string{}, []int{}, i
48	for ; !parentStop(d, i); i++ {
49		if t := d.tokens[i]; t.kind == "tableRow" {
50			rawRow := strings.FieldsFunc(d.tokens[i].content, func(r rune) bool { return r == '|' })
51			for i := range rawRow {
52				rawRow[i] = strings.TrimSpace(rawRow[i])
53			}
54			rawRows = append(rawRows, rawRow)
55		} else if t.kind == "tableSeparator" {
56			separatorIndices = append(separatorIndices, i-start)
57			rawRows = append(rawRows, nil)
58		} else {
59			break
60		}
61	}
62
63	table := Table{nil, getColumnInfos(rawRows), separatorIndices}
64	for _, rawColumns := range rawRows {
65		row := Row{nil, isSpecialRow(rawColumns)}
66		if len(rawColumns) != 0 {
67			for i := range table.ColumnInfos {
68				column := Column{nil, &table.ColumnInfos[i]}
69				if i < len(rawColumns) {
70					column.Children = d.parseInline(rawColumns[i])
71				}
72				row.Columns = append(row.Columns, column)
73			}
74		}
75		table.Rows = append(table.Rows, row)
76	}
77	return i - start, table
78}
79
80func getColumnInfos(rows [][]string) []ColumnInfo {
81	columnCount := 0
82	for _, columns := range rows {
83		if n := len(columns); n > columnCount {
84			columnCount = n
85		}
86	}
87
88	columnInfos := make([]ColumnInfo, columnCount)
89	for i := 0; i < columnCount; i++ {
90		countNumeric, countNonNumeric := 0, 0
91		for _, columns := range rows {
92			if i >= len(columns) {
93				continue
94			}
95
96			if n := utf8.RuneCountInString(columns[i]); n > columnInfos[i].Len {
97				columnInfos[i].Len = n
98			}
99
100			if m := columnAlignAndLengthRegexp.FindStringSubmatch(columns[i]); m != nil && isSpecialRow(columns) {
101				switch m[1] {
102				case "l":
103					columnInfos[i].Align = "left"
104				case "c":
105					columnInfos[i].Align = "center"
106				case "r":
107					columnInfos[i].Align = "right"
108				}
109				if m[2] != "" {
110					l, _ := strconv.Atoi(m[2])
111					columnInfos[i].DisplayLen = l
112				}
113			} else if _, err := strconv.ParseFloat(columns[i], 32); err == nil {
114				countNumeric++
115			} else if strings.TrimSpace(columns[i]) != "" {
116				countNonNumeric++
117			}
118		}
119
120		if columnInfos[i].Align == "" && countNumeric >= countNonNumeric {
121			columnInfos[i].Align = "right"
122		}
123	}
124	return columnInfos
125}
126
127func isSpecialRow(rawColumns []string) bool {
128	isAlignRow := true
129	for _, rawColumn := range rawColumns {
130		if !columnAlignAndLengthRegexp.MatchString(rawColumn) && rawColumn != "" {
131			isAlignRow = false
132		}
133	}
134	return isAlignRow
135}
136
137func (n Table) String() string { return orgWriter.WriteNodesAsString(n) }
138