1
2
3
4
5 package markdown
6
7 import (
8 "bytes"
9 "strings"
10 )
11
12 type tableTrimmed string
13
14 func isTableSpace(c byte) bool {
15 return c == ' ' || c == '\t' || c == '\v' || c == '\f'
16 }
17
18 func tableTrimSpace(s string) string {
19 i := 0
20 for i < len(s) && isTableSpace(s[i]) {
21 i++
22 }
23 j := len(s)
24 for j > i && isTableSpace(s[j-1]) {
25 j--
26 }
27 return s[i:j]
28 }
29
30 func tableTrimOuter(row string) tableTrimmed {
31 row = tableTrimSpace(row)
32 if len(row) > 0 && row[0] == '|' {
33 row = row[1:]
34 }
35 if len(row) > 0 && row[len(row)-1] == '|' {
36 row = row[:len(row)-1]
37 }
38 return tableTrimmed(row)
39 }
40
41 func isTableStart(hdr1, delim1 string) bool {
42
43
44
45 col := 0
46 delim := tableTrimOuter(delim1)
47 i := 0
48 for ; ; col++ {
49 for i < len(delim) && isTableSpace(delim[i]) {
50 i++
51 }
52 if i >= len(delim) {
53 break
54 }
55 if i < len(delim) && delim[i] == ':' {
56 i++
57 }
58 if i >= len(delim) || delim[i] != '-' {
59 return false
60 }
61 i++
62 for i < len(delim) && delim[i] == '-' {
63 i++
64 }
65 if i < len(delim) && delim[i] == ':' {
66 i++
67 }
68 for i < len(delim) && isTableSpace(delim[i]) {
69 i++
70 }
71 if i < len(delim) && delim[i] == '|' {
72 i++
73 }
74 }
75
76 if strings.TrimSpace(hdr1) == "|" {
77
78
79
80
81 return false
82 }
83
84 return col == tableCount(tableTrimOuter(hdr1))
85 }
86
87 func tableCount(row tableTrimmed) int {
88 col := 1
89 prev := byte(0)
90 for i := 0; i < len(row); i++ {
91 c := row[i]
92 if c == '|' && prev != '\\' {
93 col++
94 }
95 prev = c
96 }
97 return col
98 }
99
100 type tableBuilder struct {
101 hdr tableTrimmed
102 delim tableTrimmed
103 rows []tableTrimmed
104 }
105
106 func (b *tableBuilder) start(hdr, delim string) {
107 b.hdr = tableTrimOuter(hdr)
108 b.delim = tableTrimOuter(delim)
109 }
110
111 func (b *tableBuilder) addRow(row string) {
112 b.rows = append(b.rows, tableTrimOuter(row))
113 }
114
115 type Table struct {
116 Position
117 Header []*Text
118 Align []string
119 Rows [][]*Text
120 }
121
122 func (t *Table) PrintHTML(buf *bytes.Buffer) {
123 buf.WriteString("<table>\n")
124 buf.WriteString("<thead>\n")
125 buf.WriteString("<tr>\n")
126 for i, hdr := range t.Header {
127 buf.WriteString("<th")
128 if t.Align[i] != "" {
129 buf.WriteString(" align=\"")
130 buf.WriteString(t.Align[i])
131 buf.WriteString("\"")
132 }
133 buf.WriteString(">")
134 hdr.PrintHTML(buf)
135 buf.WriteString("</th>\n")
136 }
137 buf.WriteString("</tr>\n")
138 buf.WriteString("</thead>\n")
139 if len(t.Rows) > 0 {
140 buf.WriteString("<tbody>\n")
141 for _, row := range t.Rows {
142 buf.WriteString("<tr>\n")
143 for i, cell := range row {
144 buf.WriteString("<td")
145 if i < len(t.Align) && t.Align[i] != "" {
146 buf.WriteString(" align=\"")
147 buf.WriteString(t.Align[i])
148 buf.WriteString("\"")
149 }
150 buf.WriteString(">")
151 cell.PrintHTML(buf)
152 buf.WriteString("</td>\n")
153 }
154 buf.WriteString("</tr>\n")
155 }
156 buf.WriteString("</tbody>\n")
157 }
158 buf.WriteString("</table>\n")
159 }
160
161 func (t *Table) printMarkdown(buf *bytes.Buffer, s mdState) {
162 }
163
164 func (b *tableBuilder) build(p buildState) Block {
165 pos := p.pos()
166 pos.StartLine--
167 pos.EndLine = pos.StartLine + 1 + len(b.rows)
168 t := &Table{
169 Position: pos,
170 }
171 width := tableCount(b.hdr)
172 t.Header = b.parseRow(p, b.hdr, pos.StartLine, width)
173 t.Align = b.parseAlign(b.delim, width)
174 t.Rows = make([][]*Text, len(b.rows))
175 for i, row := range b.rows {
176 t.Rows[i] = b.parseRow(p, row, pos.StartLine+2+i, width)
177 }
178 return t
179 }
180
181 func (b *tableBuilder) parseRow(p buildState, row tableTrimmed, line int, width int) []*Text {
182 out := make([]*Text, 0, width)
183 pos := Position{StartLine: line, EndLine: line}
184 start := 0
185 unesc := nop
186 for i := 0; i < len(row); i++ {
187 c := row[i]
188 if c == '\\' && i+1 < len(row) && row[i+1] == '|' {
189 unesc = tableUnescape
190 i++
191 continue
192 }
193 if c == '|' {
194 out = append(out, p.newText(pos, unesc(strings.Trim(string(row[start:i]), " \t\v\f"))))
195 if len(out) == width {
196
197 return out
198 }
199 start = i + 1
200 unesc = nop
201 }
202 }
203 out = append(out, p.newText(pos, unesc(strings.Trim(string(row[start:]), " \t\v\f"))))
204 for len(out) < width {
205
206 out = append(out, p.newText(pos, ""))
207 }
208 return out
209 }
210
211 func nop(text string) string {
212 return text
213 }
214
215 func tableUnescape(text string) string {
216 out := make([]byte, 0, len(text))
217 for i := 0; i < len(text); i++ {
218 c := text[i]
219 if c == '\\' && i+1 < len(text) && text[i+1] == '|' {
220 i++
221 c = '|'
222 }
223 out = append(out, c)
224 }
225 return string(out)
226 }
227
228 func (b *tableBuilder) parseAlign(delim tableTrimmed, n int) []string {
229 align := make([]string, 0, tableCount(delim))
230 start := 0
231 for i := 0; i < len(delim); i++ {
232 if delim[i] == '|' {
233 align = append(align, tableAlign(string(delim[start:i])))
234 start = i + 1
235 }
236 }
237 align = append(align, tableAlign(string(delim[start:])))
238 return align
239 }
240
241 func tableAlign(cell string) string {
242 cell = tableTrimSpace(cell)
243 l := cell[0] == ':'
244 r := cell[len(cell)-1] == ':'
245 switch {
246 case l && r:
247 return "center"
248 case l:
249 return "left"
250 case r:
251 return "right"
252 }
253 return ""
254 }
255
View as plain text