// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"regexp"
"strings"
)
func getLine(s *StateBlock, line int) string {
pos := s.BMarks[line] + s.BlkIndent
max := s.EMarks[line]
if pos >= max {
return ""
}
return s.Src[pos:max]
}
func escapedSplit(s string) (result []string) {
pos := 0
escapes := 0
lastPos := 0
backTicked := false
lastBackTick := 0
for pos < len(s) {
ch := s[pos]
if ch == '`' {
if backTicked {
backTicked = false
lastBackTick = pos
} else if escapes%2 == 0 {
backTicked = true
lastBackTick = pos
}
} else if ch == '|' && (escapes%2 == 0) && !backTicked {
result = append(result, s[lastPos:pos])
lastPos = pos + 1
}
if ch == '\\' {
escapes++
} else {
escapes = 0
}
pos++
if pos == len(s) && backTicked {
backTicked = false
pos = lastBackTick + 1
}
}
return append(result, s[lastPos:])
}
var rColumn = regexp.MustCompile("^:?-+:?$")
func ruleTable(s *StateBlock, startLine, endLine int, silent bool) bool {
if !s.Md.Tables {
return false
}
if startLine+2 > endLine {
return false
}
nextLine := startLine + 1
if s.SCount[nextLine] < s.BlkIndent {
return false
}
if s.SCount[nextLine]-s.BlkIndent >= 4 {
return false
}
pos := s.BMarks[nextLine] + s.TShift[nextLine]
if pos >= s.EMarks[nextLine] {
return false
}
src := s.Src
ch := src[pos]
pos++
if ch != '|' && ch != '-' && ch != ':' {
return false
}
for pos < s.EMarks[nextLine] {
ch = src[pos]
if ch != '|' && ch != '-' && ch != ':' && !byteIsSpace(ch) {
return false
}
pos++
}
//
lineText := getLine(s, startLine+1)
columns := strings.Split(lineText, "|")
var aligns []Align
for i := 0; i < len(columns); i++ {
t := strings.TrimSpace(columns[i])
if t == "" {
if i == 0 || i == len(columns)-1 {
continue
}
return false
}
if !rColumn.MatchString(t) {
return false
}
if t[len(t)-1] == ':' {
if t[0] == ':' {
aligns = append(aligns, AlignCenter)
} else {
aligns = append(aligns, AlignRight)
}
} else if t[0] == ':' {
aligns = append(aligns, AlignLeft)
} else {
aligns = append(aligns, AlignNone)
}
}
lineText = strings.TrimSpace(getLine(s, startLine))
if strings.IndexByte(lineText, '|') == -1 {
return false
}
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
columns = escapedSplit(strings.TrimSuffix(strings.TrimPrefix(lineText, "|"), "|"))
columnCount := len(columns)
if columnCount > len(aligns) {
return false
}
if silent {
return true
}
tableTok := &TableOpen{
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tableTok)
s.PushOpeningToken(&TheadOpen{
Map: [2]int{startLine, startLine + 1},
})
s.PushOpeningToken(&TrOpen{
Map: [2]int{startLine, startLine + 1},
})
for i := 0; i < len(columns); i++ {
s.PushOpeningToken(&ThOpen{
Align: aligns[i],
Map: [2]int{startLine, startLine + 1},
})
s.PushToken(&Inline{
Content: strings.TrimSpace(columns[i]),
Map: [2]int{startLine, startLine + 1},
})
s.PushClosingToken(&ThClose{})
}
s.PushClosingToken(&TrClose{})
s.PushClosingToken(&TheadClose{})
tbodyTok := &TbodyOpen{
Map: [2]int{startLine + 2, 0},
}
s.PushOpeningToken(tbodyTok)
for nextLine = startLine + 2; nextLine < endLine; nextLine++ {
if s.SCount[nextLine] < s.BlkIndent {
break
}
lineText = strings.TrimSpace(getLine(s, nextLine))
if strings.IndexByte(lineText, '|') == -1 {
break
}
if s.SCount[nextLine]-s.BlkIndent >= 4 {
break
}
columns = escapedSplit(strings.TrimPrefix(strings.TrimSuffix(lineText, "|"), "|"))
if len(columns) < len(aligns) {
columns = append(columns, make([]string, len(aligns)-len(columns))...)
} else if len(columns) > len(aligns) {
columns = columns[:len(aligns)]
}
s.PushOpeningToken(&TrOpen{})
for i := 0; i < columnCount; i++ {
tdOpen := TdOpen{}
if i < len(aligns) {
tdOpen.Align = aligns[i]
}
s.PushOpeningToken(&tdOpen)
inline := Inline{}
if i < len(columns) {
inline.Content = strings.TrimSpace(columns[i])
}
s.PushToken(&inline)
s.PushClosingToken(&TdClose{})
}
s.PushClosingToken(&TrClose{})
}
s.PushClosingToken(&TbodyClose{})
s.PushClosingToken(&TableClose{})
tableTok.Map[1] = nextLine
tbodyTok.Map[1] = nextLine
s.Line = nextLine
return true
}