summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/gomarkdown/markdown/parser
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/gomarkdown/markdown/parser')
-rw-r--r--vendor/github.com/gomarkdown/markdown/parser/block.go332
-rw-r--r--vendor/github.com/gomarkdown/markdown/parser/block_table.go311
-rw-r--r--vendor/github.com/gomarkdown/markdown/parser/inline.go2
-rw-r--r--vendor/github.com/gomarkdown/markdown/parser/parser.go24
4 files changed, 357 insertions, 312 deletions
diff --git a/vendor/github.com/gomarkdown/markdown/parser/block.go b/vendor/github.com/gomarkdown/markdown/parser/block.go
index 95438174..7d7e9f9c 100644
--- a/vendor/github.com/gomarkdown/markdown/parser/block.go
+++ b/vendor/github.com/gomarkdown/markdown/parser/block.go
@@ -74,9 +74,9 @@ var (
}
)
-// sanitizeAnchorName returns a sanitized anchor name for the given text.
+// sanitizeHeadingID returns a sanitized anchor name for the given text.
// Taken from https://github.com/shurcooL/sanitized_anchor_name/blob/master/main.go#L14:1
-func sanitizeAnchorName(text string) string {
+func sanitizeHeadingID(text string) string {
var anchorName []rune
var futureDash = false
for _, r := range text {
@@ -91,6 +91,9 @@ func sanitizeAnchorName(text string) string {
futureDash = true
}
}
+ if len(anchorName) == 0 {
+ return "empty"
+ }
return string(anchorName)
}
@@ -278,12 +281,6 @@ func (p *Parser) block(data []byte) {
}
}
- // table:
- //
- // Name | Age | Phone
- // ------|-----|---------
- // Bob | 31 | 555-1234
- // Alice | 27 | 555-4321
if p.extensions&Tables != 0 {
if i := p.table(data); i > 0 {
data = data[i:]
@@ -422,13 +419,14 @@ func (p *Parser) prefixHeading(data []byte) int {
end--
}
if end > i {
- if id == "" && p.extensions&AutoHeadingIDs != 0 {
- id = sanitizeAnchorName(string(data[i:end]))
- }
block := &ast.Heading{
HeadingID: id,
Level: level,
}
+ if id == "" && p.extensions&AutoHeadingIDs != 0 {
+ block.HeadingID = sanitizeHeadingID(string(data[i:end]))
+ p.allHeadingsWithAutoID = append(p.allHeadingsWithAutoID, block)
+ }
block.Content = data[i:end]
p.addBlock(block)
}
@@ -492,14 +490,15 @@ func (p *Parser) prefixSpecialHeading(data []byte) int {
end--
}
if end > i {
- if id == "" && p.extensions&AutoHeadingIDs != 0 {
- id = sanitizeAnchorName(string(data[i:end]))
- }
block := &ast.Heading{
HeadingID: id,
IsSpecial: true,
Level: 1, // always level 1.
}
+ if id == "" && p.extensions&AutoHeadingIDs != 0 {
+ block.HeadingID = sanitizeHeadingID(string(data[i:end]))
+ p.allHeadingsWithAutoID = append(p.allHeadingsWithAutoID, block)
+ }
block.Literal = data[i:end]
block.Content = data[i:end]
p.addBlock(block)
@@ -647,7 +646,7 @@ func (p *Parser) html(data []byte, doRender bool) int {
if doRender {
// trim newlines
end := backChar(data, i, '\n')
- htmlBLock := &ast.HTMLBlock{ast.Leaf{Content: data[:end]}}
+ htmlBLock := &ast.HTMLBlock{Leaf: ast.Leaf{Content: data[:end]}}
p.addBlock(htmlBLock)
finalizeHTMLBlock(htmlBLock)
}
@@ -669,7 +668,7 @@ func (p *Parser) htmlComment(data []byte, doRender bool) int {
if doRender {
// trim trailing newlines
end := backChar(data, size, '\n')
- htmlBLock := &ast.HTMLBlock{ast.Leaf{Content: data[:end]}}
+ htmlBLock := &ast.HTMLBlock{Leaf: ast.Leaf{Content: data[:end]}}
p.addBlock(htmlBLock)
finalizeHTMLBlock(htmlBLock)
}
@@ -701,7 +700,7 @@ func (p *Parser) htmlHr(data []byte, doRender bool) int {
if doRender {
// trim newlines
end := backChar(data, size, '\n')
- htmlBlock := &ast.HTMLBlock{ast.Leaf{Content: data[:end]}}
+ htmlBlock := &ast.HTMLBlock{Leaf: ast.Leaf{Content: data[:end]}}
p.addBlock(htmlBlock)
finalizeHTMLBlock(htmlBlock)
}
@@ -1005,294 +1004,6 @@ func finalizeCodeBlock(code *ast.CodeBlock) {
code.Content = nil
}
-func (p *Parser) table(data []byte) int {
- i, columns, table := p.tableHeader(data)
- if i == 0 {
- return 0
- }
-
- p.addBlock(&ast.TableBody{})
-
- for i < len(data) {
- pipes, rowStart := 0, i
- for ; i < len(data) && data[i] != '\n'; i++ {
- if data[i] == '|' {
- pipes++
- }
- }
-
- if pipes == 0 {
- i = rowStart
- break
- }
-
- // include the newline in data sent to tableRow
- i = skipCharN(data, i, '\n', 1)
-
- if p.tableFooter(data[rowStart:i]) {
- continue
- }
-
- p.tableRow(data[rowStart:i], columns, false)
- }
- if captionContent, id, consumed := p.caption(data[i:], []byte("Table: ")); consumed > 0 {
- caption := &ast.Caption{}
- p.Inline(caption, captionContent)
-
- // Some switcheroo to re-insert the parsed table as a child of the captionfigure.
- figure := &ast.CaptionFigure{}
- figure.HeadingID = id
- table2 := &ast.Table{}
- // Retain any block level attributes.
- table2.AsContainer().Attribute = table.AsContainer().Attribute
- children := table.GetChildren()
- ast.RemoveFromTree(table)
-
- table2.SetChildren(children)
- ast.AppendChild(figure, table2)
- ast.AppendChild(figure, caption)
-
- p.addChild(figure)
- p.finalize(figure)
-
- i += consumed
- }
-
- return i
-}
-
-// check if the specified position is preceded by an odd number of backslashes
-func isBackslashEscaped(data []byte, i int) bool {
- backslashes := 0
- for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
- backslashes++
- }
- return backslashes&1 == 1
-}
-
-// tableHeaders parses the header. If recognized it will also add a table.
-func (p *Parser) tableHeader(data []byte) (size int, columns []ast.CellAlignFlags, table ast.Node) {
- i := 0
- colCount := 1
- headerIsUnderline := true
- for i = 0; i < len(data) && data[i] != '\n'; i++ {
- if data[i] == '|' && !isBackslashEscaped(data, i) {
- colCount++
- }
- if data[i] != '-' && data[i] != ' ' && data[i] != ':' && data[i] != '|' {
- headerIsUnderline = false
- }
- }
-
- // doesn't look like a table header
- if colCount == 1 {
- return
- }
-
- // include the newline in the data sent to tableRow
- j := skipCharN(data, i, '\n', 1)
- header := data[:j]
-
- // column count ignores pipes at beginning or end of line
- if data[0] == '|' {
- colCount--
- }
- if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) {
- colCount--
- }
-
- // if the header looks like a underline, then we omit the header
- // and parse the first line again as underline
- if headerIsUnderline {
- header = nil
- i = 0
- } else {
- i++ // move past newline
- }
-
- columns = make([]ast.CellAlignFlags, colCount)
-
- // move on to the header underline
- if i >= len(data) {
- return
- }
-
- if data[i] == '|' && !isBackslashEscaped(data, i) {
- i++
- }
- i = skipChar(data, i, ' ')
-
- // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
- // and trailing | optional on last column
- col := 0
- n := len(data)
- for i < n && data[i] != '\n' {
- dashes := 0
-
- if data[i] == ':' {
- i++
- columns[col] |= ast.TableAlignmentLeft
- dashes++
- }
- for i < n && data[i] == '-' {
- i++
- dashes++
- }
- if i < n && data[i] == ':' {
- i++
- columns[col] |= ast.TableAlignmentRight
- dashes++
- }
- for i < n && data[i] == ' ' {
- i++
- }
- if i == n {
- return
- }
- // end of column test is messy
- switch {
- case dashes < 3:
- // not a valid column
- return
-
- case data[i] == '|' && !isBackslashEscaped(data, i):
- // marker found, now skip past trailing whitespace
- col++
- i++
- for i < n && data[i] == ' ' {
- i++
- }
-
- // trailing junk found after last column
- if col >= colCount && i < len(data) && data[i] != '\n' {
- return
- }
-
- case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
- // something else found where marker was required
- return
-
- case data[i] == '\n':
- // marker is optional for the last column
- col++
-
- default:
- // trailing junk found after last column
- return
- }
- }
- if col != colCount {
- return
- }
-
- table = &ast.Table{}
- p.addBlock(table)
- if header != nil {
- p.addBlock(&ast.TableHeader{})
- p.tableRow(header, columns, true)
- }
- size = skipCharN(data, i, '\n', 1)
- return
-}
-
-func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool) {
- p.addBlock(&ast.TableRow{})
- i, col := 0, 0
-
- if data[i] == '|' && !isBackslashEscaped(data, i) {
- i++
- }
-
- n := len(data)
- colspans := 0 // keep track of total colspan in this row.
- for col = 0; col < len(columns) && i < n; col++ {
- colspan := 0
- for i < n && data[i] == ' ' {
- i++
- }
-
- cellStart := i
-
- for i < n && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
- i++
- }
-
- cellEnd := i
-
- // skip the end-of-cell marker, possibly taking us past end of buffer
- // each _extra_ | means a colspan
- for i < len(data) && data[i] == '|' && !isBackslashEscaped(data, i) {
- i++
- colspan++
- }
- // only colspan > 1 make sense.
- if colspan < 2 {
- colspan = 0
- }
-
- for cellEnd > cellStart && cellEnd-1 < n && data[cellEnd-1] == ' ' {
- cellEnd--
- }
-
- block := &ast.TableCell{
- IsHeader: header,
- Align: columns[col],
- ColSpan: colspan,
- }
- block.Content = data[cellStart:cellEnd]
- if cellStart == cellEnd && colspans > 0 {
- // an empty cell that we should ignore, it exists because of colspan
- colspans--
- } else {
- p.addBlock(block)
- }
-
- if colspan > 0 {
- colspans += colspan - 1
- }
- }
-
- // pad it out with empty columns to get the right number
- for ; col < len(columns); col++ {
- block := &ast.TableCell{
- IsHeader: header,
- Align: columns[col],
- }
- p.addBlock(block)
- }
-
- // silently ignore rows with too many cells
-}
-
-// tableFooter parses the (optional) table footer.
-func (p *Parser) tableFooter(data []byte) bool {
- colCount := 1
- i := 0
- n := len(data)
- for i < 3 && i < n && data[i] == ' ' { // ignore up to 3 spaces
- i++
- }
- for ; i < n && data[i] != '\n'; i++ {
- if data[i] == '|' && !isBackslashEscaped(data, i) {
- colCount++
- continue
- }
- // remaining data must be the = character
- if data[i] != '=' {
- return false
- }
- }
-
- // doesn't look like a table footer
- if colCount == 1 {
- return false
- }
-
- p.addBlock(&ast.TableFooter{})
-
- return true
-}
-
// returns blockquote prefix length
func (p *Parser) quotePrefix(data []byte) int {
i := 0
@@ -1887,15 +1598,14 @@ func (p *Parser) paragraph(data []byte) int {
eol--
}
- id := ""
+ block := &ast.Heading{
+ Level: level,
+ }
if p.extensions&AutoHeadingIDs != 0 {
- id = sanitizeAnchorName(string(data[prev:eol]))
+ block.HeadingID = sanitizeHeadingID(string(data[prev:eol]))
+ p.allHeadingsWithAutoID = append(p.allHeadingsWithAutoID, block)
}
- block := &ast.Heading{
- Level: level,
- HeadingID: id,
- }
block.Content = data[prev:eol]
p.addBlock(block)
diff --git a/vendor/github.com/gomarkdown/markdown/parser/block_table.go b/vendor/github.com/gomarkdown/markdown/parser/block_table.go
new file mode 100644
index 00000000..f6c06dff
--- /dev/null
+++ b/vendor/github.com/gomarkdown/markdown/parser/block_table.go
@@ -0,0 +1,311 @@
+package parser
+
+import "github.com/gomarkdown/markdown/ast"
+
+// check if the specified position is preceded by an odd number of backslashes
+func isBackslashEscaped(data []byte, i int) bool {
+ backslashes := 0
+ for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
+ backslashes++
+ }
+ return backslashes&1 == 1
+}
+
+func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool) {
+ p.addBlock(&ast.TableRow{})
+ col := 0
+
+ i := skipChar(data, 0, '|')
+
+ n := len(data)
+ colspans := 0 // keep track of total colspan in this row.
+ for col = 0; col < len(columns) && i < n; col++ {
+ colspan := 0
+ i = skipChar(data, i, ' ')
+
+ cellStart := i
+
+ for i < n && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
+ i++
+ }
+
+ cellEnd := i
+
+ // skip the end-of-cell marker, possibly taking us past end of buffer
+ // each _extra_ | means a colspan
+ for i < len(data) && data[i] == '|' && !isBackslashEscaped(data, i) {
+ i++
+ colspan++
+ }
+ // only colspan > 1 make sense.
+ if colspan < 2 {
+ colspan = 0
+ }
+
+ for cellEnd > cellStart && cellEnd-1 < n && data[cellEnd-1] == ' ' {
+ cellEnd--
+ }
+
+ block := &ast.TableCell{
+ IsHeader: header,
+ Align: columns[col],
+ ColSpan: colspan,
+ }
+ block.Content = data[cellStart:cellEnd]
+ if cellStart == cellEnd && colspans > 0 {
+ // an empty cell that we should ignore, it exists because of colspan
+ colspans--
+ } else {
+ p.addBlock(block)
+ }
+
+ if colspan > 0 {
+ colspans += colspan - 1
+ }
+ }
+
+ // pad it out with empty columns to get the right number
+ for ; col < len(columns); col++ {
+ block := &ast.TableCell{
+ IsHeader: header,
+ Align: columns[col],
+ }
+ p.addBlock(block)
+ }
+
+ // silently ignore rows with too many cells
+}
+
+// tableFooter parses the (optional) table footer.
+func (p *Parser) tableFooter(data []byte) bool {
+ colCount := 1
+
+ // ignore up to 3 spaces
+ n := len(data)
+ i := skipCharN(data, 0, ' ', 3)
+ for ; i < n && data[i] != '\n'; i++ {
+ if data[i] == '|' && !isBackslashEscaped(data, i) {
+ colCount++
+ continue
+ }
+ // remaining data must be the = character
+ if data[i] != '=' {
+ return false
+ }
+ }
+
+ // doesn't look like a table footer
+ if colCount == 1 {
+ return false
+ }
+
+ p.addBlock(&ast.TableFooter{})
+
+ return true
+}
+
+// tableHeaders parses the header. If recognized it will also add a table.
+func (p *Parser) tableHeader(data []byte) (size int, columns []ast.CellAlignFlags, table ast.Node) {
+ i := 0
+ colCount := 1
+ headerIsUnderline := true
+ headerIsWithEmptyFields := true
+ for i = 0; i < len(data) && data[i] != '\n'; i++ {
+ if data[i] == '|' && !isBackslashEscaped(data, i) {
+ colCount++
+ }
+ if data[i] != '-' && data[i] != ' ' && data[i] != ':' && data[i] != '|' {
+ headerIsUnderline = false
+ }
+ if data[i] != ' ' && data[i] != '|' {
+ headerIsWithEmptyFields = false
+ }
+ }
+
+ // doesn't look like a table header
+ if colCount == 1 {
+ return
+ }
+
+ // include the newline in the data sent to tableRow
+ j := skipCharN(data, i, '\n', 1)
+ header := data[:j]
+
+ // column count ignores pipes at beginning or end of line
+ if data[0] == '|' {
+ colCount--
+ }
+ {
+ tmp := header
+ // remove whitespace from the end
+ for len(tmp) > 0 {
+ lastIdx := len(tmp) - 1
+ if tmp[lastIdx] == '\n' || tmp[lastIdx] == ' ' {
+ tmp = tmp[:lastIdx]
+ } else {
+ break
+ }
+ }
+ n := len(tmp)
+ if n > 2 && tmp[n-1] == '|' && !isBackslashEscaped(tmp, n-1) {
+ colCount--
+ }
+ }
+
+ // if the header looks like a underline, then we omit the header
+ // and parse the first line again as underline
+ if headerIsUnderline && !headerIsWithEmptyFields {
+ header = nil
+ i = 0
+ } else {
+ i++ // move past newline
+ }
+
+ columns = make([]ast.CellAlignFlags, colCount)
+
+ // move on to the header underline
+ if i >= len(data) {
+ return
+ }
+
+ if data[i] == '|' && !isBackslashEscaped(data, i) {
+ i++
+ }
+ i = skipChar(data, i, ' ')
+
+ // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
+ // and trailing | optional on last column
+ col := 0
+ n := len(data)
+ for i < n && data[i] != '\n' {
+ dashes := 0
+
+ if data[i] == ':' {
+ i++
+ columns[col] |= ast.TableAlignmentLeft
+ dashes++
+ }
+ for i < n && data[i] == '-' {
+ i++
+ dashes++
+ }
+ if i < n && data[i] == ':' {
+ i++
+ columns[col] |= ast.TableAlignmentRight
+ dashes++
+ }
+ for i < n && data[i] == ' ' {
+ i++
+ }
+ if i == n {
+ return
+ }
+ // end of column test is messy
+ switch {
+ case dashes < 3:
+ // not a valid column
+ return
+
+ case data[i] == '|' && !isBackslashEscaped(data, i):
+ // marker found, now skip past trailing whitespace
+ col++
+ i++
+ for i < n && data[i] == ' ' {
+ i++
+ }
+
+ // trailing junk found after last column
+ if col >= colCount && i < len(data) && data[i] != '\n' {
+ return
+ }
+
+ case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
+ // something else found where marker was required
+ return
+
+ case data[i] == '\n':
+ // marker is optional for the last column
+ col++
+
+ default:
+ // trailing junk found after last column
+ return
+ }
+ }
+ if col != colCount {
+ return
+ }
+
+ table = &ast.Table{}
+ p.addBlock(table)
+ if header != nil {
+ p.addBlock(&ast.TableHeader{})
+ p.tableRow(header, columns, true)
+ }
+ size = skipCharN(data, i, '\n', 1)
+ return
+}
+
+/*
+Table:
+
+Name | Age | Phone
+------|-----|---------
+Bob | 31 | 555-1234
+Alice | 27 | 555-4321
+*/
+func (p *Parser) table(data []byte) int {
+ i, columns, table := p.tableHeader(data)
+ if i == 0 {
+ return 0
+ }
+
+ p.addBlock(&ast.TableBody{})
+
+ for i < len(data) {
+ pipes, rowStart := 0, i
+ for ; i < len(data) && data[i] != '\n'; i++ {
+ if data[i] == '|' {
+ pipes++
+ }
+ }
+
+ if pipes == 0 {
+ i = rowStart
+ break
+ }
+
+ // include the newline in data sent to tableRow
+ i = skipCharN(data, i, '\n', 1)
+
+ if p.tableFooter(data[rowStart:i]) {
+ continue
+ }
+
+ p.tableRow(data[rowStart:i], columns, false)
+ }
+ if captionContent, id, consumed := p.caption(data[i:], []byte("Table: ")); consumed > 0 {
+ caption := &ast.Caption{}
+ p.Inline(caption, captionContent)
+
+ // Some switcheroo to re-insert the parsed table as a child of the captionfigure.
+ figure := &ast.CaptionFigure{}
+ figure.HeadingID = id
+ table2 := &ast.Table{}
+ // Retain any block level attributes.
+ table2.AsContainer().Attribute = table.AsContainer().Attribute
+ children := table.GetChildren()
+ ast.RemoveFromTree(table)
+
+ table2.SetChildren(children)
+ ast.AppendChild(figure, table2)
+ ast.AppendChild(figure, caption)
+
+ p.addChild(figure)
+ p.finalize(figure)
+
+ i += consumed
+ }
+
+ return i
+}
diff --git a/vendor/github.com/gomarkdown/markdown/parser/inline.go b/vendor/github.com/gomarkdown/markdown/parser/inline.go
index 9bb5b30b..bc30326d 100644
--- a/vendor/github.com/gomarkdown/markdown/parser/inline.go
+++ b/vendor/github.com/gomarkdown/markdown/parser/inline.go
@@ -1293,7 +1293,7 @@ func math(p *Parser, data []byte, offset int) (int, ast.Node) {
}
func newTextNode(d []byte) *ast.Text {
- return &ast.Text{ast.Leaf{Literal: d}}
+ return &ast.Text{Leaf: ast.Leaf{Literal: d}}
}
func normalizeURI(s []byte) []byte {
diff --git a/vendor/github.com/gomarkdown/markdown/parser/parser.go b/vendor/github.com/gomarkdown/markdown/parser/parser.go
index c7302dfd..7712a29f 100644
--- a/vendor/github.com/gomarkdown/markdown/parser/parser.go
+++ b/vendor/github.com/gomarkdown/markdown/parser/parser.go
@@ -6,6 +6,7 @@ package parser
import (
"bytes"
"fmt"
+ "strconv"
"strings"
"unicode/utf8"
@@ -113,6 +114,10 @@ type Parser struct {
attr *ast.Attribute
includeStack *incStack
+
+ // collect headings where we auto-generated id so that we can
+ // ensure they are unique at the end
+ allHeadingsWithAutoID []*ast.Heading
}
// New creates a markdown parser with CommonExtensions.
@@ -282,6 +287,25 @@ func (p *Parser) Parse(input []byte) ast.Node {
if p.Opts.Flags&SkipFootnoteList == 0 {
p.parseRefsToAST()
}
+
+ // ensure HeadingIDs generated with AutoHeadingIDs are unique
+ // this is delayed here (as opposed to done when we create the id)
+ // so that we can preserve more original ids when there are conflicts
+ taken := map[string]bool{}
+ for _, h := range p.allHeadingsWithAutoID {
+ id := h.HeadingID
+ if id == "" {
+ continue
+ }
+ n := 0
+ for taken[id] {
+ n++
+ id = h.HeadingID + "-" + strconv.Itoa(n)
+ }
+ h.HeadingID = id
+ taken[id] = true
+ }
+
return p.Doc
}