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
|
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 ||
|
||||||
|
|
|
@ -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
27
html.go
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue