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