1
0
mirror of https://github.com/danog/blackfriday.git synced 2024-11-30 04:29:13 +01: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:
Vytautas Šaltenis 2017-02-12 19:00:18 +02:00
parent ad7f7c56d5
commit 747587a52d
6 changed files with 89 additions and 89 deletions

View File

@ -224,7 +224,7 @@ are a few of note:
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
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
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),

View File

@ -43,14 +43,14 @@ func (p *parser) block(data []byte) {
// parse out one block-level construct at a time
for len(data) > 0 {
// prefixed header:
// prefixed heading:
//
// # Header 1
// ## Header 2
// # Heading 1
// ## Heading 2
// ...
// ###### Header 6
if p.isPrefixHeader(data) {
data = data[p.prefixHeader(data):]
// ###### Heading 6
if p.isPrefixHeading(data) {
data = data[p.prefixHeading(data):]
continue
}
@ -190,7 +190,7 @@ func (p *parser) block(data []byte) {
}
// 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):]
}
@ -204,12 +204,12 @@ func (p *parser) addBlock(typ NodeType, content []byte) *Node {
return container
}
func (p *parser) isPrefixHeader(data []byte) bool {
func (p *parser) isPrefixHeading(data []byte) bool {
if data[0] != '#' {
return false
}
if p.flags&SpaceHeaders != 0 {
if p.flags&SpaceHeadings != 0 {
level := 0
for level < 6 && level < len(data) && data[level] == '#' {
level++
@ -221,7 +221,7 @@ func (p *parser) isPrefixHeader(data []byte) bool {
return true
}
func (p *parser) prefixHeader(data []byte) int {
func (p *parser) prefixHeading(data []byte) int {
level := 0
for level < 6 && level < len(data) && data[level] == '#' {
level++
@ -230,14 +230,14 @@ func (p *parser) prefixHeader(data []byte) int {
end := skipUntilChar(data, i, '\n')
skip := end
id := ""
if p.flags&HeaderIDs != 0 {
if p.flags&HeadingIDs != 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 k = j + 1; k < end && data[k] != '}'; k++ {
}
// extract header id iff found
// extract heading id iff found
if j < end && k < end {
id = string(data[j+2 : k])
end = j
@ -257,18 +257,18 @@ func (p *parser) prefixHeader(data []byte) int {
end--
}
if end > i {
if id == "" && p.flags&AutoHeaderIDs != 0 {
if id == "" && p.flags&AutoHeadingIDs != 0 {
id = sanitized_anchor_name.Create(string(data[i:end]))
}
block := p.addBlock(Header, data[i:end])
block.HeaderID = id
block := p.addBlock(Heading, data[i:end])
block.HeadingID = id
block.Level = level
}
return skip
}
func (p *parser) isUnderlinedHeader(data []byte) int {
// test of level 1 header
func (p *parser) isUnderlinedHeading(data []byte) int {
// test of level 1 heading
if data[0] == '=' {
i := skipChar(data, 1, '=')
i = skipChar(data, i, ' ')
@ -278,7 +278,7 @@ func (p *parser) isUnderlinedHeader(data []byte) int {
return 0
}
// test of level 2 header
// test of level 2 heading
if data[0] == '-' {
i := skipChar(data, 1, '-')
i = skipChar(data, i, ' ')
@ -308,7 +308,7 @@ func (p *parser) titleBlock(data []byte, doRender bool) int {
consumed := len(data)
data = bytes.TrimPrefix(data, []byte("% "))
data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
block := p.addBlock(Header, data)
block := p.addBlock(Heading, data)
block.Level = 1
block.IsTitleblock = true
@ -1301,9 +1301,9 @@ gatherlines:
sublist = raw.Len()
}
// is this a nested prefix header?
case p.isPrefixHeader(chunk):
// if the header is not indented, it is not nested in the list
// is this a nested prefix heading?
case p.isPrefixHeading(chunk):
// if the heading is not indented, it is not nested in the list
// and thus ends the list
if containsBlankLine && indent < 4 {
*flags |= ListItemEndOfList
@ -1445,9 +1445,9 @@ func (p *parser) paragraph(data []byte) int {
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 level := p.isUnderlinedHeader(current); level > 0 {
if level := p.isUnderlinedHeading(current); level > 0 {
// render the paragraph
p.renderParagraph(data[:prev])
@ -1461,13 +1461,13 @@ func (p *parser) paragraph(data []byte) int {
}
id := ""
if p.flags&AutoHeaderIDs != 0 {
if p.flags&AutoHeadingIDs != 0 {
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.HeaderID = id
block.HeadingID = id
// find the end of the underline
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 p.isPrefixHeader(current) || p.isHRule(current) {
// if there's a prefixed heading or a horizontal rule after this, paragraph is over
if p.isPrefixHeading(current) || p.isHRule(current) {
p.renderParagraph(data[:i])
return i
}

View File

@ -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" +
"<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n",
}
doTestsBlock(t, tests, SpaceHeaders)
doTestsBlock(t, tests, SpaceHeadings)
}
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" +
"<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) {
@ -248,12 +248,12 @@ func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
}
parameters := HTMLRendererParameters{
HeaderIDPrefix: "PRE:",
HeaderIDSuffix: ":POST",
HeadingIDPrefix: "PRE:",
HeadingIDSuffix: ":POST",
}
doTestsParam(t, tests, TestParams{
Options: Options{Extensions: HeaderIDs},
Options: Options{Extensions: HeadingIDs},
HTMLFlags: UseXHTML,
HTMLRendererParameters: parameters,
})
@ -307,7 +307,7 @@ func TestPrefixAutoHeaderIdExtension(t *testing.T) {
"# 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",
}
doTestsBlock(t, tests, AutoHeaderIDs)
doTestsBlock(t, tests, AutoHeadingIDs)
}
func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
@ -360,12 +360,12 @@ func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
}
parameters := HTMLRendererParameters{
HeaderIDPrefix: "PRE:",
HeaderIDSuffix: ":POST",
HeadingIDPrefix: "PRE:",
HeadingIDSuffix: ":POST",
}
doTestsParam(t, tests, TestParams{
Options: Options{Extensions: AutoHeaderIDs},
Options: Options{Extensions: AutoHeadingIDs},
HTMLFlags: UseXHTML,
HTMLRendererParameters: parameters,
})
@ -376,7 +376,7 @@ func TestPrefixMultipleHeaderExtensions(t *testing.T) {
"# 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",
}
doTestsBlock(t, tests, AutoHeaderIDs|HeaderIDs)
doTestsBlock(t, tests, AutoHeadingIDs|HeadingIDs)
}
func TestUnderlineHeaders(t *testing.T) {
@ -476,7 +476,7 @@ func TestUnderlineHeadersAutoIDs(t *testing.T) {
"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",
}
doTestsBlock(t, tests, AutoHeaderIDs)
doTestsBlock(t, tests, AutoHeadingIDs)
}
func TestHorizontalRule(t *testing.T) {

64
html.go
View File

@ -80,11 +80,11 @@ type HTMLRendererParameters struct {
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
// <sup>[return]</sup> is used.
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.
HeaderIDPrefix string
// If set, add this text to the back of each Header ID, to ensure uniqueness.
HeaderIDSuffix string
HeadingIDPrefix string
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
HeadingIDSuffix string
Title string // Document title (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 ">"
// Track header IDs to prevent ID collision in a single generation.
headerIDs map[string]int
// Track heading IDs to prevent ID collision in a single generation.
headingIDs map[string]int
lastOutputLen int
disableTags int
@ -131,8 +131,8 @@ func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
return &HTMLRenderer{
HTMLRendererParameters: params,
closeTag: closeTag,
headerIDs: make(map[string]int),
closeTag: closeTag,
headingIDs: make(map[string]int),
sr: NewSmartypantsRenderer(params.Flags),
}
@ -238,20 +238,20 @@ func isRelativeLink(link []byte) (yes bool) {
return false
}
func (r *HTMLRenderer) ensureUniqueHeaderID(id string) string {
for count, found := r.headerIDs[id]; found; count, found = r.headerIDs[id] {
func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
tmp := fmt.Sprintf("%s-%d", id, count+1)
if _, tmpFound := r.headerIDs[tmp]; !tmpFound {
r.headerIDs[id] = count + 1
if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
r.headingIDs[id] = count + 1
id = tmp
} else {
id = id + "-1"
}
}
if _, found := r.headerIDs[id]; !found {
r.headerIDs[id] = 0
if _, found := r.headingIDs[id]; !found {
r.headingIDs[id] = 0
}
return id
@ -457,7 +457,7 @@ var (
footnotesCloseDivBytes = []byte("\n</div>\n")
)
func headerTagsFromLevel(level int) ([]byte, []byte) {
func headingTagsFromLevel(level int) ([]byte, []byte) {
switch level {
case 1:
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.
if node.Prev != nil {
switch node.Prev.Type {
case HTMLBlock, List, Paragraph, Header, CodeBlock, BlockQuote, HorizontalRule:
case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
r.cr(w)
}
}
@ -648,19 +648,19 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.cr(w)
r.out(w, node.Literal)
r.cr(w)
case Header:
openTag, closeTag := headerTagsFromLevel(node.Level)
case Heading:
openTag, closeTag := headingTagsFromLevel(node.Level)
if entering {
if node.IsTitleblock {
attrs = append(attrs, `class="title"`)
}
if node.HeaderID != "" {
id := r.ensureUniqueHeaderID(node.HeaderID)
if r.HeaderIDPrefix != "" {
id = r.HeaderIDPrefix + id
if node.HeadingID != "" {
id := r.ensureUniqueHeadingID(node.HeadingID)
if r.HeadingIDPrefix != "" {
id = r.HeadingIDPrefix + id
}
if r.HeaderIDSuffix != "" {
id = id + r.HeaderIDSuffix
if r.HeadingIDSuffix != "" {
id = id + r.HeadingIDSuffix
}
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) {
buf := bytes.Buffer{}
inHeader := false
inHeading := false
tocLevel := 0
headerCount := 0
headingCount := 0
ast.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Header && !node.HeaderData.IsTitleblock {
inHeader = entering
if node.Type == Heading && !node.HeadingData.IsTitleblock {
inHeading = entering
if entering {
node.HeaderID = fmt.Sprintf("toc_%d", headerCount)
node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
if node.Level == tocLevel {
buf.WriteString("</li>\n\n<li>")
} 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)
headerCount++
fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
headingCount++
} else {
buf.WriteString("</a>")
}
return GoToNext
}
if inHeader {
if inHeading {
return r.RenderNode(&buf, node, entering)
}

View File

@ -36,14 +36,14 @@ const (
Autolink // Detect embedded URLs that are not explicitly marked
Strikethrough // Strikethrough text using ~~test~~
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
TabSizeEight // Expand tabs to eight spaces instead of four
Footnotes // Pandoc-style footnotes
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
AutoHeaderIDs // Create the header ID from the text
AutoHeadingIDs // Create the heading ID from the text
BackslashLineBreak // Translate trailing backslashes into line breaks
DefinitionLists // Render definition lists
@ -51,7 +51,7 @@ const (
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
Autolink | Strikethrough | SpaceHeaders | HeaderIDs |
Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
BackslashLineBreak | DefinitionLists
)
@ -310,9 +310,9 @@ func MarkdownBasic(input []byte) []byte {
//
// * Strikethrough support
//
// * Strict header parsing
// * Strict heading parsing
//
// * Custom Header IDs
// * Custom Heading IDs
func MarkdownCommon(input []byte) []byte {
// set up the HTML renderer
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
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)
node.content = nil
}
@ -433,7 +433,7 @@ func (p *parser) parseRefsToAST() {
finalizeList(block)
p.tip = above
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)
node.content = nil
}

14
node.go
View File

@ -17,7 +17,7 @@ const (
List
Item
Paragraph
Header
Heading
HorizontalRule
Emph
Strong
@ -44,7 +44,7 @@ var nodeTypeNames = []string{
List: "List",
Item: "Item",
Paragraph: "Paragraph",
Header: "Header",
Heading: "Heading",
HorizontalRule: "HorizontalRule",
Emph: "Emph",
Strong: "Strong",
@ -102,10 +102,10 @@ type TableCellData struct {
Align CellAlignFlags // This holds the value for align attribute
}
// HeaderData contains fields relevant to a Header node type.
type HeaderData struct {
// HeadingData contains fields relevant to a Heading node type.
type HeadingData struct {
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
}
@ -122,7 +122,7 @@ type Node struct {
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
CodeBlockData // Populated if Type is CodeBlock
LinkData // Populated if Type is Link
@ -211,7 +211,7 @@ func (n *Node) isContainer() bool {
fallthrough
case Paragraph:
fallthrough
case Header:
case Heading:
fallthrough
case Emph:
fallthrough