mirror of
https://github.com/russross/blackfriday.git
synced 2024-03-22 13:40:34 +08:00
Add definition lists extension support
This commit is contained in:
parent
93ae1e873a
commit
c4825a719d
82
block.go
82
block.go
|
@ -166,6 +166,21 @@ func (p *parser) block(out *bytes.Buffer, data []byte) {
|
|||
continue
|
||||
}
|
||||
|
||||
// definition lists:
|
||||
//
|
||||
// Term 1
|
||||
// : Definition a
|
||||
// : Definition b
|
||||
//
|
||||
// Term 2
|
||||
// : Definition c
|
||||
if p.flags&EXTENSION_DEFINITION_LISTS != 0 {
|
||||
if p.dliPrefix(data) > 0 {
|
||||
data = data[p.list(out, data, LIST_TYPE_DEFINITION):]
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// anything else must look like a normal paragraph
|
||||
// note: this finds underlined headers, too
|
||||
data = data[p.paragraph(out, data):]
|
||||
|
@ -1018,6 +1033,20 @@ func (p *parser) oliPrefix(data []byte) int {
|
|||
return i + 2
|
||||
}
|
||||
|
||||
// returns definition list item prefix
|
||||
func (p *parser) dliPrefix(data []byte) int {
|
||||
i := 0
|
||||
|
||||
// need a : followed by a spaces
|
||||
if data[i] != ':' || data[i+1] != ' ' {
|
||||
return 0
|
||||
}
|
||||
for data[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
return i + 2
|
||||
}
|
||||
|
||||
// parse ordered or unordered list block
|
||||
func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int {
|
||||
i := 0
|
||||
|
@ -1053,7 +1082,19 @@ func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int {
|
|||
i = p.oliPrefix(data)
|
||||
}
|
||||
if i == 0 {
|
||||
return 0
|
||||
i = p.dliPrefix(data)
|
||||
// reset definition term flag
|
||||
if i > 0 {
|
||||
*flags &= ^LIST_TYPE_TERM
|
||||
}
|
||||
}
|
||||
if i == 0 {
|
||||
// if in defnition list, set term flag and continue
|
||||
if *flags&LIST_TYPE_DEFINITION != 0 {
|
||||
*flags |= LIST_TYPE_TERM
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// skip leading whitespace on first line
|
||||
|
@ -1063,7 +1104,7 @@ func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int {
|
|||
|
||||
// find the end of the line
|
||||
line := i
|
||||
for data[i-1] != '\n' {
|
||||
for i > 0 && data[i-1] != '\n' {
|
||||
i++
|
||||
}
|
||||
|
||||
|
@ -1107,7 +1148,8 @@ gatherlines:
|
|||
switch {
|
||||
// is this a nested list item?
|
||||
case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) ||
|
||||
p.oliPrefix(chunk) > 0:
|
||||
p.oliPrefix(chunk) > 0 ||
|
||||
p.dliPrefix(chunk) > 0:
|
||||
|
||||
if containsBlankLine {
|
||||
*flags |= LIST_ITEM_CONTAINS_BLOCK
|
||||
|
@ -1138,7 +1180,18 @@ gatherlines:
|
|||
// of this item if it is indented 4 spaces
|
||||
// (regardless of the indentation of the beginning of the item)
|
||||
case containsBlankLine && indent < 4:
|
||||
*flags |= LIST_ITEM_END_OF_LIST
|
||||
if *flags&LIST_TYPE_DEFINITION != 0 && i < len(data)-1 {
|
||||
// is the next item still a part of this list?
|
||||
next := i
|
||||
for data[next] != '\n' {
|
||||
next++
|
||||
}
|
||||
if next < len(data)-2 && data[next+1] != ':' {
|
||||
*flags |= LIST_ITEM_END_OF_LIST
|
||||
}
|
||||
} else {
|
||||
*flags |= LIST_ITEM_END_OF_LIST
|
||||
}
|
||||
break gatherlines
|
||||
|
||||
// a blank line means this should be parsed as a block
|
||||
|
@ -1152,6 +1205,7 @@ gatherlines:
|
|||
if containsBlankLine {
|
||||
containsBlankLine = false
|
||||
raw.WriteByte('\n')
|
||||
|
||||
}
|
||||
|
||||
// add the line into the working buffer without prefix
|
||||
|
@ -1164,8 +1218,8 @@ gatherlines:
|
|||
|
||||
// render the contents of the list item
|
||||
var cooked bytes.Buffer
|
||||
if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 {
|
||||
// intermediate render of block li
|
||||
if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 {
|
||||
// intermediate render of block item, except for definition term
|
||||
if sublist > 0 {
|
||||
p.block(&cooked, rawBytes[:sublist])
|
||||
p.block(&cooked, rawBytes[sublist:])
|
||||
|
@ -1173,7 +1227,7 @@ gatherlines:
|
|||
p.block(&cooked, rawBytes)
|
||||
}
|
||||
} else {
|
||||
// intermediate render of inline li
|
||||
// intermediate render of inline item
|
||||
if sublist > 0 {
|
||||
p.inline(&cooked, rawBytes[:sublist])
|
||||
p.block(&cooked, rawBytes[sublist:])
|
||||
|
@ -1237,6 +1291,13 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
|
|||
|
||||
// did we find a blank line marking the end of the paragraph?
|
||||
if n := p.isEmpty(current); n > 0 {
|
||||
// did this blank line followed by a definition list item?
|
||||
if p.flags&EXTENSION_DEFINITION_LISTS != 0 {
|
||||
if i < len(data)-1 && data[i+1] == ':' {
|
||||
return p.list(out, data[prev:], LIST_TYPE_DEFINITION)
|
||||
}
|
||||
}
|
||||
|
||||
p.renderParagraph(out, data[:i])
|
||||
return i + n
|
||||
}
|
||||
|
@ -1295,6 +1356,13 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
|
|||
return i
|
||||
}
|
||||
|
||||
// if there's a definition list item, prev line is a definition term
|
||||
if p.flags&EXTENSION_DEFINITION_LISTS != 0 {
|
||||
if p.dliPrefix(current) != 0 {
|
||||
return p.list(out, data[prev:], LIST_TYPE_DEFINITION)
|
||||
}
|
||||
}
|
||||
|
||||
// if there's a list after this, paragraph is over
|
||||
if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 {
|
||||
if p.uliPrefix(current) != 0 ||
|
||||
|
|
|
@ -801,6 +801,86 @@ func TestOrderedList(t *testing.T) {
|
|||
doTestsBlock(t, tests, 0)
|
||||
}
|
||||
|
||||
func TestDefinitionList(t *testing.T) {
|
||||
var tests = []string{
|
||||
"Term 1\n: Definition a\n",
|
||||
"<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a \n",
|
||||
"<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a\n: Definition b\n",
|
||||
"<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n<dd>Definition b</dd>\n</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a\n\nTerm 2\n: Definition b\n",
|
||||
"<dl>\n" +
|
||||
"<dt>Term 1</dt>\n" +
|
||||
"<dd>Definition a</dd>\n" +
|
||||
"<dt>Term 2</dt>\n" +
|
||||
"<dd>Definition b</dd>\n" +
|
||||
"</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a\n: Definition b\n\nTerm 2\n: Definition c\n",
|
||||
"<dl>\n" +
|
||||
"<dt>Term 1</dt>\n" +
|
||||
"<dd>Definition a</dd>\n" +
|
||||
"<dd>Definition b</dd>\n" +
|
||||
"<dt>Term 2</dt>\n" +
|
||||
"<dd>Definition c</dd>\n" +
|
||||
"</dl>\n",
|
||||
|
||||
"Term 1\n\n: Definition a\n\nTerm 2\n\n: Definition b\n",
|
||||
"<dl>\n" +
|
||||
"<dt>Term 1</dt>\n" +
|
||||
"<dd><p>Definition a</p></dd>\n" +
|
||||
"<dt>Term 2</dt>\n" +
|
||||
"<dd><p>Definition b</p></dd>\n" +
|
||||
"</dl>\n",
|
||||
|
||||
"Term 1\n\n: Definition a\n\n: Definition b\n\nTerm 2\n\n: Definition c\n",
|
||||
"<dl>\n" +
|
||||
"<dt>Term 1</dt>\n" +
|
||||
"<dd><p>Definition a</p></dd>\n" +
|
||||
"<dd><p>Definition b</p></dd>\n" +
|
||||
"<dt>Term 2</dt>\n" +
|
||||
"<dd><p>Definition c</p></dd>\n" +
|
||||
"</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a\nNext line\n",
|
||||
"<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a\n Next line\n",
|
||||
"<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a \n Next line \n",
|
||||
"<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a\nNext line\n\nTerm 2\n: Definition b",
|
||||
"<dl>\n" +
|
||||
"<dt>Term 1</dt>\n" +
|
||||
"<dd>Definition a\nNext line</dd>\n" +
|
||||
"<dt>Term 2</dt>\n" +
|
||||
"<dd>Definition b</dd>\n" +
|
||||
"</dl>\n",
|
||||
|
||||
"Term 1\n: Definition a\n",
|
||||
"<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n",
|
||||
|
||||
"Term 1\n:Definition a\n",
|
||||
"<p>Term 1\n:Definition a</p>\n",
|
||||
|
||||
"Term 1\n\n: Definition a\n\nTerm 2\n\n: Definition b\n\nText 1",
|
||||
"<dl>\n" +
|
||||
"<dt>Term 1</dt>\n" +
|
||||
"<dd><p>Definition a</p></dd>\n" +
|
||||
"<dt>Term 2</dt>\n" +
|
||||
"<dd><p>Definition b</p></dd>\n" +
|
||||
"</dl>\n" +
|
||||
"\n<p>Text 1</p>\n",
|
||||
}
|
||||
doTestsBlock(t, tests, EXTENSION_DEFINITION_LISTS)
|
||||
}
|
||||
|
||||
func TestPreformattedHtml(t *testing.T) {
|
||||
var tests = []string{
|
||||
"<div></div>\n",
|
||||
|
|
27
html.go
27
html.go
|
@ -375,7 +375,9 @@ func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
|
|||
marker := out.Len()
|
||||
doubleSpace(out)
|
||||
|
||||
if flags&LIST_TYPE_ORDERED != 0 {
|
||||
if flags&LIST_TYPE_DEFINITION != 0 {
|
||||
out.WriteString("<dl>")
|
||||
} else if flags&LIST_TYPE_ORDERED != 0 {
|
||||
out.WriteString("<ol>")
|
||||
} else {
|
||||
out.WriteString("<ul>")
|
||||
|
@ -384,7 +386,9 @@ func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
|
|||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
if flags&LIST_TYPE_ORDERED != 0 {
|
||||
if flags&LIST_TYPE_DEFINITION != 0 {
|
||||
out.WriteString("</dl>\n")
|
||||
} else if flags&LIST_TYPE_ORDERED != 0 {
|
||||
out.WriteString("</ol>\n")
|
||||
} else {
|
||||
out.WriteString("</ul>\n")
|
||||
|
@ -392,12 +396,25 @@ func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
|
|||
}
|
||||
|
||||
func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||
if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) ||
|
||||
flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||
doubleSpace(out)
|
||||
}
|
||||
out.WriteString("<li>")
|
||||
if flags&LIST_TYPE_TERM != 0 {
|
||||
out.WriteString("<dt>")
|
||||
} else if flags&LIST_TYPE_DEFINITION != 0 {
|
||||
out.WriteString("<dd>")
|
||||
} else {
|
||||
out.WriteString("<li>")
|
||||
}
|
||||
out.Write(text)
|
||||
out.WriteString("</li>\n")
|
||||
if flags&LIST_TYPE_TERM != 0 {
|
||||
out.WriteString("</dt>\n")
|
||||
} else if flags&LIST_TYPE_DEFINITION != 0 {
|
||||
out.WriteString("</dd>\n")
|
||||
} else {
|
||||
out.WriteString("</li>\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
|
|
|
@ -44,6 +44,7 @@ const (
|
|||
EXTENSION_TITLEBLOCK // Titleblock ala pandoc
|
||||
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
|
||||
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
|
||||
EXTENSION_DEFINITION_LISTS // render definition lists
|
||||
|
||||
commonHtmlFlags = 0 |
|
||||
HTML_USE_XHTML |
|
||||
|
@ -59,7 +60,8 @@ const (
|
|||
EXTENSION_STRIKETHROUGH |
|
||||
EXTENSION_SPACE_HEADERS |
|
||||
EXTENSION_HEADER_IDS |
|
||||
EXTENSION_BACKSLASH_LINE_BREAK
|
||||
EXTENSION_BACKSLASH_LINE_BREAK |
|
||||
EXTENSION_DEFINITION_LISTS
|
||||
)
|
||||
|
||||
// These are the possible flag values for the link renderer.
|
||||
|
@ -76,6 +78,8 @@ const (
|
|||
// These are mostly of interest if you are writing a new output format.
|
||||
const (
|
||||
LIST_TYPE_ORDERED = 1 << iota
|
||||
LIST_TYPE_DEFINITION
|
||||
LIST_TYPE_TERM
|
||||
LIST_ITEM_CONTAINS_BLOCK
|
||||
LIST_ITEM_BEGINNING_OF_LIST
|
||||
LIST_ITEM_END_OF_LIST
|
||||
|
|
Loading…
Reference in New Issue
Block a user