From c4825a719d3ad79436b3961e52b4fad8428c8bf0 Mon Sep 17 00:00:00 2001 From: Vincent Batoufflet Date: Fri, 29 May 2015 13:30:49 +0200 Subject: [PATCH] Add definition lists extension support --- block.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++----- block_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ html.go | 27 +++++++++++++---- markdown.go | 6 +++- 4 files changed, 182 insertions(+), 13 deletions(-) diff --git a/block.go b/block.go index c9bf2ff..6e61351 100644 --- a/block.go +++ b/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 || diff --git a/block_test.go b/block_test.go index ddcf0ff..4819c6b 100644 --- a/block_test.go +++ b/block_test.go @@ -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", + "
\n
Term 1
\n
Definition a
\n
\n", + + "Term 1\n: Definition a \n", + "
\n
Term 1
\n
Definition a
\n
\n", + + "Term 1\n: Definition a\n: Definition b\n", + "
\n
Term 1
\n
Definition a
\n
Definition b
\n
\n", + + "Term 1\n: Definition a\n\nTerm 2\n: Definition b\n", + "
\n" + + "
Term 1
\n" + + "
Definition a
\n" + + "
Term 2
\n" + + "
Definition b
\n" + + "
\n", + + "Term 1\n: Definition a\n: Definition b\n\nTerm 2\n: Definition c\n", + "
\n" + + "
Term 1
\n" + + "
Definition a
\n" + + "
Definition b
\n" + + "
Term 2
\n" + + "
Definition c
\n" + + "
\n", + + "Term 1\n\n: Definition a\n\nTerm 2\n\n: Definition b\n", + "
\n" + + "
Term 1
\n" + + "

Definition a

\n" + + "
Term 2
\n" + + "

Definition b

\n" + + "
\n", + + "Term 1\n\n: Definition a\n\n: Definition b\n\nTerm 2\n\n: Definition c\n", + "
\n" + + "
Term 1
\n" + + "

Definition a

\n" + + "

Definition b

\n" + + "
Term 2
\n" + + "

Definition c

\n" + + "
\n", + + "Term 1\n: Definition a\nNext line\n", + "
\n
Term 1
\n
Definition a\nNext line
\n
\n", + + "Term 1\n: Definition a\n Next line\n", + "
\n
Term 1
\n
Definition a\nNext line
\n
\n", + + "Term 1\n: Definition a \n Next line \n", + "
\n
Term 1
\n
Definition a\nNext line
\n
\n", + + "Term 1\n: Definition a\nNext line\n\nTerm 2\n: Definition b", + "
\n" + + "
Term 1
\n" + + "
Definition a\nNext line
\n" + + "
Term 2
\n" + + "
Definition b
\n" + + "
\n", + + "Term 1\n: Definition a\n", + "
\n
Term 1
\n
Definition a
\n
\n", + + "Term 1\n:Definition a\n", + "

Term 1\n:Definition a

\n", + + "Term 1\n\n: Definition a\n\nTerm 2\n\n: Definition b\n\nText 1", + "
\n" + + "
Term 1
\n" + + "

Definition a

\n" + + "
Term 2
\n" + + "

Definition b

\n" + + "
\n" + + "\n

Text 1

\n", + } + doTestsBlock(t, tests, EXTENSION_DEFINITION_LISTS) +} + func TestPreformattedHtml(t *testing.T) { var tests = []string{ "
\n", diff --git a/html.go b/html.go index 4c31cac..264aae5 100644 --- a/html.go +++ b/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("
") + } else if flags&LIST_TYPE_ORDERED != 0 { out.WriteString("
    ") } else { out.WriteString("
      ") @@ -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("
\n") + } else if flags&LIST_TYPE_ORDERED != 0 { out.WriteString("\n") } else { out.WriteString("\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("
  • ") + if flags&LIST_TYPE_TERM != 0 { + out.WriteString("
    ") + } else if flags&LIST_TYPE_DEFINITION != 0 { + out.WriteString("
    ") + } else { + out.WriteString("
  • ") + } out.Write(text) - out.WriteString("
  • \n") + if flags&LIST_TYPE_TERM != 0 { + out.WriteString("\n") + } else if flags&LIST_TYPE_DEFINITION != 0 { + out.WriteString("
    \n") + } else { + out.WriteString("
  • \n") + } } func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) { diff --git a/markdown.go b/markdown.go index fcb8ac2..3ca79d9 100644 --- a/markdown.go +++ b/markdown.go @@ -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