if data[0] == '<' {
if i := p.html(out, data, true); i > 0 {
data = data[i:]
continue
}
}
// title block in TOML
//
// % stuff = "foo"
// % port = 1024
if p.flags&EXTENSION_TITLEBLOCK_TOML != 0 && len(data) > 2 {
// only one % at the left
if data[0] == '%' && data[1] != '%' {
if i := p.titleBlock(out, data, true); i > 0 {
data = data[i:]
continue
}
}
}
// title block in TOML, second way to typeset
//
// %%%
// stuff = "foot"
// port = 1024
// %%%
if p.flags&EXTENSION_TITLEBLOCK_TOML != 0 && len(data) > 3 {
if data[0] == '%' && data[1] == '%' && data[2] == '%' {
if i := p.titleBlockBlock(out, data, true); i > 0 {
data = data[i:]
continue
}
}
}
// blank lines. note: returns the # of bytes to skip
if i := p.isEmpty(data); i > 0 {
data = data[i:]
continue
}
// indented code block:
//
// func max(a, b int) int {
// if a > b {
// return a
// }
// return b
// }
if p.codePrefix(data) > 0 {
data = data[p.code(out, data):]
continue
}
// fenced code block:
//
// ``` go
// func fact(n int) int {
// if n <= 1 {
// return n
// }
// return n * fact(n-1)
// }
// ```
if p.flags&EXTENSION_FENCED_CODE != 0 {
if i := p.fencedCode(out, data, true); i > 0 {
data = data[i:]
continue
}
}
// horizontal rule:
//
// ------
// or
// ******
// or
// ______
if p.isHRule(data) {
p.r.HRule(out)
var i int
for i = 0; data[i] != '\n'; i++ {
}
data = data[i:]
continue
}
// Aside quote:
//
// A> This is an aside
// A> I found on the web
if p.asidePrefix(data) > 0 {
data = data[p.aside(out, data):]
continue
}
// Figure "quote":
//
// F> 
// F> 
// Figure: Caption.
if p.figurePrefix(data) > 0 {
data = data[p.figure(out, data):]
continue
}
// block quote:
//
// > A big quote I found somewhere
// > on the web
if p.quotePrefix(data) > 0 {
data = data[p.quote(out, data):]
continue
}
// table:
//
// Name | Age | Phone
// ------|-----|---------
// Bob | 31 | 555-1234
// Alice | 27 | 555-4321
// Table: this is a caption
if p.flags&EXTENSION_TABLES != 0 {
if i := p.table(out, data); i > 0 {
data = data[i:]
continue
}
}
// block table:
// (cells contain block elements)
//
// |-------|-----|---------
// | Name | Age | Phone
// | ------|-----|---------
// | Bob | 31 | 555-1234
// | Alice | 27 | 555-4321
// |-------|-----|---------
// | Bob | 31 | 555-1234
// | Alice | 27 | 555-4321
if p.flags&EXTENSION_TABLES != 0 {
if i := p.blockTable(out, data); i > 0 {
data = data[i:]
continue
}
}
// a definition list:
//
// Item1
// : Definition1
// Item2
// : Definition2
if p.dliPrefix(data) > 0 {
p.insideDefinitionList = true
data = data[p.list(out, data, _LIST_TYPE_DEFINITION, 0, nil):]
p.insideDefinitionList = false
}
// an itemized/unordered list:
//
// * Item 1
// * Item 2
//
// also works with + or -
if p.uliPrefix(data) > 0 {
data = data[p.list(out, data, 0, 0, nil):]
continue
}
// a numbered/ordered list:
//
// 1. Item 1
// 2. Item 2
if i := p.oliPrefix(data); i > 0 {
start := 0
if i > 2 {
start, _ = strconv.Atoi(string(data[:i-2])) // this cannot fail because we just est. the thing *is* a number, and if it does start is zero anyway.
}
data = data[p.list(out, data, _LIST_TYPE_ORDERED, start, nil):]
continue
}
// a numberd/ordered list:
//
// ii. Item 1
// ii. Item 2
if p.rliPrefix(data) > 0 {
data = data[p.list(out, data, _LIST_TYPE_ORDERED|_LIST_TYPE_ORDERED_ROMAN_LOWER, 0, nil):]
continue
}
// a numberd/ordered list:
//
// II. Item 1
// II. Item 2
if p.rliPrefixU(data) > 0 {
data = data[p.list(out, data, _LIST_TYPE_ORDERED|_LIST_TYPE_ORDERED_ROMAN_UPPER, 0, nil):]
continue
}
// a numberd/ordered list:
//
// a. Item 1
// b. Item 2
if p.aliPrefix(data) > 0 {
data = data[p.list(out, data, _LIST_TYPE_ORDERED|_LIST_TYPE_ORDERED_ALPHA_LOWER, 0, nil):]
continue
}
// a numberd/ordered list:
//
// A. Item 1
// B. Item 2
if p.aliPrefixU(data) > 0 {
data = data[p.list(out, data, _LIST_TYPE_ORDERED|_LIST_TYPE_ORDERED_ALPHA_UPPER, 0, nil):]
continue
}
// an example lists:
//
// (@good) Item1
// (@good) Item2
if i := p.eliPrefix(data); i > 0 {
group := data[2 : i-2]
data = data[p.list(out, data, _LIST_TYPE_ORDERED|_LIST_TYPE_ORDERED_GROUP, 0, group):]
continue
}
// anything else must look like a normal paragraph
// note: this finds underlined headers, too
data = data[p.paragraph(out, data):]
}
p.nesting--
}
func (p *parser) isPrefixHeader(data []byte) bool {
// CommonMark: up to three spaces allowed
k := 0
for k < len(data) && data[k] == ' ' {
k++
}
if k == len(data) || k > 3 {
return false
}
data = data[k:]
if data[0] != '#' {
return false
}
if p.flags&EXTENSION_SPACE_HEADERS != 0 {
level := 0
for level < 6 && data[level] == '#' {
level++
}
if !iswhitespace(data[level]) {
return false
}
}
return true
}
func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int {
// CommonMark: up to three spaces allowed
k := 0
for k < len(data) && data[k] == ' ' {
k++
}
if k == len(data) || k > 3 {
return 0
}
data = data[k:]
level := 0
for level < 6 && data[level] == '#' {
level++
}
i, end := 0, 0
for i = level; iswhitespace(data[i]); i++ {
}
for end = i; data[end] != '\n'; end++ {
}
skip := end
id := ""
if p.flags&EXTENSION_HEADER_IDS != 0 {
j, k := 0, 0
// find start/end of header id
for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
}
for k = j + 1; k < end && data[k] != '}'; k++ {
}
// extract header id iff found
if j < end && k < end {
id = string(data[j+2 : k])
end = j
skip = k + 1
for end > 0 && data[end-1] == ' ' {
end--
}
}
}
// CommonMark spaces *after* the header
for end > 0 && data[end-1] == ' ' {
end--
}
for end > 0 && data[end-1] == '#' {
// CommonMark: a # directly following the header name is allowed and we
// should keep it
if end > 1 && data[end-2] != '#' && !iswhitespace(data[end-2]) {
end++
break
}
end--
}
for end > 0 && iswhitespace(data[end-1]) {
end--
}
if end > i {
if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
id = createSanitizedAnchorName(string(data[i:end]))
}
work := func() bool {
p.inline(out, data[i:end])
return true
}
if id != "" {
if v, ok := p.anchors[id]; ok && p.flags&EXTENSION_UNIQUE_HEADER_IDS != 0 {
p.anchors[id]++
// anchor found
id += "-" + strconv.Itoa(v)
} else {
p.anchors[id] = 1
}
}
p.r.SetInlineAttr(p.ial)
p.ial = nil
p.r.Header(out, work, level, id)
}
return skip + k
}
func (p *parser) isUnderlinedHeader(data []byte) int {
// test of level 1 header
if data[0] == '=' {
i := 1
for data[i] == '=' {
i++
}
for iswhitespace(data[i]) {
i++
}
if data[i] == '\n' {
return 1
}
return 0
}
// test of level 2 header
if data[0] == '-' {
i := 1
for data[i] == '-' {
i++
}
for iswhitespace(data[i]) {
i++
}
if data[i] == '\n' {
return 2
}
return 0
}
return 0
}
func (p *parser) isPartHeader(data []byte) bool {
k := 0
for k < len(data) && data[k] == ' ' {
k++
}
if k == len(data) || k > 3 {
return false
}
data = data[k:]
if len(data) < 3 {
return false
}
if data[0] != '-' || data[1] != '#' {
return false
}
if p.flags&EXTENSION_SPACE_HEADERS != 0 {
if !iswhitespace(data[2]) {
return false
}
}
return true
}
func (p *parser) isSpecialHeader(data []byte) bool {
k := 0
for k < len(data) && data[k] == ' ' {
k++
}
if k == len(data) || k > 3 {
return false
}
data = data[k:]
if len(data) < 3 {
return false
}
if data[0] != '.' || data[1] != '#' {
return false
}
if p.flags&EXTENSION_SPACE_HEADERS != 0 {
if !iswhitespace(data[2]) {
return false
}
}
return true
}
func (p *parser) specialHeader(out *bytes.Buffer, data []byte) int {
k := 0
for k < len(data) && data[k] == ' ' {
k++
}
if k == len(data) || k > 3 {
return 0
}
data = data[k:]
if len(data) < 3 {
return 0
}
if data[0] != '.' || data[1] != '#' {
return 0
}
i, end := 0, 0
for i = 2; iswhitespace(data[i]); i++ {
}
for end = i; data[end] != '\n'; end++ {
}
skip := end
id := ""
if p.flags&EXTENSION_HEADER_IDS != 0 {
j, k := 0, 0
// find start/end of header id
for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
}
for k = j + 1; k < end && data[k] != '}'; k++ {
}
// extract header id iff found
if j < end && k < end {
id = string(data[j+2 : k])
end = j
skip = k + 1
for end > 0 && data[end-1] == ' ' {
end--
}
}
}
// CommonMark spaces *after* the header
for end > 0 && data[end-1] == ' ' {
end--
}
// Remove this, not true for this header
for end > 0 && data[end-1] == '#' {
// CommonMark: a # directly following the header name is allowed and we
// should keep it
if end > 1 && data[end-2] != '#' && !iswhitespace(data[end-2]) {
end++
break
}
end--
}
for end > 0 && iswhitespace(data[end-1]) {
end--
}
if end > i {
if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
id = createSanitizedAnchorName(string(data[i:end]))
}
work := func() bool {
p.inline(out, data[i:end])
return true
}
p.r.SetInlineAttr(p.ial)
p.ial = nil
name := bytes.ToLower(data[i:end])
switch {
case bytes.Compare(name, []byte("abstract")) == 0:
fallthrough
case bytes.Compare(name, []byte("preface")) == 0:
name := bytes.ToLower(data[i:end])
if id != "" {
if v, ok := p.anchors[id]; ok && p.flags&EXTENSION_UNIQUE_HEADER_IDS != 0 {
p.anchors[id]++
// anchor found
id += "-" + strconv.Itoa(v)
} else {
p.anchors[id] = 1
}
}
p.r.SpecialHeader(out, name, work, id)
default: // A note section
// There is no id for notes, but we still give it to the method.
p.r.Note(out, work, id)
}
}
return skip + k
}
func (p *parser) partHeader(out *bytes.Buffer, data []byte) int {
k := 0
for k < len(data) && data[k] == ' ' {
k++
}
if k == len(data) || k > 3 {
return 0
}
data = data[k:]
if len(data) < 3 {
return 0
}
if data[0] != '-' || data[1] != '#' {
return 0
}
i, end := 0, 0
for i = 2; iswhitespace(data[i]); i++ {
}
for end = i; data[end] != '\n'; end++ {
}
skip := end
id := ""
if p.flags&EXTENSION_HEADER_IDS != 0 {
j, k := 0, 0
// find start/end of header id
for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
}
for k = j + 1; k < end && data[k] != '}'; k++ {
}
// extract header id iff found
if j < end && k < end {
id = string(data[j+2 : k])
end = j
skip = k + 1
for end > 0 && data[end-1] == ' ' {
end--
}
}
}
// CommonMark spaces *after* the header
for end > 0 && data[end-1] == ' ' {
end--
}
for end > 0 && data[end-1] == '#' {
// CommonMark: a # directly following the header name is allowed and we
// should keep it
if end > 1 && data[end-2] != '#' && !iswhitespace(data[end-2]) {
end++
break
}
end--
}
for end > 0 && iswhitespace(data[end-1]) {
end--
}
if end > i {
if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
id = createSanitizedAnchorName(string(data[i:end]))
}
work := func() bool {
p.inline(out, data[i:end])
return true
}
if id != "" {
if v, ok := p.anchors[id]; ok && p.flags&EXTENSION_UNIQUE_HEADER_IDS != 0 {
p.anchors[id]++
// anchor found
id += "-" + strconv.Itoa(v)
} else {
p.anchors[id] = 1
}
}
p.r.SetInlineAttr(p.ial)
p.ial = nil
p.r.Part(out, work, id)
}
return skip + k
}
func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int {
if p.titleblock {
return 0
}
if data[0] != '%' {
return 0
}
splitData := bytes.Split(data, []byte("\n"))
var i int
for idx, b := range splitData {
if !bytes.HasPrefix(b, []byte("%")) {
i = idx // - 1
break
}
}
p.titleblock = true
data = bytes.Join(splitData[0:i], []byte("\n"))
block := p.titleBlockTOML(out, data)
p.r.TitleBlockTOML(out, &block)
return len(data)
}
func (p *parser) titleBlockBlock(out *bytes.Buffer, data []byte, doRender bool) int {
if p.titleblock {
return 0
}
if data[0] != '%' || data[1] != '%' || data[2] != '%' {
return 0
}
// find current eol
i := 0
for i < len(data) && data[i] != '\n' {
i++
}
beg := i
delimLength := 0
for i < len(data) {
if delimLength = p.isTOMLBlockBlock(data[i:]); delimLength > 0 {
break
}
i++
}
end := i
p.titleblock = true
data = data[beg:end]
block := p.titleBlockTOML(out, data)
p.r.TitleBlockTOML(out, &block)
return len(data) + delimLength + beg
}
func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int {
var i, j int
// identify the opening tag
if data[0] != '<' {
return 0
}
curtag, tagfound := p.htmlFindTag(data[1:])
// handle special cases
if !tagfound {
// check for an HTML comment
if size := p.htmlComment(out, data, doRender); size > 0 {
return size
}
// check for an tag
if size := p.htmlHr(out, data, doRender); size > 0 {
return size
}
// check for HTML CDATA
if size := p.htmlCDATA(out, data, doRender); size > 0 {
return size
}
// check for an
if size := p.htmlReference(out, data, doRender); size > 0 {
return size
}
// no special case recognized
return 0
}
// look for an unindented matching closing tag
// followed by a blank line
found := false
// if not found, try a second pass looking for indented match
// but not if tag is "ins" or "del" (following original Markdown.pl)
if !found && curtag != "ins" && curtag != "del" {
i = 1
for i < len(data) {
i++
for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
i++
}
if i+2+len(curtag) >= len(data) {
break
}
j = p.htmlFindEnd(curtag, data[i-1:])
if j > 0 {
i += j - 1
found = true
break
}
}
}
if !found {
return 0
}
// the end of the block has been found
if doRender {
// trim newlines
end := i
for end > 0 && data[end-1] == '\n' {
end--
}
p.r.BlockHtml(out, data[:end])
}
return i
}
func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRender bool) int {
// html block needs to end with a blank line
if i := p.isEmpty(data[start:]); i > 0 {
size := start + i
if doRender {
// trim trailing newlines
end := size
for end > 0 && data[end-1] == '\n' {
end--
}
// breaks the tests if we parse this
// var cooked bytes.Buffer
// p.inline(&cooked, data[:end])
p.r.SetInlineAttr(p.ial)
p.ial = nil
p.r.CommentHtml(out, data[:end])
}
return size
}
return 0
}
// HTML comment, lax form
func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int {
i := p.inlineHTMLComment(out, data)
return p.renderHTMLBlock(out, data, i, doRender)
}
// HTML CDATA section
func (p *parser) htmlCDATA(out *bytes.Buffer, data []byte, doRender bool) int {
const cdataTag = "') {
i++
}
i++
// no end-of-comment marker
if i >= len(data) {
return 0
}
return p.renderHTMLBlock(out, data, i, doRender)
}
// HR, which is the only self-closing block tag considered
func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int {
if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
return 0
}
if data[3] != ' ' && data[3] != '/' && data[3] != '>' {
// not an tag after all; at least not a valid one
return 0
}
i := 3
for data[i] != '>' && data[i] != '\n' {
i++
}
if data[i] == '>' {
return p.renderHTMLBlock(out, data, i+1, doRender)
}
return 0
}
// HTML reference, actually xml, but keep the spirit and call it html
func (p *parser) htmlReference(out *bytes.Buffer, data []byte, doRender bool) int {
if !bytes.HasPrefix(data, []byte("') {
i++
}
i++
// no end-of-reference marker
if i >= len(data) {
return 0
}
// needs to end with a blank line
if j := p.isEmpty(data[i:]); j > 0 {
size := i + j
if doRender {
// trim trailing newlines
end := size
for end > 0 && data[end-1] == '\n' {
end--
}
anchor := bytes.Index(data[:end], []byte("anchor="))
if anchor == -1 {
// nothing found, not a real reference
return 0
}
// look for the some tag after anchor=
open := data[anchor+7]
i := anchor + 7 + 2
for i < end && data[i-1] != open {
i++
}
if i >= end {
return 0
}
anchorStr := string(data[anchor+7+1 : i-1])
if c, ok := p.citations[anchorStr]; !ok {
p.citations[anchorStr] = &citation{xml: data[:end]}
} else {
c.xml = data[:end]
}
}
return size
}
return 0
}
func (p *parser) htmlFindTag(data []byte) (string, bool) {
i := 0
for isalnum(data[i]) {
i++
}
key := string(data[:i])
if _, ok := blockTags[key]; ok {
return key, true
}
return "", false
}
func (p *parser) htmlFindEnd(tag string, data []byte) int {
// assume data[0] == '<' && data[1] == '/' already tested
// check if tag is a match
closetag := []byte("" + tag + ">")
if !bytes.HasPrefix(data, closetag) {
return 0
}
i := len(closetag)
// check that the rest of the line is blank
skip := 0
if skip = p.isEmpty(data[i:]); skip == 0 {
return 0
}
i += skip
skip = 0
if i >= len(data) {
return i
}
if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
return i
}
if skip = p.isEmpty(data[i:]); skip == 0 {
// following line must be blank
return 0
}
return i + skip
}
func (p *parser) isEmpty(data []byte) int {
// it is okay to call isEmpty on an empty buffer
if len(data) == 0 {
return 0
}
var i int
for i = 0; i < len(data) && data[i] != '\n' && data[i] != '\r'; i++ {
if data[i] != ' ' && data[i] != '\t' {
return 0
}
}
return i + 1
}
func (p *parser) isHRule(data []byte) bool {
i := 0
// skip up to three spaces
for i < 3 && data[i] == ' ' {
i++
}
// look at the hrule char
if data[i] != '*' && data[i] != '-' && data[i] != '_' {
return false
}
c := data[i]
// the whole line must be the char or whitespace
n := 0
for data[i] != '\n' {
switch {
case data[i] == c:
n++
case data[i] != ' ':
return false
}
i++
}
return n >= 3
}
func (p *parser) isFencedCode(data []byte, syntax **string, oldmarker string) (skip int, marker string) {
i, size := 0, 0
skip = 0
// skip up to three spaces
for i < len(data) && i < 3 && data[i] == ' ' {
i++
}
if i >= len(data) {
return
}
// check for the marker characters: ~ or `
if data[i] != '~' && data[i] != '`' {
return
}
c := data[i]
// the whole line must be the same char or whitespace
for i < len(data) && data[i] == c {
size++
i++
}
if i >= len(data) {
return
}
// the marker char must occur at least 3 times
if size < 3 {
return
}
marker = string(data[i-size : i])
// if this is the end marker, it must match the beginning marker
if oldmarker != "" && marker != oldmarker {
return
}
if syntax != nil {
syn := 0
i = skipChar(data, i, ' ')
if i >= len(data) {
return
}
syntaxStart := i
if data[i] == '{' {
i++
syntaxStart++
for i < len(data) && data[i] != '}' && data[i] != '\n' {
syn++
i++
}
if i >= len(data) && data[i] != '}' {
return
}
// strip all whitespace at the beginning and the end
// of the {} block
for syn > 0 && isspace(data[syntaxStart]) {
syntaxStart++
syn--
}
for syn > 0 && isspace(data[syntaxStart+syn-1]) {
syn--
}
i++
} else {
for i < len(data) && !isspace(data[i]) {
syn++
i++
}
}
language := string(data[syntaxStart : syntaxStart+syn])
*syntax = &language
}
i = skipChar(data, i, ' ')
if i >= len(data) || data[i] != '\n' {
return
}
skip = i + 1
return
}
func (p *parser) isTOMLBlockBlock(data []byte) int {
if len(data) < 3 {
return 0
}
if data[0] != '%' || data[1] != '%' || data[2] != '%' {
return 0
}
i := 0
for data[i] != '\n' {
switch {
case data[i] == '%':
i++
case data[i] != ' ':
return 0
}
i++
}
return i
}
func (p *parser) fencedCode(out *bytes.Buffer, data []byte, doRender bool) int {
var lang *string
beg, marker := p.isFencedCode(data, &lang, "")
if beg == 0 || beg >= len(data) {
return 0
}
co := ""
if p.ial != nil {
// enabled, any non-empty value
co = p.ial.Value("callout")
}
// CommonMark: if indented strip this many leading spaces from code block
indent := 0
for indent < beg && data[indent] == ' ' {
indent++
}
var work bytes.Buffer
for {
// safe to assume beg < len(data)
// check for the end of the code block
fenceEnd, _ := p.isFencedCode(data[beg:], nil, marker)
if fenceEnd != 0 {
beg += fenceEnd
break
}
// copy the current line
end := beg
for end < len(data) && data[end] != '\n' {
end++
}
end++
// did we reach the end of the buffer without a closing marker?
if end >= len(data) {
return 0
}
// CommmonMark, strip beginning spaces
s := 0
for s < indent && data[beg] == ' ' {
beg++
s++
}
// verbatim copy to the working buffer
if doRender {
work.Write(data[beg:end])
}
beg = end
}
var caption bytes.Buffer
line := beg
j := beg
if bytes.HasPrefix(bytes.TrimSpace(data[j:]), []byte("Figure: ")) {
for line < len(data) {
j++
// find the end of this line
for data[j-1] != '\n' {
j++
}
if p.isEmpty(data[line:j]) > 0 {
break
}
line = j
}
if beg+8 < j-1 {
p.inline(&caption, data[beg+8:j-1])
}
}
syntax := ""
if lang != nil {
syntax = *lang
}
if doRender {
p.r.SetInlineAttr(p.ial)
p.ial = nil
if co != "" {
var callout bytes.Buffer
callouts(p, &callout, work.Bytes(), 0, co)
p.r.BlockCode(out, callout.Bytes(), syntax, caption.Bytes(), p.insideFigure, true)
} else {
p.callouts = nil
p.r.BlockCode(out, work.Bytes(), syntax, caption.Bytes(), p.insideFigure, false)
}
}
return j
}
func (p *parser) table(out *bytes.Buffer, data []byte) int {
var (
header bytes.Buffer
body bytes.Buffer
footer bytes.Buffer
)
i, columns := p.tableHeader(&header, data)
if i == 0 {
return 0
}
foot := false
for i < len(data) {
if j := p.isTableFooter(data[i:]); j > 0 && !foot {
foot = true
i += j
continue
}
pipes, rowStart := 0, i
for ; data[i] != '\n'; i++ {
if data[i] == '|' {
pipes++
}
}
if pipes == 0 {
i = rowStart
break
}
// include the newline in data sent to tableRow
i++
if foot {
p.tableRow(&footer, data[rowStart:i], columns, false)
continue
}
p.tableRow(&body, data[rowStart:i], columns, false)
}
var caption bytes.Buffer
line := i
j := i
if bytes.HasPrefix(data[j:], []byte("Table: ")) {
for line < len(data) {
j++
// find the end of this line
for data[j-1] != '\n' {
j++
}
if p.isEmpty(data[line:j]) > 0 {
break
}
line = j
}
if i+7 < j-1 {
p.inline(&caption, data[i+7:j-1]) // +7 for 'Table: '
}
}
p.r.SetInlineAttr(p.ial)
p.ial = nil
p.r.Table(out, header.Bytes(), body.Bytes(), footer.Bytes(), columns, caption.Bytes())
return j
}
func (p *parser) blockTable(out *bytes.Buffer, data []byte) int {
var (
header bytes.Buffer
body bytes.Buffer
footer bytes.Buffer
rowWork bytes.Buffer
)
i := p.isBlockTableHeader(data)
if i == 0 || i == len(data) {
return 0
}
j, columns := p.tableHeader(&header, data[i:])
i += j
// each cell in a row gets multiple lines which we store per column, we
// process the buffers when we see a row separator
bodies := make([]bytes.Buffer, len(columns))
colspans := make([]int, len(columns))
foot := false
j = 0
for i < len(data) {
if j = p.isTableFooter(data[i:]); j > 0 && !foot {
// prepare previous ones
foot = true
i += j
continue
}
if j = p.isRowSeperator(data[i:]); j > 0 {
switch foot {
case false: // separator before any footer
var cellWork bytes.Buffer
colSpanSkip := 0
for c := 0; c < len(columns); c++ {
cellWork.Truncate(0)
if bodies[c].Len() > 0 {
p.block(&cellWork, bodies[c].Bytes())
bodies[c].Truncate(0)
}
if colSpanSkip == 0 {
p.r.TableCell(&rowWork, cellWork.Bytes(), columns[c], colspans[c])
}
if colspans[c] > 1 {
colSpanSkip += colspans[c]
}
if colSpanSkip > 0 {
colSpanSkip--
}
}
p.r.TableRow(&body, rowWork.Bytes())
rowWork.Truncate(0)
i += j
continue
case true: // closing separator that closes the table
i += j
continue
}
}
pipes, rowStart := 0, i
for ; data[i] != '\n'; i++ {
if data[i] == '|' {
pipes++
}
}
if pipes == 0 {
i = rowStart
break
}
// include the newline in data sent to tableRow and blockTabeRow
i++
if foot {
// footer can't contain block level elements
p.tableRow(&footer, data[rowStart:i], columns, false)
} else {
p.blockTableRow(bodies, colspans, data[rowStart:i])
}
}
// are there cells left to process?
if len(bodies) > 0 && bodies[0].Len() != 0 {
colSpanSkip := 0
for c := 0; c < len(columns); c++ {
var cellWork bytes.Buffer
cellWork.Truncate(0)
if bodies[c].Len() > 0 {
p.block(&cellWork, bodies[c].Bytes())
bodies[c].Truncate(0)
}
if colSpanSkip == 0 {
p.r.TableCell(&rowWork, cellWork.Bytes(), columns[c], colspans[c])
}
if colspans[c] > 1 {
colSpanSkip += colspans[c]
}
if colSpanSkip > 0 {
colSpanSkip--
}
}
p.r.TableRow(&body, rowWork.Bytes())
}
var caption bytes.Buffer
line := i
j = i
if bytes.HasPrefix(data[j:], []byte("Table: ")) {
for line < len(data) {
j++
// find the end of this line
for data[j-1] != '\n' {
j++
}
if p.isEmpty(data[line:j]) > 0 {
break
}
line = j
}
p.inline(&caption, data[i+7:j-1]) // +7 for 'Table: '
}
p.r.SetInlineAttr(p.ial)
p.ial = nil
p.r.Table(out, header.Bytes(), body.Bytes(), footer.Bytes(), columns, caption.Bytes())
return j
}
// check if the specified position is preceeded by an odd number of backslashes
func isBackslashEscaped(data []byte, i int) bool {
backslashes := 0
for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
backslashes++
}
return backslashes&1 == 1
}
func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns []int) {
i := 0
colCount := 1
for i = 0; data[i] != '\n'; i++ {
if data[i] == '|' && !isBackslashEscaped(data, i) {
colCount++
}
}
// doesn't look like a table header
if colCount == 1 {
return
}
// include the newline in the data sent to tableRow
header := data[:i+1]
// column count ignores pipes at beginning or end of line
if data[0] == '|' {
colCount--
}
if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) {
colCount--
}
columns = make([]int, colCount)
// move on to the header underline
i++
if i >= len(data) {
return
}
if data[i] == '|' && !isBackslashEscaped(data, i) {
i++
}
for data[i] == ' ' {
i++
}
// each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
// and trailing | optional on last column
col := 0
for data[i] != '\n' {
dashes := 0
if data[i] == ':' {
i++
columns[col] |= _TABLE_ALIGNMENT_LEFT
dashes++
}
for data[i] == '-' {
i++
dashes++
}
if data[i] == ':' {
i++
columns[col] |= _TABLE_ALIGNMENT_RIGHT
dashes++
}
for data[i] == ' ' {
i++
}
// end of column test is messy
switch {
case dashes < 3:
// not a valid column
return
case data[i] == '|' && !isBackslashEscaped(data, i):
// marker found, now skip past trailing whitespace
col++
i++
for data[i] == ' ' {
i++
}
// trailing junk found after last column
if col >= colCount && data[i] != '\n' {
return
}
case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
// something else found where marker was required
return
case data[i] == '\n':
// marker is optional for the last column
col++
default:
// trailing junk found after last column
return
}
}
if col != colCount {
return
}
p.tableRow(out, header, columns, true)
size = i + 1
return
}
func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header bool) {
i, col := 0, 0
var rowWork bytes.Buffer
if data[i] == '|' && !isBackslashEscaped(data, i) {
i++
}
colSpanSkip := 0
for col = 0; col < len(columns) && i < len(data); col++ {
for data[i] == ' ' {
i++
}
cellStart := i
for (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
i++
}
cellEnd := i
// count number of pipe symbols to calculate colspan
colspan := 0
for data[i+colspan] == '|' && i+colspan < len(data) {
colspan++
}
// skip the end-of-cell marker, possibly taking us past end of buffer
i++
for cellEnd > cellStart && data[cellEnd-1] == ' ' {
cellEnd--
}
var cellWork bytes.Buffer
p.inline(&cellWork, data[cellStart:cellEnd])
if header {
if colSpanSkip == 0 {
p.r.TableHeaderCell(&rowWork, cellWork.Bytes(), columns[col], colspan)
}
} else {
if colSpanSkip == 0 {
p.r.TableCell(&rowWork, cellWork.Bytes(), columns[col], colspan)
}
}
if colspan > 1 {
colSpanSkip += colspan
}
if colSpanSkip > 0 {
colSpanSkip--
}
}
// pad it out with empty columns to get the right number
for ; col < len(columns); col++ {
if header {
p.r.TableHeaderCell(&rowWork, nil, columns[col], 0)
} else {
p.r.TableCell(&rowWork, nil, columns[col], 0)
}
}
// silently ignore rows with too many cells
p.r.TableRow(out, rowWork.Bytes())
}
func (p *parser) blockTableRow(out []bytes.Buffer, colspans []int, data []byte) {
i, col := 0, 0
if data[i] == '|' && !isBackslashEscaped(data, i) {
i++
}
for col = 0; col < len(out) && i < len(data); col++ {
for data[i] == ' ' {
i++
}
cellStart := i
for (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
i++
}
cellEnd := i
// count number of pipe symbols to calculate colspan
colspan := 0
for data[i+colspan] == '|' && i+colspan < len(data) {
colspan++
}
// skip the end-of-cell marker, possibly taking us past end of buffer
i++
for cellEnd > cellStart && data[cellEnd-1] == ' ' {
cellEnd--
}
out[col].Write(data[cellStart:cellEnd])
out[col].WriteByte('\n')
colspans[col] = colspan
}
}
// optional | or + at the beginning, then at least 3 equals
func (p *parser) isTableFooter(data []byte) int {
i := 0
if data[i] == '|' || data[i] == '+' {
i++
}
if len(data[i:]) < 4 {
return 0
}
if data[i+1] != '=' && data[i+2] != '=' && data[i+3] != '=' {
return 0
}
for i < len(data) && data[i] != '\n' {
i++
}
return i + 1
}
// this starts a table, basically three dashes with mandatory | or + at the start
func (p *parser) isBlockTableHeader(data []byte) int {
i := 0
if data[i] != '|' && data[i] != '+' {
return 0
}
i++
if len(data[i:]) < 4 {
return 0
}
if data[i+1] != '-' && data[i+2] != '-' && data[i+3] != '-' {
return 0
}
for i < len(data) && data[i] != '\n' {
i++
}
return i + 1
}
// table row separator (use in block tables): | or + at the start, 3 or more dashes
func (p *parser) isRowSeperator(data []byte) int {
return p.isBlockTableHeader(data)
}
// returns prefix length for block code
func (p *parser) codePrefix(data []byte) int {
if data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
return 4
}
return 0
}
func (p *parser) code(out *bytes.Buffer, data []byte) int {
var work bytes.Buffer
i := 0
for i < len(data) {
beg := i
for data[i] != '\n' {
i++
}
i++
blankline := p.isEmpty(data[beg:i]) > 0
if pre := p.codePrefix(data[beg:i]); pre > 0 {
beg += pre
} else if !blankline {
// non-empty, non-prefixed line breaks the pre
i = beg
break
}
// verbatim copy to the working buffer
if blankline {
work.WriteByte('\n')
} else {
work.Write(data[beg:i])
}
}
caption := ""
line := i
j := i
// In the case of F> there may be spaces in front of it
if bytes.HasPrefix(bytes.TrimSpace(data[j:]), []byte("Figure: ")) {
for line < len(data) {
j++
// find the end of this line
for data[j-1] != '\n' {
j++
}
if p.isEmpty(data[line:j]) > 0 {
break
}
line = j
}
// save for later processing.
if i+8 < j-1 {
caption = string(data[i+8 : j-1]) // +8 for 'Figure: '
}
}
// trim all the \n off the end of work
workbytes := work.Bytes()
eol := len(workbytes)
for eol > 0 && workbytes[eol-1] == '\n' {
eol--
}
if eol != len(workbytes) {
work.Truncate(eol)
}
work.WriteByte('\n')
co := ""
if p.ial != nil {
// enabled, any non-empty value
co = p.ial.Value("callout")
}
p.r.SetInlineAttr(p.ial)
p.ial = nil
var capb bytes.Buffer
if co != "" {
var callout bytes.Buffer
callouts(p, &callout, work.Bytes(), 0, co)
p.inline(&capb, []byte(caption))
p.r.BlockCode(out, callout.Bytes(), "", capb.Bytes(), p.insideFigure, true)
} else {
p.callouts = nil
p.inline(&capb, []byte(caption))
p.r.BlockCode(out, work.Bytes(), "", capb.Bytes(), p.insideFigure, false)
}
return j
}
// returns unordered list item prefix
func (p *parser) uliPrefix(data []byte) int {
i := 0
if len(data) < 3 {
return 0
}
// start with up to 3 spaces
for i < 3 && data[i] == ' ' {
i++
}
// need a *, +, #, or - followed by a space
if (data[i] != '*' && data[i] != '+' && data[i] != '-' && !iswhitespace(data[i])) ||
!iswhitespace(data[i+1]) {
return 0
}
return i + 2
}
// returns ordered list item prefix
func (p *parser) oliPrefix(data []byte) int {
i := 0
if len(data) < 3 {
return 0
}
// start with up to 3 spaces
for i < 3 && data[i] == ' ' {
i++
}
// count the digits
start := i
for isnum(data[i]) {
i++
}
// we need >= 1 digits followed by a dot or brace and a space
if start == i || (data[i] != '.' && data[i] != ')') || !iswhitespace(data[i+1]) {
return 0
}
return i + 2
}
// returns ordered list item prefix for alpha ordered list
func (p *parser) aliPrefix(data []byte) int {
i := 0
if len(data) < 4 {
return 0
}
// start with up to 3 spaces
for i < 3 && data[i] == ' ' {
i++
}
// count the digits
start := i
for data[i] >= 'a' && data[i] <= 'z' {
i++
}
// we need >= 1 letter followed by a dot and two spaces
if start == i || (data[i] != '.' && data[i] != ')') || !iswhitespace(data[i+1]) || !iswhitespace(data[i+2]) {
return 0
}
if i-start > 2 {
// crazy list, i.e. too many letters.
return 0
}
return i + 3
}
// returns ordered list item prefix for alpha uppercase ordered list
func (p *parser) aliPrefixU(data []byte) int {
i := 0
if len(data) < 4 {
return 0
}
// start with up to 3 spaces
for i < 3 && data[i] == ' ' {
i++
}
// count the digits
start := i
for isupper(data[i]) {
i++
}
// we need >= 1 letter followed by a dot and two spaces
if start == i || (data[i] != '.' && data[i] != ')') || !iswhitespace(data[i+1]) || !iswhitespace(data[i+2]) {
return 0
}
if i-start > 2 {
// crazy list, i.e. too many letters.
return 0
}
return i + 3
}
// returns ordered list item prefix for roman ordered list
func (p *parser) rliPrefix(data []byte) int {
i := 0
if len(data) < 4 {
return 0
}
// start with up to 3 spaces
for i < 3 && data[i] == ' ' {
i++
}
// count the digits
start := i
for isroman(data[i], false) {
i++
}
// we need >= 1 letter followed by a dot and two spaces
if start == i || (data[i] != '.' && data[i] != ')') || !iswhitespace(data[i+1]) || !iswhitespace(data[i+2]) {
return 0
}
return i + 3
}
// returns ordered list item prefix for roman uppercase ordered list
func (p *parser) rliPrefixU(data []byte) int {
i := 0
if len(data) < 4 {
return 0
}
// start with up to 3 spaces
for i < 3 && data[i] == ' ' {
i++
}
// count the digits
start := i
for isroman(data[i], true) {
i++
}
// we need >= 1 letter followed by a dot and two spaces
if start == i || (data[i] != '.' && data[i] != ')') || !iswhitespace(data[i+1]) || !iswhitespace(data[i+2]) {
return 0
}
return i + 3
}
// returns definition list item prefix
func (p *parser) dliPrefix(data []byte) int {
// return the index of where the term ends
i := 0
for data[i] != '\n' && i < len(data) {
i++
}
if i == 0 || i == len(data) {
return 0
}
// allow an optional newline to be here
if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\n' {
i++
}
// start with up to 3 spaces before :
j := 0
for j < 3 && iswhitespace(data[i+j]) && i+j < len(data) {
j++
}
i += j + 1
if i >= len(data) {
return 0
}
if data[i] == ':' {
return i + 1
}
return 0
}
// returns example list item prefix
func (p *parser) eliPrefix(data []byte) int {
i := 0
if len(data) < 6 {
return 0
}
// start with up to 3 spaces
for i < 3 && iswhitespace(data[i]) {
i++
}
// (@)
if data[i] != '(' || data[i+1] != '@' {
return 0
}
// count up until the closing )
for data[i] != ')' {
i++
if i == len(data) {
return 0
}
}
// now two spaces
if data[i] != ')' || !iswhitespace(data[i+1]) || !iswhitespace(data[i+2]) {
return 0
}
return i + 2
}
// parse ordered or unordered or definition list block
func (p *parser) list(out *bytes.Buffer, data []byte, flags, start int, group []byte) int {
p.insideList++
defer func() {
p.insideList--
}()
i := 0
flags |= _LIST_ITEM_BEGINNING_OF_LIST
work := func() bool {
for i < len(data) {
skip := p.listItem(out, data[i:], &flags)
i += skip
if skip == 0 || flags&_LIST_ITEM_END_OF_LIST != 0 {
break
}
flags &= ^_LIST_ITEM_BEGINNING_OF_LIST
}
return true
}
if group != nil {
gr := string(group)
if _, ok := p.examples[gr]; ok {
p.examples[gr]++
} else {
p.examples[gr] = 1
}
}
p.r.SetInlineAttr(p.ial)
p.ial = nil
if p.insideList > 1 {
flags |= _LIST_INSIDE_LIST
} else {
flags &= ^_LIST_INSIDE_LIST
}
p.r.List(out, work, flags, start, group)
return i
}
// Parse a single list item.
// Assumes initial prefix is already removed if this is a sublist.
func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int {
// keep track of the indentation of the first line
itemIndent := 0
for itemIndent < 3 && iswhitespace(data[itemIndent]) {
itemIndent++
}
i := p.uliPrefix(data)
if i == 0 {
i = p.oliPrefix(data)
}
if i == 0 {
i = p.aliPrefix(data)
}
if i == 0 {
i = p.aliPrefixU(data)
}
if i == 0 {
i = p.rliPrefix(data)
}
if i == 0 {
i = p.rliPrefixU(data)
}
if i == 0 {
i = p.eliPrefix(data)
}
if i == 0 {
i = p.dliPrefix(data)
if i > 0 {
var rawTerm bytes.Buffer
if data[i-2] == '\n' && data[i-3] == '\n' {
p.inline(&rawTerm, data[:i-3]) // -3 for : and the 2newline
} else {
p.inline(&rawTerm, data[:i-2]) // -2 for : and the newline
}
p.r.ListItem(out, rawTerm.Bytes(), *flags|_LIST_TYPE_TERM)
}
}
if i == 0 {
return 0
}
// skip leading whitespace on first line
for iswhitespace(data[i]) {
i++
}
// find the end of the line
line := i
for data[i-1] != '\n' {
i++
}
// get working buffer
var raw bytes.Buffer
// put the first line into the working buffer
raw.Write(data[line:i])
line = i
// process the following lines
containsBlankLine := false
sublist := 0
gatherlines:
for line < len(data) {
i++
// find the end of this line
for data[i-1] != '\n' {
i++
}
// if it is an empty line, guess that it is part of this item
// and move on to the next line
if p.isEmpty(data[line:i]) > 0 {
containsBlankLine = true
raw.Write(data[line:i])
line = i
continue
}
// calculate the indentation
indent := 0
for indent < 4 && line+indent < i && data[line+indent] == ' ' {
indent++
}
chunk := data[line+indent : i]
// evaluate how this line fits in
switch {
// is this a nested list item?
case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) ||
p.aliPrefix(chunk) > 0 || p.aliPrefixU(chunk) > 0 ||
p.rliPrefix(chunk) > 0 || p.rliPrefixU(chunk) > 0 ||
p.oliPrefix(chunk) > 0 || p.eliPrefix(chunk) > 0 ||
p.dliPrefix(data[line+indent:]) > 0:
if containsBlankLine {
*flags |= _LIST_ITEM_CONTAINS_BLOCK
}
// to be a nested list, it must be indented more
// if not, it is the next item in the same list
if indent <= itemIndent {
break gatherlines
}
// is this the first item in the the nested list?
if sublist == 0 {
sublist = raw.Len()
}
// is this a nested prefix header?
case p.isPrefixHeader(chunk):
// if the header is not indented, it is not nested in the list
// and thus ends the list
if containsBlankLine && indent < 4 {
*flags |= _LIST_ITEM_END_OF_LIST
break gatherlines
}
*flags |= _LIST_ITEM_CONTAINS_BLOCK
// 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)
// if the is beginning with ': term', we have a new term
case containsBlankLine && indent < 4:
*flags |= _LIST_ITEM_END_OF_LIST
break gatherlines
// a blank line means this should be parsed as a block
case containsBlankLine:
*flags |= _LIST_ITEM_CONTAINS_BLOCK
// CommonMark, rule breaks the list, but when indented it belong to the list
case p.isHRule(chunk) && indent < 4:
*flags |= _LIST_ITEM_END_OF_LIST
break gatherlines
}
containsBlankLine = false
// add the line into the working buffer without prefix
raw.Write(data[line+indent : i])
line = i
}
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 sublist > 0 {
p.block(&cooked, rawBytes[:sublist])
p.block(&cooked, rawBytes[sublist:])
} else {
p.block(&cooked, rawBytes)
}
} else {
// intermediate render of inline li
if sublist > 0 {
p.inline(&cooked, rawBytes[:sublist])
p.block(&cooked, rawBytes[sublist:])
} else {
p.inline(&cooked, rawBytes)
}
}
// render the actual list item
cookedBytes := cooked.Bytes()
parsedEnd := len(cookedBytes)
// strip trailing newlines
for parsedEnd > 0 && cookedBytes[parsedEnd-1] == '\n' {
parsedEnd--
}
p.r.ListItem(out, cookedBytes[:parsedEnd], *flags)
return line
}
// render a single paragraph that has already been parsed out
func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) {
if len(data) == 0 {
return
}
// trim leading spaces
beg := 0
for iswhitespace(data[beg]) {
beg++
}
// trim trailing newline
end := len(data) - 1
// trim trailing spaces
for end > beg && iswhitespace(data[end-1]) {
end--
}
if isMatter(data) {
p.inline(out, data[beg:end])
return
}
p.displayMath = false
work := func() bool {
// if we are a single paragraph constisting entirely out of math
// we set the displayMath to true
k := 0
if end-beg > 4 && data[beg] == '$' && data[beg+1] == '$' {
for k = beg + 2; k < end-1; k++ {
if data[k] == '$' && data[k+1] == '$' {
break
}
}
if k+2 == end {
p.displayMath = true
}
}
p.inline(out, data[beg:end])
return true
}
flags := 0
if p.insideDefinitionList {
flags |= _LIST_TYPE_DEFINITION
}
if p.insideList > 0 {
flags |= _LIST_INSIDE_LIST // Not really, just in a list
} else {
flags &= ^_LIST_INSIDE_LIST // Not really, just in a list
}
p.r.Paragraph(out, work, flags)
}
func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
// prev: index of 1st char of previous line
// line: index of 1st char of current line
// i: index of cursor/end of current line
var prev, line, i int
// keep going until we find something to mark the end of the paragraph
for i < len(data) {
// mark the beginning of the current line
prev = line
current := data[i:]
line = i
// did we find a blank line marking the end of the paragraph?
if n := p.isEmpty(current); n > 0 {
p.renderParagraph(out, data[:i])
return i + n
}
// an underline under some text marks a header, so our paragraph ended on prev line
if i > 0 {
if level := p.isUnderlinedHeader(current); level > 0 {
// render the paragraph
p.renderParagraph(out, data[:prev])
// ignore leading and trailing whitespace
eol := i - 1
for prev < eol && iswhitespace(data[prev]) {
prev++
}
for eol > prev && iswhitespace(data[eol-1]) {
eol--
}
// render the header
// this ugly double closure avoids forcing variables onto the heap
work := func(o *bytes.Buffer, pp *parser, d []byte) func() bool {
return func() bool {
// this renders the name, but how to make attribute out of it
pp.inline(o, d)
return true
}
}(out, p, data[prev:eol])
id := ""
if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
id = createSanitizedAnchorName(string(data[prev:eol]))
}
p.r.SetInlineAttr(p.ial)
p.ial = nil
p.r.Header(out, work, level, id)
// find the end of the underline
for data[i] != '\n' {
i++
}
return i
}
}
// if the next line starts a block of HTML, then the paragraph ends here
if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
if data[i] == '<' && p.html(out, current, false) > 0 {
// rewind to before the HTML block
p.renderParagraph(out, data[:i])
return i
}
}
// 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 fenced code block, paragraph is over
if p.flags&EXTENSION_FENCED_CODE != 0 {
if p.fencedCode(out, current, false) > 0 {
p.renderParagraph(out, data[:i])
return i
}
}
// 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 ||
p.aliPrefixU(current) != 0 ||
p.aliPrefix(current) != 0 ||
p.rliPrefixU(current) != 0 ||
p.rliPrefix(current) != 0 ||
p.oliPrefix(current) != 0 ||
p.eliPrefix(current) != 0 ||
p.dliPrefix(current) != 0 ||
p.quotePrefix(current) != 0 ||
p.figurePrefix(current) != 0 ||
p.asidePrefix(current) != 0 ||
p.codePrefix(current) != 0 {
p.renderParagraph(out, data[:i])
return i
}
}
// otherwise, scan to the beginning of the next line
for data[i] != '\n' {
i++
}
i++
}
p.renderParagraph(out, data[:i])
return i
}
func createSanitizedAnchorName(text string) string {
var anchorName []rune
number := 0
for _, r := range []rune(text) {
switch {
case r == ' ':
anchorName = append(anchorName, '-')
case unicode.IsNumber(r):
number++
fallthrough
case unicode.IsLetter(r):
anchorName = append(anchorName, unicode.ToLower(r))
}
}
if number == len(anchorName) {
anchorName = append([]rune("section-"), anchorName...)
}
return string(anchorName)
}
func isMatter(text []byte) bool {
if string(text) == "{frontmatter}\n" {
return true
}
if string(text) == "{mainmatter}\n" {
return true
}
if string(text) == "{backmatter}\n" {
return true
}
return false
}
mmark-1.3-git849458a/block_test.go 0000664 0000000 0000000 00000150555 12640731723 0016614 0 ustar 00root root 0000000 0000000 // Unit tests for block parsing
package mmark
import (
"io/ioutil"
"os"
"strings"
"testing"
)
func init() {
test = true
}
func runMarkdownBlock(input string, extensions int) string {
htmlFlags := 0
renderer := HtmlRenderer(htmlFlags, "", "")
return Parse([]byte(input), renderer, extensions).String()
}
func runMarkdownBlockXML(input string, extensions int) string {
xmlFlags := 0
extensions |= commonXmlExtensions
extensions |= EXTENSION_UNIQUE_HEADER_IDS
renderer := XmlRenderer(xmlFlags)
return Parse([]byte(input), renderer, extensions).String()
}
func doTestsBlock(t *testing.T, tests []string, extensions int) {
// catch and report panics
var candidate string
defer func() {
if err := recover(); err != nil {
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
}
}()
for i := 0; i+1 < len(tests); i += 2 {
input := tests[i]
candidate = input
expected := tests[i+1]
actual := runMarkdownBlock(candidate, extensions)
if actual != expected {
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
candidate, expected, actual)
}
// now test every substring to stress test bounds checking
if !testing.Short() {
for start := 0; start < len(input); start++ {
for end := start + 1; end <= len(input); end++ {
candidate = input[start:end]
_ = runMarkdownBlock(candidate, extensions)
}
}
}
}
}
func doTestsBlockXML(t *testing.T, tests []string, extensions int) {
// catch and report panics
var candidate string
defer func() {
if err := recover(); err != nil {
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
}
}()
for i := 0; i+1 < len(tests); i += 2 {
input := tests[i]
candidate = input
expected := tests[i+1]
actual := runMarkdownBlockXML(candidate, extensions)
if actual != expected {
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
candidate, expected, actual)
}
// now test every substring to stress test bounds checking
if !testing.Short() {
for start := 0; start < len(input); start++ {
for end := start + 1; end <= len(input); end++ {
candidate = input[start:end]
_ = runMarkdownBlockXML(candidate, extensions)
}
}
}
}
}
func TestPrefixHeaderNoExtensions(t *testing.T) {
var tests = []string{
"# Header 1\n",
"
\n",
"Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nSome text in between\n``` oz\nmultiple code blocks work okay\n```\nAnd some text after a fenced code block",
"
`,
}
// These 2 alternative forms of blockquoted fenced code blocks should produce same output.
forms := [2]string{
cat("> plain quoted text",
"> ```fenced",
"code",
" with leading single space correctly preserved",
"okay",
"```",
"> rest of quoted text"),
cat("> plain quoted text",
"> ```fenced",
"> code",
"> with leading single space correctly preserved",
"> okay",
"> ```",
"> rest of quoted text"),
}
want := `
plain quoted text
code
with leading single space correctly preserved
okay
\n",
}
doTestsBlock(t, tests, EXTENSION_TABLES)
}
func TestBlockTable(t *testing.T) {
var tests = []string{
"|--------+--------+\n| Defaul |Left ald|\n|--------|--------|\n| Second |foo |\n|--------+--------+\n| Second | 2. Ite |\n| 2 line | 3. Ite |\n|--------+--------+\n| Footer | Footer |\n|--------+--------+\n",
"
\n\n
\n
Defaul
\n
Left ald
\n
\n\n\n\n
\n
Second
\n
\n
foo
\n
\n
\n\n
\n
Second\n2 line
\n
\n
\n
Ite
\n
Ite
\n\n
\n
\n\n
\n
Footer
\n
\n
Footer
\n
\n
\n\n
\n",
"|--------+--------+\n| Defaul |Left ald|\n|--------|--------|\n| Second |foo |\n|--------+--------+\n| Second | 2. Ite |\n| 2 line | 3. Ite |\n|--------+--------+\n| Footer | Footer |\n|--------+--------+\n",
"
\n\n
\n
Defaul
\n
Left ald
\n
\n\n\n\n
\n
Second
\n
\n
foo
\n
\n
\n\n
\n
Second\n2 line
\n
\n
\n
Ite
\n
Ite
\n\n
\n
\n\n
\n
Footer
\n
\n
Footer
\n
\n
\n\n
\n",
"|--------+--------+\n| Defaul |Left ald|\n|--------|--------|\n| Second |foo |\n|--------+--------+\n| Second | 2. Ite |\n| 2 line | 3. Ite |\n|--------+--------+\n| Footer | Footer |\n|--------+--------+\nTable: this is a table\n",
"
\n
\nthis is a table\n
\n\n
\n
Defaul
\n
Left ald
\n
\n\n\n\n
\n
Second
\n
\n
foo
\n
\n
\n\n
\n
Second\n2 line
\n
\n
\n
Ite
\n
Ite
\n\n
\n
\n\n
\n
Footer
\n
\n
Footer
\n
\n
\n\n
\n",
"|--------+--------+\n| Defaul |Left ald|\n|--------|--------|\n| Second |foo |\n|--------+--------+\n| Second | 2. Ite |\n| 2 line | 3. Ite |\n+========+========+\n| Footer | Footer |\n|--------+--------+\nTable: this is a table\n",
"
\n
\nthis is a table\n
\n\n
\n
Defaul
\n
Left ald
\n
\n\n\n\n
\n
Second
\n
\n
foo
\n
\n
\n\n
\n
Second\n2 line
\n
\n
\n
Ite
\n
Ite
\n\n
\n
\n\n\n
\n
Footer
\n
Footer
\n
\n\n
\n",
"|--------+--------+\n|--------+--------+\n| Defaul |Left ald|\n|--------|--------|\n|--------+--------+\n| Second | 2. Ite |\n| 2 line | 3. Ite |\n+========+========+\n|--------+--------+\nTable: this is a table\n",
"
\n
\nthis is a table\n
\n\n\n\n\n
\n
\n
\n\n
\n
Defaul
\n
\n
\n\n
\n
\n
\n\n
\n
Second\n2 line
\n
\n
\n\n
\n",
"|--------+--------+\n|--------+--------+\n| Defaul |Left ald|\n|--------|--------|\n|--------+--------+\n| Second | 2. Ite |\n| 2 line | 3. Ite |\n+========+========+\n+========+========+\n|--------+--------+\nTable: this is a table\n",
"
\n\n\n\n\n
\n
\n
\n\n
\n
Defaul
\n
\n
\n\n
\n
\n
\n\n
\n
Second\n2 line
\n
\n
\n\n
\n\n
+========+========+\n|--------+--------+\nTable: this is a table
\n",
"+--------+--------+\n| Defaul |Left ald|\n|--------|--------|\n|--------+--------+\n| Second | 2. Ite |\n| 2 line | 3. Ite |\n+========+========+\n|--------+--------+\nTable: this is a table\n",
"
\n",
// fix sourcecode/artwork here.
`Term 1
: This is a definition with two paragraphs. Lorem ipsum
Vestibulum enim wisi, viverra nec, fringilla in, laoreet
vitae, risus.
Term 2
: This definition has a code block, a blockquote and a list.
code block.
> block quote
> on two lines.
1. first list item
2. second list item
`,
"
\n
Term 1
\n
\nThis is a definition with two paragraphs. Lorem ipsum\n\n\nVestibulum enim wisi, viverra nec, fringilla in, laoreet\nvitae, risus.\n
\n
Term 2
\n
\nThis definition has a code block, a blockquote and a list.\n\n\ncode block.\n\n
\n\nblock quote\non two lines.\n\n
\n\n
first list item
\n
second list item
\n
\n
\n",
`Apple
: Pomaceous fruit of plants of the genus Malus in
the family Rosaceae.
Orange and *Apples*
: The thing of an evergreen tree of the genus Citrus.`,
"
\n
Apple
\n
\nPomaceous fruit of plants of the genus Malus in\nthe family Rosaceae.\n
\n
Orange and Apples
\n
\nThe thing of an evergreen tree of the genus Citrus.\n
\n
\n",
`Apple
: Pomaceous fruit of plants of the genus Malus in
the family Rosaceae.
Orange and *Apples*
: The thing of an evergreen tree of the genus Citrus.`,
"
\n
Apple
\n
\nPomaceous fruit of plants of the genus Malus in\nthe family Rosaceae.\n
\n
Orange and Apples
\n
\nThe thing of an evergreen tree of the genus Citrus.\n
\n
\n",
}
doTestsBlockXML(t, tests, 0)
}
func TestAbstractNoteAsideXML(t *testing.T) {
var tests = []string{
".# Abstract\nbegin of abstract\n\nthis is an abstract\n",
"\n\n\nbegin of abstract\n\n\nthis is an abstract\n\n\n\n",
".# A note\nthis is the content\n",
"\n\nA note\n\nthis is the content\n\n\n\n",
"A> begin of aside\nA> this is an aside\n",
"\n",
}
doTestsBlockXML(t, tests, 0)
}
func TestOrderedListStartXML(t *testing.T) {
var tests = []string{
"1. hello\n1. hello\n\ndivide\n\n4. hello\n5. hello\n\ndivide\n\n 7. hello\n5. hello\n",
"\n
hello
\n
hello
\n\n\ndivide\n\n\n
hello
\n
hello
\n\n\ndivide\n\n\n
hello
\n
hello
\n\n",
}
doTestsBlockXML(t, tests, 0)
}
func TestIncludesXML(t *testing.T) {
if !testing.Short() {
return
}
var tests = []string{
"{{/dev/null}}",
"",
"<{{/dev/null}}",
"\n\n\n\n\n\n",
" <{{/dev/null}}",
"\n\n\n\n\n\n",
"`{{does-not-exist}}`",
"\n{{does-not-exist}}\n\n",
" {{does-not-exist}}",
"\n{{does-not-exist}}\n\n",
"`<{{prog-not-exist}}`",
"\n<{{prog-not-exist}}\n\n",
`1. This is item1
2. This is item2.
Lets include some code:
<{{/dev/null}}
Figure: this is some code!`,
"\n
This is item1
\n
This is item2.\nLets include some code:\n\nthis is some code!\n\n\n\n
\n\n",
}
f, e := ioutil.TempFile("/tmp", "mmark_test.")
if e == nil {
defer os.Remove(f.Name())
ioutil.WriteFile(f.Name(), []byte(`
tedious_code = boring_function()
// START OMIT
interesting_code = fascinating_function()
// END OMIT`), 0644)
t1 := "Include some code\n <{{" + f.Name() + "}}[/START OMIT/,/END OMIT/]\n"
e1 := "\nInclude some code\n \ninteresting_code = fascinating_function()\n\n\n\n"
tests = append(tests, []string{t1, e1}...)
}
doTestsBlockXML(t, tests, EXTENSION_INCLUDE)
}
func TestInlineAttrXML(t *testing.T) {
var tests = []string{
"{attribution=\"BLA BLA\" .green}\n{bla=BLA}\n{more=\"ALB ALB\" #ref:quote .yellow}\n> Hallo2\n> Hallo3\n\nThis is no one `{source='BLEIP'}` on of them\n\n{evenmore=\"BLE BLE\"}\n> Hallo6\n> Hallo7",
"
\n\nHallo2\nHallo3\n\n
\n\nThis is no one {source='BLEIP'} on of them\n\n
\n\n",
"{.green #ref1}\n# hallo\n\n{.yellow}\n# hallo {#ref2}\n\n{.blue #ref3}\n# hallo {#ref4}\n",
"\n\nhallo\n\n\n\nhallo\n\n\n\nhallo\n\n",
}
doTestsBlockXML(t, tests, 0)
}
func TestCloseHeaderXML(t *testing.T) {
var tests = []string{
"# Header1\n",
"\n\nHeader1\n\n",
}
doTestsBlockXML(t, tests, 0)
}
func TestOrderedExampleListXML(t *testing.T) {
var tests = []string{
`(@good) Example1
(@good) Example2
As this illustrates
(@good) Example3
As we can see from (@good) some things never change. Some we do not when we reference (@good1).
`,
"\n
\nExample1\n
\n
\nExample2\n
\n\n\nAs this illustrates\n\n\n
Example3
\n\n\nAs we can see from (2) some things never change. Some we do not when we reference (@good1).\n\n",
}
doTestsBlockXML(t, tests, 0)
}
// TODO(miek): titleblock, this will only work with full document output.
func testTitleBlockTOML(t *testing.T) {
var tests = []string{
`% Title = "Test Document"
% abbr = "test"
{mainmatter}
`,
"dsjsdk",
}
doTestsBlockXML(t, tests, EXTENSION_TITLEBLOCK_TOML)
}
func TestSubFiguresXML(t *testing.T) {
var tests = []string{`
* Item1
* Item2
Basic usage:
F> {type="ascii-art"}
F> +-----+
F> | ART |
F> +-----+
F> Figure: This caption is ignored.
F>
F> ~~~ c
F> printf("%s\n", "hello");
F> ~~~
Figure: Caption you will see, for both figures.
`,
"
\n
Item1
\n
\nItem2\n\n\nBasic usage:\n\n\nCaption you will see, for both figures.\n\n\n\n\n +-----+\n | ART |\n +-----+\n\n\n\nprintf(\"%s\\n\", \"hello\");\n\n
\n
\n",
`
And another one
F> {type="ascii-art"}
F> +-----+
F> | ART |
F> +-----+
F> Figure: This caption is ignored.
F>
F> ~~~ c
F> printf("%s\n", "hello");
F> ~~~
F>
Figure: Caption you will see, for both figures.`,
"\nAnd another one\n\n\nCaption you will see, for both figures.\n\n\n\n\n +-----+\n | ART |\n +-----+\n\n\n\nprintf(\"%s\\n\", \"hello\");\n\n\n",
}
doTestsBlockXML(t, tests, 0)
}
func TestBlockComments(t *testing.T) {
var tests = []string{
"Some text\n\n\n",
"
Some text
\n\n\n",
"Some text\n\n\n",
"
Some text
\n\n\n",
"Some text\n\n\n",
"
Some text
\n\n\n",
}
doTestsBlock(t, tests, 0)
}
func TestIALXML(t *testing.T) {
var tests = []string{
"{#id}\n Code\n",
"\nCode\n\n",
"{id}\n Code\n",
"\n{id}\n\n\nCode\n\n",
"{#id}\n",
"",
"{#id}\n{type=\"go\"}\n Code\n",
"\nCode\n\n",
"{#id \n Code\n",
"\n{#id\n\n\nCode\n\n",
}
doTestsBlockXML(t, tests, 0)
}
func testCalloutXML(t *testing.T) {
var tests = []string{`
{callout="true"}
This is some code
Code <1>
More <1>
Not a callout \<3>
As you can see in <1> we do some funky stuff above here.
`,
""}
doTestsBlockXML(t, tests, 0)
}
// TODO:
// figure caption
// table caption
// frontmatter
mmark-1.3-git849458a/cm_test.go 0000664 0000000 0000000 00000004756 12640731723 0016122 0 ustar 00root root 0000000 0000000 // Unit tests for commonmark parsing
package mmark
import (
"testing"
)
func runMarkdownCommonMark(input string, extensions int) string {
htmlFlags := 0
renderer := HtmlRenderer(htmlFlags, "", "")
return Parse([]byte(input), renderer, extensions).String()
}
func doTestsCommonMark(t *testing.T, tests []string, extensions int) {
// catch and report panics
var candidate string
defer func() {
if err := recover(); err != nil {
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
}
}()
for i := 0; i+1 < len(tests); i += 2 {
input := tests[i]
candidate = input
expected := tests[i+1]
actual := runMarkdownCommonMark(candidate, extensions)
if actual != expected {
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
candidate, expected, actual)
}
// now test every substring to stress test bounds checking
if !testing.Short() {
for start := 0; start < len(input); start++ {
for end := start + 1; end <= len(input); end++ {
candidate = input[start:end]
_ = runMarkdownBlock(candidate, extensions)
}
}
}
}
}
func TestPrefixHeaderCommonMark_29(t *testing.T) {
var tests = []string{
"# hallo\n\n # hallo\n\n # hallo\n\n # hallo\n\n # hallo\n",
"
hallo
\n\n
hallo
\n\n
hallo
\n\n
hallo
\n\n
# hallo\n
\n",
}
doTestsCommonMark(t, tests, 0)
}
func TestHRuleCommonMark_18_22(t *testing.T) {
var tests = []string{
"* List\n * Sublist\n Not a header\n ------\n",
"
\n",
}
doTestsCommonMark(t, tests, EXTENSION_FENCED_CODE)
}
mmark-1.3-git849458a/code.go 0000664 0000000 0000000 00000015627 12640731723 0015375 0 ustar 00root root 0000000 0000000 // Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Adapted for mmark, by Miek Gieben, 2015.
package mmark
import (
"bufio"
"bytes"
"errors"
"io/ioutil"
"regexp"
"strconv"
"unicode/utf8"
)
// SourceCodeTypes are the different languages that are supported as
// a type attribute in sourcecode, see Section 2.48.4 of XML2RFC v3 (-21).
var SourceCodeTypes = map[string]bool{
"abnf": true,
"asn.1": true,
"bash": true,
"c++": true,
"c": true,
"cbor": true,
"dtd": true,
"java": true,
"javascript": true,
"json": true,
"mib": true,
"perl": true,
"pseudocode": true,
"python": true,
"rnc": true,
"xml": true,
"go": true,
}
// parseCode parses a code address directive.
func parseCode(addr []byte, file []byte) []byte {
bytes.TrimSpace(addr)
textBytes, err := ioutil.ReadFile(string(file))
if err != nil {
printf(nil, "failed: `%s': %s", string(file), err)
return nil
}
lo, hi, err := addrToByteRange(string(addr), 0, textBytes)
if err != nil {
printf(nil, "code include address: %s", err.Error())
return textBytes
}
// Acme pattern matches can stop mid-line,
// so run to end of line in both directions if not at line start/end.
for lo > 0 && textBytes[lo-1] != '\n' {
lo--
}
if hi > 0 {
for hi < len(textBytes) && textBytes[hi-1] != '\n' {
hi++
}
}
lines := codeLines(textBytes, lo, hi)
return lines
}
// codeLines takes a source file and returns the lines that
// span the byte range specified by start and end.
// It discards lines that end in "OMIT" and in "OMIT -->"
func codeLines(src []byte, start, end int) (lines []byte) {
startLine := 1
for i, b := range src {
if i == start {
break
}
if b == '\n' {
startLine++
}
}
s := bufio.NewScanner(bytes.NewReader(src[start:end]))
for n := startLine; s.Scan(); n++ {
l := s.Bytes()
if bytes.HasSuffix(l, []byte("OMIT")) {
continue
}
if bytes.HasSuffix(l, []byte("OMIT -->")) {
continue
}
lines = append(lines, l...)
lines = append(lines, '\n')
}
// TODO(miek): trim leading and trailing blanklines
return
}
// This file is stolen from go/src/cmd/godoc/codewalk.go.
// It's an evaluator for the file address syntax implemented by acme and sam,
// but using Go-native regular expressions.
// To keep things reasonably close, this version uses (?m:re) for all user-provided
// regular expressions. That is the only change to the code from codewalk.go.
// See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II
// for details on the syntax.
// addrToByte evaluates the given address starting at offset start in data.
// It returns the lo and hi byte offset of the matched region within data.
func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err error) {
if addr == "" {
lo, hi = start, len(data)
return
}
var (
dir byte
prevc byte
charOffset bool
)
lo = start
hi = start
for addr != "" && err == nil {
c := addr[0]
switch c {
default:
err = errors.New("invalid address syntax near " + string(c))
case ',':
if len(addr) == 1 {
hi = len(data)
} else {
_, hi, err = addrToByteRange(addr[1:], hi, data)
}
return
case '+', '-':
if prevc == '+' || prevc == '-' {
lo, hi, err = addrNumber(data, lo, hi, prevc, 1, charOffset)
}
dir = c
case '$':
lo = len(data)
hi = len(data)
if len(addr) > 1 {
dir = '+'
}
case '#':
charOffset = true
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
var i int
for i = 1; i < len(addr); i++ {
if addr[i] < '0' || addr[i] > '9' {
break
}
}
var n int
n, err = strconv.Atoi(addr[0:i])
if err != nil {
break
}
lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffset)
dir = 0
charOffset = false
prevc = c
addr = addr[i:]
continue
case '/':
var i, j int
Regexp:
for i = 1; i < len(addr); i++ {
switch addr[i] {
case '\\':
i++
case '/':
j = i + 1
break Regexp
}
}
if j == 0 {
j = i
}
pattern := addr[1:i]
lo, hi, err = addrRegexp(data, lo, hi, dir, pattern)
prevc = c
addr = addr[j:]
continue
}
prevc = c
addr = addr[1:]
}
if err == nil && dir != 0 {
lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset)
}
if err != nil {
return 0, 0, err
}
return lo, hi, nil
}
// addrNumber applies the given dir, n, and charOffset to the address lo, hi.
// dir is '+' or '-', n is the count, and charOffset is true if the syntax
// used was #n. Applying +n (or +#n) means to advance n lines
// (or characters) after hi. Applying -n (or -#n) means to back up n lines
// (or characters) before lo.
// The return value is the new lo, hi.
func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, error) {
switch dir {
case 0:
lo = 0
hi = 0
fallthrough
case '+':
if charOffset {
pos := hi
for ; n > 0 && pos < len(data); n-- {
_, size := utf8.DecodeRune(data[pos:])
pos += size
}
if n == 0 {
return pos, pos, nil
}
break
}
// find next beginning of line
if hi > 0 {
for hi < len(data) && data[hi-1] != '\n' {
hi++
}
}
lo = hi
if n == 0 {
return lo, hi, nil
}
for ; hi < len(data); hi++ {
if data[hi] != '\n' {
continue
}
switch n--; n {
case 1:
lo = hi + 1
case 0:
return lo, hi + 1, nil
}
}
case '-':
if charOffset {
// Scan backward for bytes that are not UTF-8 continuation bytes.
pos := lo
for ; pos > 0 && n > 0; pos-- {
if data[pos]&0xc0 != 0x80 {
n--
}
}
if n == 0 {
return pos, pos, nil
}
break
}
// find earlier beginning of line
for lo > 0 && data[lo-1] != '\n' {
lo--
}
hi = lo
if n == 0 {
return lo, hi, nil
}
for ; lo >= 0; lo-- {
if lo > 0 && data[lo-1] != '\n' {
continue
}
switch n--; n {
case 1:
hi = lo
case 0:
return lo, hi, nil
}
}
}
return 0, 0, errors.New("address out of range")
}
// addrRegexp searches for pattern in the given direction starting at lo, hi.
// The direction dir is '+' (search forward from hi) or '-' (search backward from lo).
// Backward searches are unimplemented.
func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, error) {
// We want ^ and $ to work as in sam/acme, so use ?m.
re, err := regexp.Compile("(?m:" + pattern + ")")
if err != nil {
return 0, 0, err
}
if dir == '-' {
// Could implement reverse search using binary search
// through file, but that seems like overkill.
return 0, 0, errors.New("reverse search not implemented")
}
m := re.FindIndex(data[hi:])
if len(m) > 0 {
m[0] += hi
m[1] += hi
} else if hi > 0 {
// No match. Wrap to beginning of data.
m = re.FindIndex(data)
}
if len(m) == 0 {
return 0, 0, errors.New("no match for " + pattern)
}
return m[0], m[1], nil
}
mmark-1.3-git849458a/doc_test.go 0000664 0000000 0000000 00000003220 12640731723 0016251 0 ustar 00root root 0000000 0000000 // Parse document fragments.
package mmark
import "testing"
var extensions = EXTENSION_TABLES | EXTENSION_FENCED_CODE | EXTENSION_AUTOLINK | EXTENSION_SPACE_HEADERS | EXTENSION_CITATION | EXTENSION_TITLEBLOCK_TOML | EXTENSION_HEADER_IDS | EXTENSION_AUTO_HEADER_IDS | EXTENSION_UNIQUE_HEADER_IDS | EXTENSION_FOOTNOTES | EXTENSION_SHORT_REF | EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK | EXTENSION_INCLUDE | EXTENSION_PARTS | EXTENSION_ABBREVIATIONS
func TestCarriageReturn(t *testing.T) {
var tests = []string{".# Abstract\n\rThis document\n\r# More\n\rand more\n\r{#fig-a}\n\r```\n\rfigure\n\r```\n\rFigure: Traditional Media Server\n\r\n\r{#fig-b}\n\r```\n\rfigure\n\r```\n\rFigure: Endpoint\n\r",
"\n\n\nThis document\n\n\n\n\n\nMore\n\nand more\n\n\n\nfigure\n\n\n\nFigure: Traditional Media Server\n\n\n\nfigure\n\n\n\nFigure: Endpoint\n\n\n",
}
doTestsBlockXML(t, tests, extensions)
}
func TestFigureCaption(t *testing.T) {
var tests = []string{
// This checks the *single* newline after the Figure: caption.
".# Abstract\nThis document\n# More\nand more\n{#fig-a}\n```\nfigure\n```\nFigure: Traditional Media Server\n\n{#fig-b}\n```\nfigure\n```\n",
"\n\n\nThis document\n\n\n\n\n\nMore\n\nand more\n\n\n\nTraditional Media Server\n\n\nfigure\n\n\n\nfigure\n\n\n",
}
doTestsBlockXML(t, tests, extensions)
}
mmark-1.3-git849458a/html.go 0000664 0000000 0000000 00000075132 12640731723 0015424 0 ustar 00root root 0000000 0000000 // HTML rendering backend
package mmark
import (
"bytes"
xmllib "encoding/xml"
"fmt"
"io/ioutil"
"sort"
"strconv"
"strings"
)
// Html renderer configuration options.
const (
HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks
HTML_SKIP_STYLE // skip embedded