diff options
Diffstat (limited to 'vendor/github.com/gomarkdown/markdown/parser')
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 } |