mirror of
https://github.com/russross/blackfriday.git
synced 2024-03-22 13:40:34 +08:00
Fix all headings wrongly referred to as headers
I've left test cases alone since can't lean on the compiler for crosschecking there. Fixes #330.
This commit is contained in:
parent
ad7f7c56d5
commit
747587a52d
|
@ -224,7 +224,7 @@ are a few of note:
|
||||||
|
|
||||||
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
||||||
provides a GitHub Flavored Markdown renderer with fenced code block
|
provides a GitHub Flavored Markdown renderer with fenced code block
|
||||||
highlighting, clickable header anchor links.
|
highlighting, clickable heading anchor links.
|
||||||
|
|
||||||
It's not customizable, and its goal is to produce HTML output
|
It's not customizable, and its goal is to produce HTML output
|
||||||
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
|
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
|
||||||
|
|
60
block.go
60
block.go
|
@ -43,14 +43,14 @@ func (p *parser) block(data []byte) {
|
||||||
|
|
||||||
// parse out one block-level construct at a time
|
// parse out one block-level construct at a time
|
||||||
for len(data) > 0 {
|
for len(data) > 0 {
|
||||||
// prefixed header:
|
// prefixed heading:
|
||||||
//
|
//
|
||||||
// # Header 1
|
// # Heading 1
|
||||||
// ## Header 2
|
// ## Heading 2
|
||||||
// ...
|
// ...
|
||||||
// ###### Header 6
|
// ###### Heading 6
|
||||||
if p.isPrefixHeader(data) {
|
if p.isPrefixHeading(data) {
|
||||||
data = data[p.prefixHeader(data):]
|
data = data[p.prefixHeading(data):]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ func (p *parser) block(data []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// anything else must look like a normal paragraph
|
// anything else must look like a normal paragraph
|
||||||
// note: this finds underlined headers, too
|
// note: this finds underlined headings, too
|
||||||
data = data[p.paragraph(data):]
|
data = data[p.paragraph(data):]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,12 +204,12 @@ func (p *parser) addBlock(typ NodeType, content []byte) *Node {
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) isPrefixHeader(data []byte) bool {
|
func (p *parser) isPrefixHeading(data []byte) bool {
|
||||||
if data[0] != '#' {
|
if data[0] != '#' {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.flags&SpaceHeaders != 0 {
|
if p.flags&SpaceHeadings != 0 {
|
||||||
level := 0
|
level := 0
|
||||||
for level < 6 && level < len(data) && data[level] == '#' {
|
for level < 6 && level < len(data) && data[level] == '#' {
|
||||||
level++
|
level++
|
||||||
|
@ -221,7 +221,7 @@ func (p *parser) isPrefixHeader(data []byte) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) prefixHeader(data []byte) int {
|
func (p *parser) prefixHeading(data []byte) int {
|
||||||
level := 0
|
level := 0
|
||||||
for level < 6 && level < len(data) && data[level] == '#' {
|
for level < 6 && level < len(data) && data[level] == '#' {
|
||||||
level++
|
level++
|
||||||
|
@ -230,14 +230,14 @@ func (p *parser) prefixHeader(data []byte) int {
|
||||||
end := skipUntilChar(data, i, '\n')
|
end := skipUntilChar(data, i, '\n')
|
||||||
skip := end
|
skip := end
|
||||||
id := ""
|
id := ""
|
||||||
if p.flags&HeaderIDs != 0 {
|
if p.flags&HeadingIDs != 0 {
|
||||||
j, k := 0, 0
|
j, k := 0, 0
|
||||||
// find start/end of header id
|
// find start/end of heading id
|
||||||
for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
|
for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
|
||||||
}
|
}
|
||||||
for k = j + 1; k < end && data[k] != '}'; k++ {
|
for k = j + 1; k < end && data[k] != '}'; k++ {
|
||||||
}
|
}
|
||||||
// extract header id iff found
|
// extract heading id iff found
|
||||||
if j < end && k < end {
|
if j < end && k < end {
|
||||||
id = string(data[j+2 : k])
|
id = string(data[j+2 : k])
|
||||||
end = j
|
end = j
|
||||||
|
@ -257,18 +257,18 @@ func (p *parser) prefixHeader(data []byte) int {
|
||||||
end--
|
end--
|
||||||
}
|
}
|
||||||
if end > i {
|
if end > i {
|
||||||
if id == "" && p.flags&AutoHeaderIDs != 0 {
|
if id == "" && p.flags&AutoHeadingIDs != 0 {
|
||||||
id = sanitized_anchor_name.Create(string(data[i:end]))
|
id = sanitized_anchor_name.Create(string(data[i:end]))
|
||||||
}
|
}
|
||||||
block := p.addBlock(Header, data[i:end])
|
block := p.addBlock(Heading, data[i:end])
|
||||||
block.HeaderID = id
|
block.HeadingID = id
|
||||||
block.Level = level
|
block.Level = level
|
||||||
}
|
}
|
||||||
return skip
|
return skip
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) isUnderlinedHeader(data []byte) int {
|
func (p *parser) isUnderlinedHeading(data []byte) int {
|
||||||
// test of level 1 header
|
// test of level 1 heading
|
||||||
if data[0] == '=' {
|
if data[0] == '=' {
|
||||||
i := skipChar(data, 1, '=')
|
i := skipChar(data, 1, '=')
|
||||||
i = skipChar(data, i, ' ')
|
i = skipChar(data, i, ' ')
|
||||||
|
@ -278,7 +278,7 @@ func (p *parser) isUnderlinedHeader(data []byte) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// test of level 2 header
|
// test of level 2 heading
|
||||||
if data[0] == '-' {
|
if data[0] == '-' {
|
||||||
i := skipChar(data, 1, '-')
|
i := skipChar(data, 1, '-')
|
||||||
i = skipChar(data, i, ' ')
|
i = skipChar(data, i, ' ')
|
||||||
|
@ -308,7 +308,7 @@ func (p *parser) titleBlock(data []byte, doRender bool) int {
|
||||||
consumed := len(data)
|
consumed := len(data)
|
||||||
data = bytes.TrimPrefix(data, []byte("% "))
|
data = bytes.TrimPrefix(data, []byte("% "))
|
||||||
data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
|
data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
|
||||||
block := p.addBlock(Header, data)
|
block := p.addBlock(Heading, data)
|
||||||
block.Level = 1
|
block.Level = 1
|
||||||
block.IsTitleblock = true
|
block.IsTitleblock = true
|
||||||
|
|
||||||
|
@ -1301,9 +1301,9 @@ gatherlines:
|
||||||
sublist = raw.Len()
|
sublist = raw.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// is this a nested prefix header?
|
// is this a nested prefix heading?
|
||||||
case p.isPrefixHeader(chunk):
|
case p.isPrefixHeading(chunk):
|
||||||
// if the header is not indented, it is not nested in the list
|
// if the heading is not indented, it is not nested in the list
|
||||||
// and thus ends the list
|
// and thus ends the list
|
||||||
if containsBlankLine && indent < 4 {
|
if containsBlankLine && indent < 4 {
|
||||||
*flags |= ListItemEndOfList
|
*flags |= ListItemEndOfList
|
||||||
|
@ -1445,9 +1445,9 @@ func (p *parser) paragraph(data []byte) int {
|
||||||
return i + n
|
return i + n
|
||||||
}
|
}
|
||||||
|
|
||||||
// an underline under some text marks a header, so our paragraph ended on prev line
|
// an underline under some text marks a heading, so our paragraph ended on prev line
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
if level := p.isUnderlinedHeader(current); level > 0 {
|
if level := p.isUnderlinedHeading(current); level > 0 {
|
||||||
// render the paragraph
|
// render the paragraph
|
||||||
p.renderParagraph(data[:prev])
|
p.renderParagraph(data[:prev])
|
||||||
|
|
||||||
|
@ -1461,13 +1461,13 @@ func (p *parser) paragraph(data []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
id := ""
|
id := ""
|
||||||
if p.flags&AutoHeaderIDs != 0 {
|
if p.flags&AutoHeadingIDs != 0 {
|
||||||
id = sanitized_anchor_name.Create(string(data[prev:eol]))
|
id = sanitized_anchor_name.Create(string(data[prev:eol]))
|
||||||
}
|
}
|
||||||
|
|
||||||
block := p.addBlock(Header, data[prev:eol])
|
block := p.addBlock(Heading, data[prev:eol])
|
||||||
block.Level = level
|
block.Level = level
|
||||||
block.HeaderID = id
|
block.HeadingID = id
|
||||||
|
|
||||||
// find the end of the underline
|
// find the end of the underline
|
||||||
for i < len(data) && data[i] != '\n' {
|
for i < len(data) && data[i] != '\n' {
|
||||||
|
@ -1486,8 +1486,8 @@ func (p *parser) paragraph(data []byte) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's a prefixed header or a horizontal rule after this, paragraph is over
|
// if there's a prefixed heading or a horizontal rule after this, paragraph is over
|
||||||
if p.isPrefixHeader(current) || p.isHRule(current) {
|
if p.isPrefixHeading(current) || p.isHRule(current) {
|
||||||
p.renderParagraph(data[:i])
|
p.renderParagraph(data[:i])
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@ func TestPrefixHeaderSpaceExtension(t *testing.T) {
|
||||||
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
|
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
|
||||||
"<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
"<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, SpaceHeaders)
|
doTestsBlock(t, tests, SpaceHeadings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixHeaderIdExtension(t *testing.T) {
|
func TestPrefixHeaderIdExtension(t *testing.T) {
|
||||||
|
@ -204,7 +204,7 @@ func TestPrefixHeaderIdExtension(t *testing.T) {
|
||||||
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
|
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
|
||||||
"<h1 id=\"someid\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
"<h1 id=\"someid\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, HeaderIDs)
|
doTestsBlock(t, tests, HeadingIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
|
@ -248,12 +248,12 @@ func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters := HTMLRendererParameters{
|
parameters := HTMLRendererParameters{
|
||||||
HeaderIDPrefix: "PRE:",
|
HeadingIDPrefix: "PRE:",
|
||||||
HeaderIDSuffix: ":POST",
|
HeadingIDSuffix: ":POST",
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsParam(t, tests, TestParams{
|
doTestsParam(t, tests, TestParams{
|
||||||
Options: Options{Extensions: HeaderIDs},
|
Options: Options{Extensions: HeadingIDs},
|
||||||
HTMLFlags: UseXHTML,
|
HTMLFlags: UseXHTML,
|
||||||
HTMLRendererParameters: parameters,
|
HTMLRendererParameters: parameters,
|
||||||
})
|
})
|
||||||
|
@ -307,7 +307,7 @@ func TestPrefixAutoHeaderIdExtension(t *testing.T) {
|
||||||
"# Header\n\n# Header 1\n\n# Header\n\n# Header",
|
"# Header\n\n# Header 1\n\n# Header\n\n# Header",
|
||||||
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header</h1>\n\n<h1 id=\"header-1-2\">Header</h1>\n",
|
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header</h1>\n\n<h1 id=\"header-1-2\">Header</h1>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, AutoHeaderIDs)
|
doTestsBlock(t, tests, AutoHeadingIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
|
@ -360,12 +360,12 @@ func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters := HTMLRendererParameters{
|
parameters := HTMLRendererParameters{
|
||||||
HeaderIDPrefix: "PRE:",
|
HeadingIDPrefix: "PRE:",
|
||||||
HeaderIDSuffix: ":POST",
|
HeadingIDSuffix: ":POST",
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsParam(t, tests, TestParams{
|
doTestsParam(t, tests, TestParams{
|
||||||
Options: Options{Extensions: AutoHeaderIDs},
|
Options: Options{Extensions: AutoHeadingIDs},
|
||||||
HTMLFlags: UseXHTML,
|
HTMLFlags: UseXHTML,
|
||||||
HTMLRendererParameters: parameters,
|
HTMLRendererParameters: parameters,
|
||||||
})
|
})
|
||||||
|
@ -376,7 +376,7 @@ func TestPrefixMultipleHeaderExtensions(t *testing.T) {
|
||||||
"# Header\n\n# Header {#header}\n\n# Header 1",
|
"# Header\n\n# Header {#header}\n\n# Header 1",
|
||||||
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, AutoHeaderIDs|HeaderIDs)
|
doTestsBlock(t, tests, AutoHeadingIDs|HeadingIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnderlineHeaders(t *testing.T) {
|
func TestUnderlineHeaders(t *testing.T) {
|
||||||
|
@ -476,7 +476,7 @@ func TestUnderlineHeadersAutoIDs(t *testing.T) {
|
||||||
"Header 1\n========\n\nHeader 1\n========\n",
|
"Header 1\n========\n\nHeader 1\n========\n",
|
||||||
"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, AutoHeaderIDs)
|
doTestsBlock(t, tests, AutoHeadingIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHorizontalRule(t *testing.T) {
|
func TestHorizontalRule(t *testing.T) {
|
||||||
|
|
64
html.go
64
html.go
|
@ -80,11 +80,11 @@ type HTMLRendererParameters struct {
|
||||||
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
|
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
|
||||||
// <sup>[return]</sup> is used.
|
// <sup>[return]</sup> is used.
|
||||||
FootnoteReturnLinkContents string
|
FootnoteReturnLinkContents string
|
||||||
// If set, add this text to the front of each Header ID, to ensure
|
// If set, add this text to the front of each Heading ID, to ensure
|
||||||
// uniqueness.
|
// uniqueness.
|
||||||
HeaderIDPrefix string
|
HeadingIDPrefix string
|
||||||
// If set, add this text to the back of each Header ID, to ensure uniqueness.
|
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
|
||||||
HeaderIDSuffix string
|
HeadingIDSuffix string
|
||||||
|
|
||||||
Title string // Document title (used if CompletePage is set)
|
Title string // Document title (used if CompletePage is set)
|
||||||
CSS string // Optional CSS file URL (used if CompletePage is set)
|
CSS string // Optional CSS file URL (used if CompletePage is set)
|
||||||
|
@ -101,8 +101,8 @@ type HTMLRenderer struct {
|
||||||
|
|
||||||
closeTag string // how to end singleton tags: either " />" or ">"
|
closeTag string // how to end singleton tags: either " />" or ">"
|
||||||
|
|
||||||
// Track header IDs to prevent ID collision in a single generation.
|
// Track heading IDs to prevent ID collision in a single generation.
|
||||||
headerIDs map[string]int
|
headingIDs map[string]int
|
||||||
|
|
||||||
lastOutputLen int
|
lastOutputLen int
|
||||||
disableTags int
|
disableTags int
|
||||||
|
@ -131,8 +131,8 @@ func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
|
||||||
return &HTMLRenderer{
|
return &HTMLRenderer{
|
||||||
HTMLRendererParameters: params,
|
HTMLRendererParameters: params,
|
||||||
|
|
||||||
closeTag: closeTag,
|
closeTag: closeTag,
|
||||||
headerIDs: make(map[string]int),
|
headingIDs: make(map[string]int),
|
||||||
|
|
||||||
sr: NewSmartypantsRenderer(params.Flags),
|
sr: NewSmartypantsRenderer(params.Flags),
|
||||||
}
|
}
|
||||||
|
@ -238,20 +238,20 @@ func isRelativeLink(link []byte) (yes bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLRenderer) ensureUniqueHeaderID(id string) string {
|
func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
|
||||||
for count, found := r.headerIDs[id]; found; count, found = r.headerIDs[id] {
|
for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
|
||||||
tmp := fmt.Sprintf("%s-%d", id, count+1)
|
tmp := fmt.Sprintf("%s-%d", id, count+1)
|
||||||
|
|
||||||
if _, tmpFound := r.headerIDs[tmp]; !tmpFound {
|
if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
|
||||||
r.headerIDs[id] = count + 1
|
r.headingIDs[id] = count + 1
|
||||||
id = tmp
|
id = tmp
|
||||||
} else {
|
} else {
|
||||||
id = id + "-1"
|
id = id + "-1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, found := r.headerIDs[id]; !found {
|
if _, found := r.headingIDs[id]; !found {
|
||||||
r.headerIDs[id] = 0
|
r.headingIDs[id] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return id
|
return id
|
||||||
|
@ -457,7 +457,7 @@ var (
|
||||||
footnotesCloseDivBytes = []byte("\n</div>\n")
|
footnotesCloseDivBytes = []byte("\n</div>\n")
|
||||||
)
|
)
|
||||||
|
|
||||||
func headerTagsFromLevel(level int) ([]byte, []byte) {
|
func headingTagsFromLevel(level int) ([]byte, []byte) {
|
||||||
switch level {
|
switch level {
|
||||||
case 1:
|
case 1:
|
||||||
return h1Tag, h1CloseTag
|
return h1Tag, h1CloseTag
|
||||||
|
@ -619,7 +619,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
|
||||||
// to be added and when not.
|
// to be added and when not.
|
||||||
if node.Prev != nil {
|
if node.Prev != nil {
|
||||||
switch node.Prev.Type {
|
switch node.Prev.Type {
|
||||||
case HTMLBlock, List, Paragraph, Header, CodeBlock, BlockQuote, HorizontalRule:
|
case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
|
||||||
r.cr(w)
|
r.cr(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -648,19 +648,19 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
|
||||||
r.cr(w)
|
r.cr(w)
|
||||||
r.out(w, node.Literal)
|
r.out(w, node.Literal)
|
||||||
r.cr(w)
|
r.cr(w)
|
||||||
case Header:
|
case Heading:
|
||||||
openTag, closeTag := headerTagsFromLevel(node.Level)
|
openTag, closeTag := headingTagsFromLevel(node.Level)
|
||||||
if entering {
|
if entering {
|
||||||
if node.IsTitleblock {
|
if node.IsTitleblock {
|
||||||
attrs = append(attrs, `class="title"`)
|
attrs = append(attrs, `class="title"`)
|
||||||
}
|
}
|
||||||
if node.HeaderID != "" {
|
if node.HeadingID != "" {
|
||||||
id := r.ensureUniqueHeaderID(node.HeaderID)
|
id := r.ensureUniqueHeadingID(node.HeadingID)
|
||||||
if r.HeaderIDPrefix != "" {
|
if r.HeadingIDPrefix != "" {
|
||||||
id = r.HeaderIDPrefix + id
|
id = r.HeadingIDPrefix + id
|
||||||
}
|
}
|
||||||
if r.HeaderIDSuffix != "" {
|
if r.HeadingIDSuffix != "" {
|
||||||
id = id + r.HeaderIDSuffix
|
id = id + r.HeadingIDSuffix
|
||||||
}
|
}
|
||||||
attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
|
attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
|
||||||
}
|
}
|
||||||
|
@ -870,15 +870,15 @@ func (r *HTMLRenderer) writeDocumentHeader(w *bytes.Buffer) {
|
||||||
func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) {
|
func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) {
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
inHeader := false
|
inHeading := false
|
||||||
tocLevel := 0
|
tocLevel := 0
|
||||||
headerCount := 0
|
headingCount := 0
|
||||||
|
|
||||||
ast.Walk(func(node *Node, entering bool) WalkStatus {
|
ast.Walk(func(node *Node, entering bool) WalkStatus {
|
||||||
if node.Type == Header && !node.HeaderData.IsTitleblock {
|
if node.Type == Heading && !node.HeadingData.IsTitleblock {
|
||||||
inHeader = entering
|
inHeading = entering
|
||||||
if entering {
|
if entering {
|
||||||
node.HeaderID = fmt.Sprintf("toc_%d", headerCount)
|
node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
|
||||||
if node.Level == tocLevel {
|
if node.Level == tocLevel {
|
||||||
buf.WriteString("</li>\n\n<li>")
|
buf.WriteString("</li>\n\n<li>")
|
||||||
} else if node.Level < tocLevel {
|
} else if node.Level < tocLevel {
|
||||||
|
@ -894,15 +894,15 @@ func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&buf, `<a href="#toc_%d">`, headerCount)
|
fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
|
||||||
headerCount++
|
headingCount++
|
||||||
} else {
|
} else {
|
||||||
buf.WriteString("</a>")
|
buf.WriteString("</a>")
|
||||||
}
|
}
|
||||||
return GoToNext
|
return GoToNext
|
||||||
}
|
}
|
||||||
|
|
||||||
if inHeader {
|
if inHeading {
|
||||||
return r.RenderNode(&buf, node, entering)
|
return r.RenderNode(&buf, node, entering)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
markdown.go
16
markdown.go
|
@ -36,14 +36,14 @@ const (
|
||||||
Autolink // Detect embedded URLs that are not explicitly marked
|
Autolink // Detect embedded URLs that are not explicitly marked
|
||||||
Strikethrough // Strikethrough text using ~~test~~
|
Strikethrough // Strikethrough text using ~~test~~
|
||||||
LaxHTMLBlocks // Loosen up HTML block parsing rules
|
LaxHTMLBlocks // Loosen up HTML block parsing rules
|
||||||
SpaceHeaders // Be strict about prefix header rules
|
SpaceHeadings // Be strict about prefix heading rules
|
||||||
HardLineBreak // Translate newlines into line breaks
|
HardLineBreak // Translate newlines into line breaks
|
||||||
TabSizeEight // Expand tabs to eight spaces instead of four
|
TabSizeEight // Expand tabs to eight spaces instead of four
|
||||||
Footnotes // Pandoc-style footnotes
|
Footnotes // Pandoc-style footnotes
|
||||||
NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
||||||
HeaderIDs // specify header IDs with {#id}
|
HeadingIDs // specify heading IDs with {#id}
|
||||||
Titleblock // Titleblock ala pandoc
|
Titleblock // Titleblock ala pandoc
|
||||||
AutoHeaderIDs // Create the header ID from the text
|
AutoHeadingIDs // Create the heading ID from the text
|
||||||
BackslashLineBreak // Translate trailing backslashes into line breaks
|
BackslashLineBreak // Translate trailing backslashes into line breaks
|
||||||
DefinitionLists // Render definition lists
|
DefinitionLists // Render definition lists
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ const (
|
||||||
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
|
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
|
||||||
|
|
||||||
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
|
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
|
||||||
Autolink | Strikethrough | SpaceHeaders | HeaderIDs |
|
Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
|
||||||
BackslashLineBreak | DefinitionLists
|
BackslashLineBreak | DefinitionLists
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -310,9 +310,9 @@ func MarkdownBasic(input []byte) []byte {
|
||||||
//
|
//
|
||||||
// * Strikethrough support
|
// * Strikethrough support
|
||||||
//
|
//
|
||||||
// * Strict header parsing
|
// * Strict heading parsing
|
||||||
//
|
//
|
||||||
// * Custom Header IDs
|
// * Custom Heading IDs
|
||||||
func MarkdownCommon(input []byte) []byte {
|
func MarkdownCommon(input []byte) []byte {
|
||||||
// set up the HTML renderer
|
// set up the HTML renderer
|
||||||
renderer := NewHTMLRenderer(HTMLRendererParameters{
|
renderer := NewHTMLRenderer(HTMLRendererParameters{
|
||||||
|
@ -392,7 +392,7 @@ func Parse(input []byte, opts Options) *Node {
|
||||||
}
|
}
|
||||||
// Walk the tree again and process inline markdown in each block
|
// Walk the tree again and process inline markdown in each block
|
||||||
p.doc.Walk(func(node *Node, entering bool) WalkStatus {
|
p.doc.Walk(func(node *Node, entering bool) WalkStatus {
|
||||||
if node.Type == Paragraph || node.Type == Header || node.Type == TableCell {
|
if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
|
||||||
p.inline(node, node.content)
|
p.inline(node, node.content)
|
||||||
node.content = nil
|
node.content = nil
|
||||||
}
|
}
|
||||||
|
@ -433,7 +433,7 @@ func (p *parser) parseRefsToAST() {
|
||||||
finalizeList(block)
|
finalizeList(block)
|
||||||
p.tip = above
|
p.tip = above
|
||||||
block.Walk(func(node *Node, entering bool) WalkStatus {
|
block.Walk(func(node *Node, entering bool) WalkStatus {
|
||||||
if node.Type == Paragraph || node.Type == Header {
|
if node.Type == Paragraph || node.Type == Heading {
|
||||||
p.inline(node, node.content)
|
p.inline(node, node.content)
|
||||||
node.content = nil
|
node.content = nil
|
||||||
}
|
}
|
||||||
|
|
14
node.go
14
node.go
|
@ -17,7 +17,7 @@ const (
|
||||||
List
|
List
|
||||||
Item
|
Item
|
||||||
Paragraph
|
Paragraph
|
||||||
Header
|
Heading
|
||||||
HorizontalRule
|
HorizontalRule
|
||||||
Emph
|
Emph
|
||||||
Strong
|
Strong
|
||||||
|
@ -44,7 +44,7 @@ var nodeTypeNames = []string{
|
||||||
List: "List",
|
List: "List",
|
||||||
Item: "Item",
|
Item: "Item",
|
||||||
Paragraph: "Paragraph",
|
Paragraph: "Paragraph",
|
||||||
Header: "Header",
|
Heading: "Heading",
|
||||||
HorizontalRule: "HorizontalRule",
|
HorizontalRule: "HorizontalRule",
|
||||||
Emph: "Emph",
|
Emph: "Emph",
|
||||||
Strong: "Strong",
|
Strong: "Strong",
|
||||||
|
@ -102,10 +102,10 @@ type TableCellData struct {
|
||||||
Align CellAlignFlags // This holds the value for align attribute
|
Align CellAlignFlags // This holds the value for align attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeaderData contains fields relevant to a Header node type.
|
// HeadingData contains fields relevant to a Heading node type.
|
||||||
type HeaderData struct {
|
type HeadingData struct {
|
||||||
Level int // This holds the heading level number
|
Level int // This holds the heading level number
|
||||||
HeaderID string // This might hold header ID, if present
|
HeadingID string // This might hold heading ID, if present
|
||||||
IsTitleblock bool // Specifies whether it's a title block
|
IsTitleblock bool // Specifies whether it's a title block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ type Node struct {
|
||||||
|
|
||||||
Literal []byte // Text contents of the leaf nodes
|
Literal []byte // Text contents of the leaf nodes
|
||||||
|
|
||||||
HeaderData // Populated if Type is Header
|
HeadingData // Populated if Type is Heading
|
||||||
ListData // Populated if Type is List
|
ListData // Populated if Type is List
|
||||||
CodeBlockData // Populated if Type is CodeBlock
|
CodeBlockData // Populated if Type is CodeBlock
|
||||||
LinkData // Populated if Type is Link
|
LinkData // Populated if Type is Link
|
||||||
|
@ -211,7 +211,7 @@ func (n *Node) isContainer() bool {
|
||||||
fallthrough
|
fallthrough
|
||||||
case Paragraph:
|
case Paragraph:
|
||||||
fallthrough
|
fallthrough
|
||||||
case Header:
|
case Heading:
|
||||||
fallthrough
|
fallthrough
|
||||||
case Emph:
|
case Emph:
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
Loading…
Reference in New Issue
Block a user