Add definition lists extension support

This commit is contained in:
Vincent Batoufflet 2015-05-29 13:30:49 +02:00
parent 93ae1e873a
commit c4825a719d
4 changed files with 182 additions and 13 deletions

View File

@ -166,6 +166,21 @@ func (p *parser) block(out *bytes.Buffer, data []byte) {
continue 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 // anything else must look like a normal paragraph
// note: this finds underlined headers, too // note: this finds underlined headers, too
data = data[p.paragraph(out, data):] data = data[p.paragraph(out, data):]
@ -1018,6 +1033,20 @@ func (p *parser) oliPrefix(data []byte) int {
return i + 2 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 // parse ordered or unordered list block
func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int { func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int {
i := 0 i := 0
@ -1053,7 +1082,19 @@ func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int {
i = p.oliPrefix(data) i = p.oliPrefix(data)
} }
if i == 0 { 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 // 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 // find the end of the line
line := i line := i
for data[i-1] != '\n' { for i > 0 && data[i-1] != '\n' {
i++ i++
} }
@ -1107,7 +1148,8 @@ gatherlines:
switch { switch {
// is this a nested list item? // is this a nested list item?
case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) || case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) ||
p.oliPrefix(chunk) > 0: p.oliPrefix(chunk) > 0 ||
p.dliPrefix(chunk) > 0:
if containsBlankLine { if containsBlankLine {
*flags |= LIST_ITEM_CONTAINS_BLOCK *flags |= LIST_ITEM_CONTAINS_BLOCK
@ -1138,7 +1180,18 @@ gatherlines:
// of this item if it is indented 4 spaces // of this item if it is indented 4 spaces
// (regardless of the indentation of the beginning of the item) // (regardless of the indentation of the beginning of the item)
case containsBlankLine && indent < 4: 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 break gatherlines
// a blank line means this should be parsed as a block // a blank line means this should be parsed as a block
@ -1152,6 +1205,7 @@ gatherlines:
if containsBlankLine { if containsBlankLine {
containsBlankLine = false containsBlankLine = false
raw.WriteByte('\n') raw.WriteByte('\n')
} }
// add the line into the working buffer without prefix // add the line into the working buffer without prefix
@ -1164,8 +1218,8 @@ gatherlines:
// render the contents of the list item // render the contents of the list item
var cooked bytes.Buffer var cooked bytes.Buffer
if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 { if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 {
// intermediate render of block li // intermediate render of block item, except for definition term
if sublist > 0 { if sublist > 0 {
p.block(&cooked, rawBytes[:sublist]) p.block(&cooked, rawBytes[:sublist])
p.block(&cooked, rawBytes[sublist:]) p.block(&cooked, rawBytes[sublist:])
@ -1173,7 +1227,7 @@ gatherlines:
p.block(&cooked, rawBytes) p.block(&cooked, rawBytes)
} }
} else { } else {
// intermediate render of inline li // intermediate render of inline item
if sublist > 0 { if sublist > 0 {
p.inline(&cooked, rawBytes[:sublist]) p.inline(&cooked, rawBytes[:sublist])
p.block(&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? // did we find a blank line marking the end of the paragraph?
if n := p.isEmpty(current); n > 0 { 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]) p.renderParagraph(out, data[:i])
return i + n return i + n
} }
@ -1295,6 +1356,13 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
return i 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 there's a list after this, paragraph is over
if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 { if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 {
if p.uliPrefix(current) != 0 || if p.uliPrefix(current) != 0 ||

View File

@ -801,6 +801,86 @@ func TestOrderedList(t *testing.T) {
doTestsBlock(t, tests, 0) 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) { func TestPreformattedHtml(t *testing.T) {
var tests = []string{ var tests = []string{
"<div></div>\n", "<div></div>\n",

27
html.go
View File

@ -375,7 +375,9 @@ func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
marker := out.Len() marker := out.Len()
doubleSpace(out) 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>") out.WriteString("<ol>")
} else { } else {
out.WriteString("<ul>") out.WriteString("<ul>")
@ -384,7 +386,9 @@ func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
out.Truncate(marker) out.Truncate(marker)
return 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") out.WriteString("</ol>\n")
} else { } else {
out.WriteString("</ul>\n") 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) { 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) 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.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) { func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {

View File

@ -44,6 +44,7 @@ const (
EXTENSION_TITLEBLOCK // Titleblock ala pandoc EXTENSION_TITLEBLOCK // Titleblock ala pandoc
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
EXTENSION_DEFINITION_LISTS // render definition lists
commonHtmlFlags = 0 | commonHtmlFlags = 0 |
HTML_USE_XHTML | HTML_USE_XHTML |
@ -59,7 +60,8 @@ const (
EXTENSION_STRIKETHROUGH | EXTENSION_STRIKETHROUGH |
EXTENSION_SPACE_HEADERS | EXTENSION_SPACE_HEADERS |
EXTENSION_HEADER_IDS | EXTENSION_HEADER_IDS |
EXTENSION_BACKSLASH_LINE_BREAK EXTENSION_BACKSLASH_LINE_BREAK |
EXTENSION_DEFINITION_LISTS
) )
// These are the possible flag values for the link renderer. // 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. // These are mostly of interest if you are writing a new output format.
const ( const (
LIST_TYPE_ORDERED = 1 << iota LIST_TYPE_ORDERED = 1 << iota
LIST_TYPE_DEFINITION
LIST_TYPE_TERM
LIST_ITEM_CONTAINS_BLOCK LIST_ITEM_CONTAINS_BLOCK
LIST_ITEM_BEGINNING_OF_LIST LIST_ITEM_BEGINNING_OF_LIST
LIST_ITEM_END_OF_LIST LIST_ITEM_END_OF_LIST