Blackfriday Markdown Processor Available at http://github.com/russross/blackfriday Copyright © 2011 Russ Ross <russ@russross.com>. Distributed under the Simplified BSD License. See README.md for details.
Functions to parse block-level elements.

package blackfriday

import (
	
	
	
	

	
)

const (
	charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"
	escapable  = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]"
)

var (
	reBackslashOrAmp      = regexp.MustCompile("[\\&]")
	reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
)
Parse block-level data. Note: this function and many that it calls assume that the input buffer ends with a newline.
this is called recursively: enforce a maximum depth
	if .nesting >= .maxNesting {
		return
	}
	.nesting++
parse out one block-level construct at a time
prefixed heading: # Heading 1 ## Heading 2 ... ###### Heading 6
		if .isPrefixHeading() {
			 = [.prefixHeading():]
			continue
		}
block of preformatted HTML: <div> ... </div>
		if [0] == '<' {
			if  := .html(, true);  > 0 {
				 = [:]
				continue
			}
		}
title block % stuff % more stuff % even more stuff
		if .extensions&Titleblock != 0 {
			if [0] == '%' {
				if  := .titleBlock(, true);  > 0 {
					 = [:]
					continue
				}
			}
		}
blank lines. note: returns the # of bytes to skip
		if  := .isEmpty();  > 0 {
			 = [:]
			continue
		}
indented code block: func max(a, b int) int { if a > b { return a } return b }
		if .codePrefix() > 0 {
			 = [.code():]
			continue
		}
fenced code block: ``` go func fact(n int) int { if n <= 1 { return n } return n * fact(n-1) } ```
		if .extensions&FencedCode != 0 {
			if  := .fencedCodeBlock(, true);  > 0 {
				 = [:]
				continue
			}
		}
horizontal rule: ------ or ****** or ______
		if .isHRule() {
			.addBlock(HorizontalRule, nil)
			var  int
			for  = 0;  < len() && [] != '\n'; ++ {
			}
			 = [:]
			continue
		}
block quote: > A big quote I found somewhere > on the web
		if .quotePrefix() > 0 {
			 = [.quote():]
			continue
		}
table: Name | Age | Phone ------|-----|--------- Bob | 31 | 555-1234 Alice | 27 | 555-4321
		if .extensions&Tables != 0 {
			if  := .table();  > 0 {
				 = [:]
				continue
			}
		}
an itemized/unordered list: * Item 1 * Item 2 also works with + or -
		if .uliPrefix() > 0 {
			 = [.list(, 0):]
			continue
		}
a numbered/ordered list: 1. Item 1 2. Item 2
		if .oliPrefix() > 0 {
			 = [.list(, ListTypeOrdered):]
			continue
		}
definition lists: Term 1 : Definition a : Definition b Term 2 : Definition c
		if .extensions&DefinitionLists != 0 {
			if .dliPrefix() > 0 {
				 = [.list(, ListTypeDefinition):]
				continue
			}
		}
anything else must look like a normal paragraph note: this finds underlined headings, too
		 = [.paragraph():]
	}

	.nesting--
}

func ( *Markdown) ( NodeType,  []byte) *Node {
	.closeUnmatchedBlocks()
	 := .addChild(, 0)
	.content = 
	return 
}

func ( *Markdown) ( []byte) bool {
	if [0] != '#' {
		return false
	}

	if .extensions&SpaceHeadings != 0 {
		 := 0
		for  < 6 &&  < len() && [] == '#' {
			++
		}
		if  == len() || [] != ' ' {
			return false
		}
	}
	return true
}

func ( *Markdown) ( []byte) int {
	 := 0
	for  < 6 &&  < len() && [] == '#' {
		++
	}
	 := skipChar(, , ' ')
	 := skipUntilChar(, , '\n')
	 := 
	 := ""
	if .extensions&HeadingIDs != 0 {
find start/end of heading id
		for  = ;  < -1 && ([] != '{' || [+1] != '#'); ++ {
		}
		for  =  + 1;  <  && [] != '}'; ++ {
extract heading id iff found
		if  <  &&  <  {
			 = string([+2 : ])
			 = 
			 =  + 1
			for  > 0 && [-1] == ' ' {
				--
			}
		}
	}
	for  > 0 && [-1] == '#' {
		if isBackslashEscaped(, -1) {
			break
		}
		--
	}
	for  > 0 && [-1] == ' ' {
		--
	}
	if  >  {
		if  == "" && .extensions&AutoHeadingIDs != 0 {
			 = sanitized_anchor_name.Create(string([:]))
		}
		 := .addBlock(Heading, [:])
		.HeadingID = 
		.Level = 
	}
	return 
}

test of level 1 heading
	if [0] == '=' {
		 := skipChar(, 1, '=')
		 = skipChar(, , ' ')
		if  < len() && [] == '\n' {
			return 1
		}
		return 0
	}
test of level 2 heading
	if [0] == '-' {
		 := skipChar(, 1, '-')
		 = skipChar(, , ' ')
		if  < len() && [] == '\n' {
			return 2
		}
		return 0
	}

	return 0
}

func ( *Markdown) ( []byte,  bool) int {
	if [0] != '%' {
		return 0
	}
	 := bytes.Split(, []byte("\n"))
	var  int
	for ,  := range  {
		if !bytes.HasPrefix(, []byte("%")) {
			 =  // - 1
			break
		}
	}

	 = bytes.Join([0:], []byte("\n"))
	 := len()
	 = bytes.TrimPrefix(, []byte("% "))
	 = bytes.Replace(, []byte("\n% "), []byte("\n"), -1)
	 := .addBlock(Heading, )
	.Level = 1
	.IsTitleblock = true

	return 
}

func ( *Markdown) ( []byte,  bool) int {
	var ,  int
identify the opening tag
	if [0] != '<' {
		return 0
	}
	,  := .htmlFindTag([1:])
handle special cases
check for an HTML comment
		if  := .htmlComment(, );  > 0 {
			return 
		}
check for an <hr> tag
		if  := .htmlHr(, );  > 0 {
			return 
		}
no special case recognized
		return 0
	}
look for an unindented matching closing tag followed by a blank line
closetag := []byte("\n</" + curtag + ">") j = len(curtag) + 1 for !found { scan for a closing tag at the beginning of a line if skip := bytes.Index(data[j:], closetag); skip >= 0 { j += skip + len(closetag) } else { break }
see if it is the only thing on the line if skip := p.isEmpty(data[j:]); skip > 0 { see if it is followed by a blank line/eof j += skip if j >= len(data) { found = true i = j } else { if skip := p.isEmpty(data[j:]); skip > 0 { j += skip found = true i = j } } } }
if not found, try a second pass looking for indented match but not if tag is "ins" or "del" (following original Markdown.pl)
	if ! &&  != "ins" &&  != "del" {
		 = 1
		for  < len() {
			++
			for  < len() && !([-1] == '<' && [] == '/') {
				++
			}

			if +2+len() >= len() {
				break
			}

			 = .htmlFindEnd(, [-1:])

			if  > 0 {
				 +=  - 1
				 = true
				break
			}
		}
	}

	if ! {
		return 0
	}
the end of the block has been found
trim newlines
		 := 
		for  > 0 && [-1] == '\n' {
			--
		}
		finalizeHTMLBlock(.addBlock(HTMLBlock, [:]))
	}

	return 
}

func ( *Node) {
	.Literal = .content
	.content = nil
}
HTML comment, lax form
func ( *Markdown) ( []byte,  bool) int {
needs to end with a blank line
	if  := .isEmpty([:]);  > 0 {
		 :=  + 
trim trailing newlines
			 := 
			for  > 0 && [-1] == '\n' {
				--
			}
			 := .addBlock(HTMLBlock, [:])
			finalizeHTMLBlock()
		}
		return 
	}
	return 0
}
HR, which is the only self-closing block tag considered
func ( *Markdown) ( []byte,  bool) int {
	if len() < 4 {
		return 0
	}
	if [0] != '<' || ([1] != 'h' && [1] != 'H') || ([2] != 'r' && [2] != 'R') {
		return 0
	}
not an <hr> tag after all; at least not a valid one
		return 0
	}
	 := 3
	for  < len() && [] != '>' && [] != '\n' {
		++
	}
	if  < len() && [] == '>' {
		++
		if  := .isEmpty([:]);  > 0 {
			 :=  + 
trim newlines
				 := 
				for  > 0 && [-1] == '\n' {
					--
				}
				finalizeHTMLBlock(.addBlock(HTMLBlock, [:]))
			}
			return 
		}
	}
	return 0
}

func ( *Markdown) ( []byte) (string, bool) {
	 := 0
	for  < len() && isalnum([]) {
		++
	}
	 := string([:])
	if ,  := blockTags[];  {
		return , true
	}
	return "", false
}

assume data[0] == '<' && data[1] == '/' already tested
	if  == "hr" {
		return 2
check if tag is a match
	 := []byte("</" +  + ">")
	if !bytes.HasPrefix(, ) {
		return 0
	}
	 := len()
check that the rest of the line is blank
	 := 0
	if  = .isEmpty([:]);  == 0 {
		return 0
	}
	 += 
	 = 0

	if  >= len() {
		return 
	}

	if .extensions&LaxHTMLBlocks != 0 {
		return 
	}
following line must be blank
		return 0
	}

	return  + 
}

it is okay to call isEmpty on an empty buffer
	if len() == 0 {
		return 0
	}

	var  int
	for  = 0;  < len() && [] != '\n'; ++ {
		if [] != ' ' && [] != '\t' {
			return 0
		}
	}
	if  < len() && [] == '\n' {
		++
	}
	return 
}

func (*Markdown) ( []byte) bool {
	 := 0
skip up to three spaces
	for  < 3 && [] == ' ' {
		++
	}
look at the hrule char
	if [] != '*' && [] != '-' && [] != '_' {
		return false
	}
	 := []
the whole line must be the char or whitespace
	 := 0
	for  < len() && [] != '\n' {
		switch {
		case [] == :
			++
		case [] != ' ':
			return false
		}
		++
	}

	return  >= 3
}
isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data, and returns the end index if so, or 0 otherwise. It also returns the marker found. If info is not nil, it gets set to the syntax specified in the fence line.
func ( []byte,  *string,  string) ( int,  string) {
	,  := 0, 0
skip up to three spaces
	for  < len() &&  < 3 && [] == ' ' {
		++
	}
check for the marker characters: ~ or `
	if  >= len() {
		return 0, ""
	}
	if [] != '~' && [] != '`' {
		return 0, ""
	}

	 := []
the whole line must be the same char or whitespace
	for  < len() && [] ==  {
		++
		++
	}
the marker char must occur at least 3 times
	if  < 3 {
		return 0, ""
	}
	 = string([- : ])
if this is the end marker, it must match the beginning marker
	if  != "" &&  !=  {
		return 0, ""
	}
TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here into one, always get the info string, and discard it if the caller doesn't care.
	if  != nil {
		 := 0
		 = skipChar(, , ' ')

		if  >= len() {
			if  == len() {
				return , 
			}
			return 0, ""
		}

		 := 

		if [] == '{' {
			++
			++

			for  < len() && [] != '}' && [] != '\n' {
				++
				++
			}

			if  >= len() || [] != '}' {
				return 0, ""
			}
strip all whitespace at the beginning and the end of the {} block
			for  > 0 && isspace([]) {
				++
				--
			}

			for  > 0 && isspace([+-1]) {
				--
			}
			++
			 = skipChar(, , ' ')
		} else {
			for  < len() && !isverticalspace([]) {
				++
				++
			}
		}

		* = strings.TrimSpace(string([ : +]))
	}

	if  == len() {
		return , 
	}
	if  > len() || [] != '\n' {
		return 0, ""
	}
	return  + 1,  // Take newline into account.
}
fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. If doRender is true, a final newline is mandatory to recognize the fenced code block.
func ( *Markdown) ( []byte,  bool) int {
	var  string
	,  := isFenceLine(, &, "")
	if  == 0 ||  >= len() {
		return 0
	}

	var  bytes.Buffer
	.Write([]byte())
	.WriteByte('\n')

safe to assume beg < len(data)
check for the end of the code block
		,  := isFenceLine([:], nil, )
		if  != 0 {
			 += 
			break
		}
copy the current line
		 := skipUntilChar(, , '\n') + 1
did we reach the end of the buffer without a closing marker?
		if  >= len() {
			return 0
		}
verbatim copy to the working buffer
		if  {
			.Write([:])
		}
		 = 
	}

	if  {
		 := .addBlock(CodeBlock, .Bytes()) // TODO: get rid of temp buffer
		.IsFenced = true
		finalizeCodeBlock()
	}

	return 
}

func ( []byte) []byte {
	if [0] == '\\' {
		return []byte{[1]}
	}
	return []byte(html.UnescapeString(string()))
}

func ( []byte) []byte {
	if reBackslashOrAmp.Match() {
		return reEntityOrEscapedChar.ReplaceAllFunc(, unescapeChar)
	}
	return 
}

func ( *Node) {
	if .IsFenced {
		 := bytes.IndexByte(.content, '\n')
		 := .content[:]
		 := .content[+1:]
		.Info = unescapeString(bytes.Trim(, "\n"))
		.Literal = 
	} else {
		.Literal = .content
	}
	.content = nil
}

func ( *Markdown) ( []byte) int {
	 := .addBlock(Table, nil)
	,  := .tableHeader()
	if  == 0 {
		.tip = .Parent
		.Unlink()
		return 0
	}

	.addBlock(TableBody, nil)

	for  < len() {
		,  := 0, 
		for ;  < len() && [] != '\n'; ++ {
			if [] == '|' {
				++
			}
		}

		if  == 0 {
			 = 
			break
		}
include the newline in data sent to tableRow
		if  < len() && [] == '\n' {
			++
		}
		.tableRow([:], , false)
	}

	return 
}
check if the specified position is preceded by an odd number of backslashes
func ( []byte,  int) bool {
	 := 0
	for --1 >= 0 && [--1] == '\\' {
		++
	}
	return &1 == 1
}

func ( *Markdown) ( []byte) ( int,  []CellAlignFlags) {
	 := 0
	 := 1
	for  = 0;  < len() && [] != '\n'; ++ {
		if [] == '|' && !isBackslashEscaped(, ) {
			++
		}
	}
doesn't look like a table header
	if  == 1 {
		return
	}
include the newline in the data sent to tableRow
	 := 
	if  < len() && [] == '\n' {
		++
	}
	 := [:]
column count ignores pipes at beginning or end of line
	if [0] == '|' {
		--
	}
	if  > 2 && [-1] == '|' && !isBackslashEscaped(, -1) {
		--
	}

	 = make([]CellAlignFlags, )
move on to the header underline
	++
	if  >= len() {
		return
	}

	if [] == '|' && !isBackslashEscaped(, ) {
		++
	}
	 = skipChar(, , ' ')
each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3 and trailing | optional on last column
	 := 0
	for  < len() && [] != '\n' {
		 := 0

		if [] == ':' {
			++
			[] |= TableAlignmentLeft
			++
		}
		for  < len() && [] == '-' {
			++
			++
		}
		if  < len() && [] == ':' {
			++
			[] |= TableAlignmentRight
			++
		}
		for  < len() && [] == ' ' {
			++
		}
		if  == len() {
			return
end of column test is messy
		switch {
not a valid column
			return

marker found, now skip past trailing whitespace
			++
			++
			for  < len() && [] == ' ' {
				++
			}
trailing junk found after last column
			if  >=  &&  < len() && [] != '\n' {
				return
			}

something else found where marker was required
			return

marker is optional for the last column
			++

trailing junk found after last column
			return
		}
	}
	if  !=  {
		return
	}

	.addBlock(TableHead, nil)
	.tableRow(, , true)
	 = 
	if  < len() && [] == '\n' {
		++
	}
	return
}

func ( *Markdown) ( []byte,  []CellAlignFlags,  bool) {
	.addBlock(TableRow, nil)
	,  := 0, 0

	if [] == '|' && !isBackslashEscaped(, ) {
		++
	}

	for  = 0;  < len() &&  < len(); ++ {
		for  < len() && [] == ' ' {
			++
		}

		 := 

		for  < len() && ([] != '|' || isBackslashEscaped(, )) && [] != '\n' {
			++
		}

		 := 
skip the end-of-cell marker, possibly taking us past end of buffer
		++

		for  >  && -1 < len() && [-1] == ' ' {
			--
		}

		 := .addBlock(TableCell, [:])
		.IsHeader = 
		.Align = []
	}
pad it out with empty columns to get the right number
	for ;  < len(); ++ {
		 := .addBlock(TableCell, nil)
		.IsHeader = 
		.Align = []
	}
silently ignore rows with too many cells
}
returns blockquote prefix length
func ( *Markdown) ( []byte) int {
	 := 0
	for  < 3 &&  < len() && [] == ' ' {
		++
	}
	if  < len() && [] == '>' {
		if +1 < len() && [+1] == ' ' {
			return  + 2
		}
		return  + 1
	}
	return 0
}
blockquote ends with at least one blank line followed by something without a blockquote prefix
func ( *Markdown) ( []byte, ,  int) bool {
	if .isEmpty([:]) <= 0 {
		return false
	}
	if  >= len() {
		return true
	}
	return .quotePrefix([:]) == 0 && .isEmpty([:]) == 0
}
parse a blockquote fragment
func ( *Markdown) ( []byte) int {
	 := .addBlock(BlockQuote, nil)
	var  bytes.Buffer
	,  := 0, 0
	for  < len() {
Step over whole lines, collecting them. While doing that, check for fenced code and if one's found, incorporate it altogether, irregardless of any contents inside it
		for  < len() && [] != '\n' {
			if .extensions&FencedCode != 0 {
-1 to compensate for the extra end++ after the loop:
					 +=  - 1
					break
				}
			}
			++
		}
		if  < len() && [] == '\n' {
			++
		}
skip the prefix
			 += 
		} else if .terminateBlockquote(, , ) {
			break
this line is part of the blockquote
		.Write([:])
		 = 
	}
	.block(.Bytes())
	.finalize()
	return 
}
returns prefix length for block code
func ( *Markdown) ( []byte) int {
	if len() >= 1 && [0] == '\t' {
		return 1
	}
	if len() >= 4 && [0] == ' ' && [1] == ' ' && [2] == ' ' && [3] == ' ' {
		return 4
	}
	return 0
}

func ( *Markdown) ( []byte) int {
	var  bytes.Buffer

	 := 0
	for  < len() {
		 := 
		for  < len() && [] != '\n' {
			++
		}
		if  < len() && [] == '\n' {
			++
		}

		 := .isEmpty([:]) > 0
		if  := .codePrefix([:]);  > 0 {
			 += 
non-empty, non-prefixed line breaks the pre
			 = 
			break
		}
verbatim copy to the working buffer
		if  {
			.WriteByte('\n')
		} else {
			.Write([:])
		}
	}
trim all the \n off the end of work
	 := .Bytes()
	 := len()
	for  > 0 && [-1] == '\n' {
		--
	}
	if  != len() {
		.Truncate()
	}

	.WriteByte('\n')

	 := .addBlock(CodeBlock, .Bytes()) // TODO: get rid of temp buffer
	.IsFenced = false
	finalizeCodeBlock()

	return 
}
returns unordered list item prefix
func ( *Markdown) ( []byte) int {
start with up to 3 spaces
	for  < len() &&  < 3 && [] == ' ' {
		++
	}
	if  >= len()-1 {
		return 0
need one of {'*', '+', '-'} followed by a space or a tab
	if ([] != '*' && [] != '+' && [] != '-') ||
		([+1] != ' ' && [+1] != '\t') {
		return 0
	}
	return  + 2
}
returns ordered list item prefix
func ( *Markdown) ( []byte) int {
	 := 0
start with up to 3 spaces
	for  < 3 &&  < len() && [] == ' ' {
		++
	}
count the digits
	 := 
	for  < len() && [] >= '0' && [] <= '9' {
		++
	}
	if  ==  ||  >= len()-1 {
		return 0
	}
we need >= 1 digits followed by a dot and a space or a tab
	if [] != '.' || !([+1] == ' ' || [+1] == '\t') {
		return 0
	}
	return  + 2
}
returns definition list item prefix
func ( *Markdown) ( []byte) int {
	if len() < 2 {
		return 0
	}
need a ':' followed by a space or a tab
	if [] != ':' || !([+1] == ' ' || [+1] == '\t') {
		return 0
	}
	for  < len() && [] == ' ' {
		++
	}
	return  + 2
}
parse ordered or unordered list block
func ( *Markdown) ( []byte,  ListType) int {
	 := 0
	 |= ListItemBeginningOfList
	 := .addBlock(List, nil)
	.ListFlags = 
	.Tight = true

	for  < len() {
		 := .listItem([:], &)
		if &ListItemContainsBlock != 0 {
			.ListData.Tight = false
		}
		 += 
		if  == 0 || &ListItemEndOfList != 0 {
			break
		}
		 &= ^ListItemBeginningOfList
	}

	 := .Parent
	finalizeList()
	.tip = 
	return 
}
Returns true if the list item is not the same type as its parent list
func ( *Markdown) ( []byte,  *ListType) bool {
	if .dliPrefix() > 0 && *&ListTypeDefinition == 0 {
		return true
	} else if .oliPrefix() > 0 && *&ListTypeOrdered == 0 {
		return true
	} else if .uliPrefix() > 0 && (*&ListTypeOrdered != 0 || *&ListTypeDefinition != 0) {
		return true
	}
	return false
}
Returns true if block ends with a blank line, descending if needed into lists and sublists.
TODO: figure this out. Always false now.
if block.lastLineBlank { return true }
		 := .Type
		if  == List ||  == Item {
			 = .LastChild
		} else {
			break
		}
	}
	return false
}

func ( *Node) {
	.open = false
	 := .FirstChild
check for non-final list item ending with blank line:
		if endsWithBlankLine() && .Next != nil {
			.ListData.Tight = false
			break
recurse into children of list item, to see if there are spaces between any of them:
		 := .FirstChild
		for  != nil {
			if endsWithBlankLine() && (.Next != nil || .Next != nil) {
				.ListData.Tight = false
				break
			}
			 = .Next
		}
		 = .Next
	}
}
Parse a single list item. Assumes initial prefix is already removed if this is a sublist.
keep track of the indentation of the first line
	 := 0
	if [0] == '\t' {
		 += 4
	} else {
		for  < 3 && [] == ' ' {
			++
		}
	}

	var  byte = '*'
	 := .uliPrefix()
	if  == 0 {
		 = .oliPrefix()
	} else {
		 = [-2]
	}
	if  == 0 {
reset definition term flag
		if  > 0 {
			* &= ^ListTypeTerm
		}
	}
if in definition list, set term flag and continue
		if *&ListTypeDefinition != 0 {
			* |= ListTypeTerm
		} else {
			return 0
		}
	}
skip leading whitespace on first line
	for  < len() && [] == ' ' {
		++
	}
find the end of the line
	 := 
	for  > 0 &&  < len() && [-1] != '\n' {
		++
	}
get working buffer
	var  bytes.Buffer
put the first line into the working buffer
	.Write([:])
	 = 
process the following lines
	 := false
	 := 0
	 := ""

:
	for  < len() {
		++
find the end of this line
		for  < len() && [-1] != '\n' {
			++
		}
if it is an empty line, guess that it is part of this item and move on to the next line
		if .isEmpty([:]) > 0 {
			 = true
			 = 
			continue
		}
calculate the indentation
		 := 0
		 := 0
		if [] == '\t' {
			++
			 += 4
		} else {
			for  < 4 && + <  && [+] == ' ' {
				++
				++
			}
		}

		 := [+ : ]

determine if in or out of codeblock if in codeblock, ignore normal list processing
			,  := isFenceLine(, nil, )
			if  != "" {
start of codeblock
					 = 
end of codeblock.
					 = ""
				}
we are in a codeblock, write line, and continue
			if  != "" ||  != "" {
				.Write([+ : ])
				 = 
				continue 
			}
		}
evaluate how this line fits in
is this a nested list item?
		case (.uliPrefix() > 0 && !.isHRule()) ||
			.oliPrefix() > 0 ||
			.dliPrefix() > 0:
to be a nested list, it must be indented more if not, it is either a different kind of list or the next item in the same list
			if  <=  {
				if .listTypeChanged(, ) {
					* |= ListItemEndOfList
				} else if  {
					* |= ListItemContainsBlock
				}

				break 
			}

			if  {
				* |= ListItemContainsBlock
			}
is this the first item in the nested list?
			if  == 0 {
				 = .Len()
			}
is this a nested prefix heading?
if the heading is not indented, it is not nested in the list and thus ends the list
			if  &&  < 4 {
				* |= ListItemEndOfList
				break 
			}
			* |= ListItemContainsBlock
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  &&  < 4:
is the next item still a part of this list?
				 := 
				for  < len() && [] != '\n' {
					++
				}
				for  < len()-1 && [] == '\n' {
					++
				}
				if  < len()-1 && [] != ':' && [] != ':' {
					* |= ListItemEndOfList
				}
			} else {
				* |= ListItemEndOfList
			}
			break 
a blank line means this should be parsed as a block
		case :
			.WriteByte('\n')
			* |= ListItemContainsBlock
		}
if this line was preceded by one or more blanks, re-introduce the blank into the buffer
		if  {
			 = false
			.WriteByte('\n')
		}
add the line into the working buffer without prefix
		.Write([+ : ])

		 = 
	}

	 := .Bytes()

	 := .addBlock(Item, nil)
	.ListFlags = *
	.Tight = false
	.BulletChar = 
	.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark
render the contents of the list item
intermediate render of block item, except for definition term
		if  > 0 {
			.block([:])
			.block([:])
		} else {
			.block()
		}
intermediate render of inline item
		if  > 0 {
			 := .addChild(Paragraph, 0)
			.content = [:]
			.block([:])
		} else {
			 := .addChild(Paragraph, 0)
			.content = 
		}
	}
	return 
}
render a single paragraph that has already been parsed out
func ( *Markdown) ( []byte) {
	if len() == 0 {
		return
	}
trim leading spaces
	 := 0
	for [] == ' ' {
		++
	}

trim trailing newline
	if [len()-1] == '\n' {
		--
	}
trim trailing spaces
	for  >  && [-1] == ' ' {
		--
	}

	.addBlock(Paragraph, [:])
}

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 , ,  int
	 := TabSizeDefault
	if .extensions&TabSizeEight != 0 {
		 = TabSizeDouble
keep going until we find something to mark the end of the paragraph
mark the beginning of the current line
		 = 
		 := [:]
		 = 
did we find a reference or a footnote? If so, end a paragraph preceding it and report that we have consumed up to the end of that reference:
		if  := isReference(, , );  > 0 {
			.renderParagraph([:])
			return  + 
		}
did we find a blank line marking the end of the paragraph?
did this blank line followed by a definition list item?
			if .extensions&DefinitionLists != 0 {
				if  < len()-1 && [+1] == ':' {
					return .list([:], ListTypeDefinition)
				}
			}

			.renderParagraph([:])
			return  + 
		}
an underline under some text marks a heading, so our paragraph ended on prev line
		if  > 0 {
render the paragraph
				.renderParagraph([:])
ignore leading and trailing whitespace
				 :=  - 1
				for  <  && [] == ' ' {
					++
				}
				for  >  && [-1] == ' ' {
					--
				}

				 := ""
				if .extensions&AutoHeadingIDs != 0 {
					 = sanitized_anchor_name.Create(string([:]))
				}

				 := .addBlock(Heading, [:])
				.Level = 
				.HeadingID = 
find the end of the underline
				for  < len() && [] != '\n' {
					++
				}
				return 
			}
		}
if the next line starts a block of HTML, then the paragraph ends here
rewind to before the HTML block
				.renderParagraph([:])
				return 
			}
		}
if there's a prefixed heading or a horizontal rule after this, paragraph is over
		if .isPrefixHeading() || .isHRule() {
			.renderParagraph([:])
			return 
		}
if there's a fenced code block, paragraph is over
		if .extensions&FencedCode != 0 {
			if .fencedCodeBlock(, false) > 0 {
				.renderParagraph([:])
				return 
			}
		}
if there's a definition list item, prev line is a definition term
		if .extensions&DefinitionLists != 0 {
			if .dliPrefix() != 0 {
				 := .list([:], ListTypeDefinition)
				return 
			}
		}
if there's a list after this, paragraph is over
		if .extensions&NoEmptyLineBeforeBlock != 0 {
			if .uliPrefix() != 0 ||
				.oliPrefix() != 0 ||
				.quotePrefix() != 0 ||
				.codePrefix() != 0 {
				.renderParagraph([:])
				return 
			}
		}
otherwise, scan to the beginning of the next line
		 := bytes.IndexByte([:], '\n')
		if  >= 0 {
			 +=  + 1
		} else {
			 += len([:])
		}
	}

	.renderParagraph([:])
	return 
}

func ( []byte,  int,  byte) int {
	 := 
	for  < len() && [] ==  {
		++
	}
	return 
}

func ( []byte,  int,  byte) int {
	 := 
	for  < len() && [] !=  {
		++
	}
	return