Add definition lists extension support
@@ -166,6 +166,21 @@ data = data[p.list(out, data, LIST_TYPE_ORDERED):]
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 @@ }
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 @@ if i == 0 {
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 @@ }
// 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 @@ // evaluate how this line fits in
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 @@ // anything following an empty line is only part
// 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 @@ // re-introduce the blank into the buffer
if containsBlankLine { containsBlankLine = false raw.WriteByte('\n') + } // add the line into the working buffer without prefix@@ -1164,8 +1218,8 @@ rawBytes := raw.Bytes()
// 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 @@ } else {
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 @@ line = i
// 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 }@@ -1293,6 +1354,13 @@ // if there's a prefixed header or a horizontal rule after this, paragraph is over
if p.isPrefixHeader(current) || p.isHRule(current) { p.renderParagraph(out, data[: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
@@ -801,6 +801,86 @@ }
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",
@@ -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 @@ if !text() {
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) 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 @@ EXTENSION_HEADER_IDS // specify header IDs with {#id}
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 @@ EXTENSION_AUTOLINK |
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 @@ // Multiple flag values may be ORed together.
// 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