package parserimport ()// Parsing block-level elements.const ( charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});" escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]")const ( captionTable = "Table: " captionFigure = "Figure: " captionQuote = "Quote: ")var ( reBackslashOrAmp = regexp.MustCompile(`[\&]`) reEntityOrEscapedChar = regexp.MustCompile(`(?i)\\` + escapable + "|" + charEntity)// blockTags is a set of tags that are recognized as HTML block tags. // Any of these can be included in markdown text without special escaping. blockTags = map[string]struct{}{"blockquote": {},"del": {},"dd": {},"div": {},"dl": {},"dt": {},"fieldset": {},"form": {},"h1": {},"h2": {},"h3": {},"h4": {},"h5": {},"h6": {},// TODO: technically block but breaks Inline HTML (Simple).text //"hr": {},"iframe": {},"ins": {},"li": {},"math": {},"noscript": {},"ol": {},"pre": {},"p": {},"script": {},"style": {},"table": {},"ul": {},// HTML5"address": {},"article": {},"aside": {},"canvas": {},"details": {},"dialog": {},"figcaption": {},"figure": {},"footer": {},"header": {},"hgroup": {},"main": {},"nav": {},"output": {},"progress": {},"section": {},"svg": {},"video": {}, })// sanitizeHeadingID returns a sanitized anchor name for the given text.// Taken from https://github.com/shurcooL/sanitized_anchor_name/blob/master/main.go#L14:1func sanitizeHeadingID( string) string {var []runevar = falsefor , := range {switch {caseunicode.IsLetter() || unicode.IsNumber():if && len() > 0 { = append(, '-') } = false = append(, unicode.ToLower())default: = true } }iflen() == 0 {return"empty" }returnstring()}// Parse Block-level data.// Note: this function and many that it calls assume that// the input buffer ends with a newline.func ( *Parser) ( []byte) {// this is called recursively: enforce a maximum depthif .nesting >= .maxNesting {return } .nesting++// parse out one block-level construct at a timeforlen() > 0 {// attributes that can be specific before a block element: // // {#id .class1 .class2 key="value"}if .extensions&Attributes != 0 { = .attribute() }if .extensions&Includes != 0 { := .readInclude , , := .isInclude()if == 0 { , , = .isCodeInclude() = .readCodeInclude }if > 0 { := (.includeStack.Last(), , )// if we find a caption below this, we need to include it in 'included', so // that the caption will be part of the include text. (+1 to skip newline)for , := range []string{captionFigure, captionTable, captionQuote} {if , , := .caption([+1:], []byte()); > 0 { = append(, [+1:+1+]...) += 1 + break// there can only be 1 caption. } } .includeStack.Push() .() .includeStack.Pop() = [:]continue } }// user supplied parser functionif .Opts.ParserHook != nil { , , := .Opts.ParserHook()if > 0 { = [:]if != nil { .AddBlock()if != nil { .() .Finalize() } }continue } }// prefixed heading: // // # Heading 1 // ## Heading 2 // ... // ###### Heading 6if .isPrefixHeading() { = [.prefixHeading():]continue }// prefixed special heading: // (there are no levels.) // // .# Abstractif .isPrefixSpecialHeading() { = [.prefixSpecialHeading():]continue }// block of preformatted HTML: // // <div> // ... // </div>iflen() == 0 {continue }if [0] == '<' {if := .html(, true); > 0 { = [:]continue } }// title block // // % stuff // % more stuff // % even more stuffif .extensions&Titleblock != 0 {if [0] == '%' {if := .titleBlock(, true); > 0 { = [:]continue } } }// blank lines. note: returns the # of bytes to skipif := 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 // ______ifisHRule() { := skipUntilChar(, 0, '\n') := ast.HorizontalRule{} .Literal = bytes.Trim([:], " \n") .AddBlock(&) = [:]continue }// block quote: // // > A big quote I found somewhere // > on the webif .quotePrefix() > 0 { = [.quote():]continue }// aside: // // A> The proof is too large to fit // A> in the margin.if .extensions&Mmark != 0 {if .asidePrefix() > 0 { = [.aside():]continue } }// figure block: // // !--- //  //  // !---if .extensions&Mmark != 0 {if := .figureBlock(, true); > 0 { = [:]continue } }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, 0, '.'):]continue }// a numbered/ordered list: // // 1. Item 1 // 2. Item 2if := .oliPrefix(); > 0 { := 0 := byte('.')if > 2 {if .extensions&OrderedListStart != 0 { := string([:-2]) , _ = strconv.Atoi()if == 1 { = 0 } } = [-2] } = [.list(, ast.ListTypeOrdered, , ):]continue }// definition lists: // // Term 1 // : Definition a // : Definition b // // Term 2 // : Definition cif .extensions&DefinitionLists != 0 {if .dliPrefix() > 0 { = [.list(, ast.ListTypeDefinition, 0, '.'):]continue } }if .extensions&MathJax != 0 {if := .blockMath(); > 0 { = [:]continue } }// document matters: // // {frontmatter}/{mainmatter}/{backmatter}if .extensions&Mmark != 0 {if := .documentMatter(); > 0 { = [:]continue } }// anything else must look like a normal paragraph // note: this finds underlined headings, too := .paragraph() = [:] } .nesting--}func ( *Parser) ( ast.Node) ast.Node { .closeUnmatchedBlocks()if .attr != nil {if := .AsContainer(); != nil { .Attribute = .attr }if := .AsLeaf(); != nil { .Attribute = .attr } .attr = nil }return .addChild()}func ( *Parser) ( []byte) bool {iflen() > 0 && [0] != '#' {returnfalse }if .extensions&SpaceHeadings != 0 { := skipCharN(, 0, '#', 6)if == len() || [] != ' ' {returnfalse } }returntrue}func ( *Parser) ( []byte) int { := skipCharN(, 0, '#', 6) := skipChar(, , ' ') := skipUntilChar(, , '\n') := := ""if .extensions&HeadingIDs != 0 { , := 0, 0// find start/end of heading idfor = ; < -1 && ([] != '{' || [+1] != '#'); ++ { }for = + 1; < && [] != '}'; ++ { }// extract heading id iff foundif < && < { = string([+2 : ]) = = + 1for > 0 && [-1] == ' ' { -- } } }for > 0 && [-1] == '#' {ifisBackslashEscaped(, -1) {break } -- }for > 0 && [-1] == ' ' { -- }if > { := &ast.Heading{HeadingID: ,Level: , }if == "" && .extensions&AutoHeadingIDs != 0 { .HeadingID = sanitizeHeadingID(string([:])) .allHeadingsWithAutoID = append(.allHeadingsWithAutoID, ) } .Content = [:] .AddBlock() }return}func ( *Parser) ( []byte) bool {if .extensions|Mmark == 0 {returnfalse }iflen() < 4 {returnfalse }if [0] != '.' {returnfalse }if [1] != '#' {returnfalse }if [2] == '#' { // we don't support level, so nack this.returnfalse }if .extensions&SpaceHeadings != 0 {if [2] != ' ' {returnfalse } }returntrue}func ( *Parser) ( []byte) int { := skipChar(, 2, ' ') // ".#" skipped := skipUntilChar(, , '\n') := := ""if .extensions&HeadingIDs != 0 { , := 0, 0// find start/end of heading idfor = ; < -1 && ([] != '{' || [+1] != '#'); ++ { }for = + 1; < && [] != '}'; ++ { }// extract heading id iff foundif < && < { = string([+2 : ]) = = + 1for > 0 && [-1] == ' ' { -- } } }for > 0 && [-1] == '#' {ifisBackslashEscaped(, -1) {break } -- }for > 0 && [-1] == ' ' { -- }if > { := &ast.Heading{HeadingID: ,IsSpecial: true,Level: 1, // always level 1. }if == "" && .extensions&AutoHeadingIDs != 0 { .HeadingID = sanitizeHeadingID(string([:])) .allHeadingsWithAutoID = append(.allHeadingsWithAutoID, ) } .Literal = [:] .Content = [:] .AddBlock() }return}func ( *Parser) ( []byte) int {// test of level 1 headingif [0] == '=' { := skipChar(, 1, '=') = skipChar(, , ' ')if < len() && [] == '\n' {return1 }return0 }// test of level 2 headingif [0] == '-' { := skipChar(, 1, '-') = skipChar(, , ' ')if < len() && [] == '\n' {return2 }return0 }return0}func ( *Parser) ( []byte, bool) int {if [0] != '%' {return0 } := bytes.Split(, []byte("\n"))varintfor , := range {if !bytes.HasPrefix(, []byte("%")) { = // - 1break } } = bytes.Join([0:], []byte("\n")) := len() = bytes.TrimPrefix(, []byte("% ")) = bytes.Replace(, []byte("\n% "), []byte("\n"), -1) := &ast.Heading{Level: 1,IsTitleblock: true, } .Content = .AddBlock()return}func ( *Parser) ( []byte, bool) int {var , int// identify the opening tagif [0] != '<' {return0 } , := .htmlFindTag([1:])// handle special casesif ! {// check for an HTML commentif := .htmlComment(, ); > 0 {return }// check for an <hr> tagif := .htmlHr(, ); > 0 {return }// no special case recognizedreturn0 }// look for an unindented matching closing tag // followed by a blank line := false/* 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 := 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 := 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" { = 1for < len() { ++for < len() && !([-1] == '<' && [] == '/') { ++ }if +2+len() >= len() {break } = .htmlFindEnd(, [-1:])if > 0 { += - 1 = truebreak } } }if ! {return0 }// the end of the block has been foundif {// trim newlines := backChar(, , '\n') := &ast.HTMLBlock{Leaf: ast.Leaf{Content: [:]}} .AddBlock()finalizeHTMLBlock() }return}func finalizeHTMLBlock( *ast.HTMLBlock) { .Literal = .Content .Content = nil}// HTML comment, lax formfunc ( *Parser) ( []byte, bool) int { := .inlineHTMLComment()// needs to end with a blank lineif := IsEmpty([:]); > 0 { := + if {// trim trailing newlines := backChar(, , '\n') := &ast.HTMLBlock{Leaf: ast.Leaf{Content: [:]}} .AddBlock()finalizeHTMLBlock() }return }return0}// HR, which is the only self-closing block tag consideredfunc ( *Parser) ( []byte, bool) int {iflen() < 4 {return0 }if [0] != '<' || ([1] != 'h' && [1] != 'H') || ([2] != 'r' && [2] != 'R') {return0 }if [3] != ' ' && [3] != '/' && [3] != '>' {// not an <hr> tag after all; at least not a valid onereturn0 } := 3for < len() && [] != '>' && [] != '\n' { ++ }if < len() && [] == '>' { ++if := IsEmpty([:]); > 0 { := + if {// trim newlines := backChar(, , '\n') := &ast.HTMLBlock{Leaf: ast.Leaf{Content: [:]}} .AddBlock()finalizeHTMLBlock() }return } }return0}func ( *Parser) ( []byte) (string, bool) { := skipAlnum(, 0) := string([:])if , := blockTags[]; {return , true }return"", false}func ( *Parser) ( string, []byte) int {// assume data[0] == '<' && data[1] == '/' already testedif == "hr" {return2 }// check if tag is a match := []byte("</" + + ">")if !bytes.HasPrefix(, ) {return0 } := len()// check that the rest of the line is blank := 0if = IsEmpty([:]); == 0 {return0 } += = 0if >= len() {return }if .extensions&LaxHTMLBlocks != 0 {return }if = IsEmpty([:]); == 0 {// following line must be blankreturn0 }return + }func ( []byte) int {// it is okay to call isEmpty on an empty bufferiflen() == 0 {return0 }varintfor = 0; < len() && [] != '\n'; ++ {if [] != ' ' && [] != '\t' {return0 } } = skipCharN(, , '\n', 1)return}func isHRule( []byte) bool { := 0// skip up to three spacesfor < 3 && [] == ' ' { ++ }// look at the hrule charif [] != '*' && [] != '-' && [] != '_' {returnfalse } := []// the whole line must be the char or whitespace := 0for < len() && [] != '\n' {switch {case [] == : ++case [] != ' ':returnfalse } ++ }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 syntax is not nil, it gets set to the syntax specified in the fence line.func isFenceLine( []byte, *string, string) ( int, string) { , := 0, 0 := len()// skip up to three spacesfor < && < 3 && [] == ' ' { ++ }// check for the marker characters: ~ or `if >= {return0, "" }if [] != '~' && [] != '`' {return0, "" } := []// the whole line must be the same char or whitespacefor < && [] == { ++ ++ }// the marker char must occur at least 3 timesif < 3 {return0, "" } = string([- : ])// if this is the end marker, it must match the beginning markerif != "" && != {return0, "" }// if just read the beginning marker, read the syntaxif == "" { = skipChar(, , ' ')if >= {if == {return , }return0, "" } , := syntaxRange(, &)if == 0 && == 0 {return0, "" }// caller wants the syntaxif != nil { * = string([ : +]) } } = skipChar(, , ' ')if >= || [] != '\n' {if == {return , }return0, "" }return + 1, // Take newline into account.}func syntaxRange( []byte, *int) (int, int) { := len() := 0 := * := if [] == '{' { ++ ++for < && [] != '}' && [] != '\n' { ++ ++ }if >= || [] != '}' {return0, 0 }// strip all whitespace at the beginning and the end // of the {} blockfor > 0 && IsSpace([]) { ++ -- }for > 0 && IsSpace([+-1]) { -- } ++ } else {for < && [] != '\n' { ++ ++ } } * = return , }// 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 ( *Parser) ( []byte, bool) int {varstring , := isFenceLine(, &, "")if == 0 || >= len() {return0 }varbytes.Buffer .WriteString() .WriteByte('\n')for {// 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() {return0 }// verbatim copy to the working buffer .Write([:]) = }if ! {return } := &ast.CodeBlock{IsFenced: true, } .Content = .Bytes() // TODO: get rid of temp bufferif .extensions&Mmark == 0 { .AddBlock()finalizeCodeBlock()return }// Check for caption and if found make it a figure.if , , := .caption([:], []byte(captionFigure)); > 0 { := &ast.CaptionFigure{} := &ast.Caption{} .HeadingID = .Inline(, ) .AddBlock() .AsLeaf().Attribute = .AsContainer().Attribute .addChild()finalizeCodeBlock() .addChild() .Finalize() += return }// Still here, normal block .AddBlock()finalizeCodeBlock()return}func unescapeChar( []byte) []byte {if [0] == '\\' {return []byte{[1]} }return []byte(html.UnescapeString(string()))}func unescapeString( []byte) []byte {ifreBackslashOrAmp.Match() {returnreEntityOrEscapedChar.ReplaceAllFunc(, unescapeChar) }return}func finalizeCodeBlock( *ast.CodeBlock) { := .Contentif .IsFenced { := bytes.IndexByte(, '\n') := [:] := [+1:] .Info = unescapeString(bytes.Trim(, "\n")) .Literal = } else { .Literal = } .Content = nil}// returns blockquote prefix lengthfunc ( *Parser) ( []byte) int { := 0 := len()for < 3 && < && [] == ' ' { ++ }if < && [] == '>' {if +1 < && [+1] == ' ' {return + 2 }return + 1 }return0}// blockquote ends with at least one blank line// followed by something without a blockquote prefixfunc ( *Parser) ( []byte, , int) bool {ifIsEmpty([:]) <= 0 {returnfalse }if >= len() {returntrue }return .quotePrefix([:]) == 0 && IsEmpty([:]) == 0}// parse a blockquote fragmentfunc ( *Parser) ( []byte) int {varbytes.Buffer , := 0, 0for < 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 itfor < len() && [] != '\n' {if .extensions&FencedCode != 0 {if := .fencedCodeBlock([:], false); > 0 {// -1 to compensate for the extra end++ after the loop: += - 1break } } ++ } = skipCharN(, , '\n', 1)if := .quotePrefix([:]); > 0 {// skip the prefix += } elseif .terminateBlockquote(, , ) {break }// this line is part of the blockquote .Write([:]) = }if .extensions&Mmark == 0 { := .AddBlock(&ast.BlockQuote{}) .Block(.Bytes()) .Finalize()return }if , , := .caption([:], []byte(captionQuote)); > 0 { := &ast.CaptionFigure{} := &ast.Caption{} .HeadingID = .Inline(, ) .AddBlock() // this discard any attributes := &ast.BlockQuote{} .AsContainer().Attribute = .AsContainer().Attribute .addChild() .Block(.Bytes()) .Finalize() .addChild() .Finalize() += return } := .AddBlock(&ast.BlockQuote{}) .Block(.Bytes()) .Finalize()return}// returns prefix length for block codefunc ( *Parser) ( []byte) int { := len()if >= 1 && [0] == '\t' {return1 }if >= 4 && [3] == ' ' && [2] == ' ' && [1] == ' ' && [0] == ' ' {return4 }return0}func ( *Parser) ( []byte) int {varbytes.Buffer := 0for < len() { := = skipUntilChar(, , '\n') = skipCharN(, , '\n', 1) := IsEmpty([:]) > 0if := .codePrefix([:]); > 0 { += } elseif ! {// non-empty, non-prefixed line breaks the pre = break }// verbatim copy to the working bufferif { .WriteByte('\n') } else { .Write([:]) } }// trim all the \n off the end of work := .Bytes() := backChar(, len(), '\n')if != len() { .Truncate() } .WriteByte('\n') := &ast.CodeBlock{IsFenced: false, }// TODO: get rid of temp buffer .Content = .Bytes() .AddBlock()finalizeCodeBlock()return}// returns unordered list item prefixfunc ( *Parser) ( []byte) int {// start with up to 3 spaces := skipCharN(, 0, ' ', 3)if >= len()-1 {return0 }// need one of {'*', '+', '-'} followed by a space or a tabif ([] != '*' && [] != '+' && [] != '-') || ([+1] != ' ' && [+1] != '\t') {return0 }return + 2}// returns ordered list item prefixfunc ( *Parser) ( []byte) int {// start with up to 3 spaces := skipCharN(, 0, ' ', 3)// count the digits := for < len() && [] >= '0' && [] <= '9' { ++ }if == || >= len()-1 {return0 }// we need >= 1 digits followed by a dot and a space or a tabif [] != '.' && [] != ')' || !([+1] == ' ' || [+1] == '\t') {return0 }return + 2}// returns definition list item prefixfunc ( *Parser) ( []byte) int {iflen() < 2 {return0 }// need a ':' followed by a space or a tabif [0] != ':' || !([1] == ' ' || [1] == '\t') {return0 }// TODO: this is a no-op (data[0] is ':' so not ' '). // Maybe the intent was to eat spaces before ':' ? // either way, no change in tests := skipChar(, 0, ' ')return + 2}// TODO: maybe it was meant to be like below// either way, no change in tests/*func (p *Parser) dliPrefix(data []byte) int { i := skipChar(data, 0, ' ') if i+len(data) < 2 { return 0 } // need a ':' followed by a space or a tab if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') { return 0 } return i + 2}*/// parse ordered or unordered list blockfunc ( *Parser) ( []byte, ast.ListType, int, byte) int { := 0 |= ast.ListItemBeginningOfList := &ast.List{ListFlags: ,Tight: true,Start: ,Delimiter: , } := .AddBlock()for < len() { := .listItem([:], &)if &ast.ListItemContainsBlock != 0 { .Tight = false } += if == 0 || &ast.ListItemEndOfList != 0 {break } &= ^ast.ListItemBeginningOfList } := .GetParent()finalizeList() .tip = return}// Returns true if the list item is not the same type as its parent listfunc ( *Parser) ( []byte, *ast.ListType) bool {if .dliPrefix() > 0 && *&ast.ListTypeDefinition == 0 {returntrue } elseif .oliPrefix() > 0 && *&ast.ListTypeOrdered == 0 {returntrue } elseif .uliPrefix() > 0 && (*&ast.ListTypeOrdered != 0 || *&ast.ListTypeDefinition != 0) {returntrue }returnfalse}// Returns true if block ends with a blank line, descending if needed// into lists and sublists.func endsWithBlankLine( ast.Node) bool {// TODO: figure this out. Always false now.for != nil {//if block.lastLineBlank { //return true //}switch .(type) {case *ast.List, *ast.ListItem: = ast.GetLastChild()default:returnfalse } }returnfalse}func finalizeList( *ast.List) { := .Parent.GetChildren() := len() - 1for , := range { := == // check for non-final list item ending with blank line:if ! && endsWithBlankLine() { .Tight = falsebreak }// recurse into children of list item, to see if there are spaces // between any of them: := .GetParent().GetChildren() := len() - 1for , := range { := == if (! || !) && endsWithBlankLine() { .Tight = falsebreak } } }}// Parse a single list item.// Assumes initial prefix is already removed if this is a sublist.func ( *Parser) ( []byte, *ast.ListType) int { := *&ast.ListTypeDefinition != 0// keep track of the indentation of the first line := 0if [0] == '\t' { += 4 } else {for < 3 && [] == ' ' { ++ } }var (byte = '*'byte = '.' ) := .uliPrefix()if == 0 { = .oliPrefix()if > 0 { = [-2] } } else { = [-2] }if == 0 { = .dliPrefix()// reset definition term flagif > 0 { * &= ^ast.ListTypeTerm } }if == 0 {// if in definition list, set term flag and continueif { * |= ast.ListTypeTerm } else {return0 } }// skip leading whitespace on first line = skipChar(, , ' ')// find the end of the line := for > 0 && < len() && [-1] != '\n' { ++ }// get working buffervarbytes.Buffer// put the first line into the working buffer .Write([:]) = // process the following lines := false := 0:for < len() { ++// find the end of this linefor < len() && [-1] != '\n' { ++ }// if it is an empty line, guess that it is part of this item // and move on to the next lineifIsEmpty([:]) > 0 { = true = continue }// calculate the indentation := 0 := 0if [] == '\t' { ++ += 4 } else {for < 4 && + < && [+] == ' ' { ++ ++ } } := [+ : ]// If there is a fence line (marking starting of a code block) // without indent do not process it as part of the list. // // does not apply for definition lists because it causes infinite // loop if text before defintion term is fenced code block start // marker but not part of actual fenced code block // for defnition lists we're called after parsing fence code blocks // so we kno this cannot be a fenced block // https://github.com/gomarkdown/markdown/issues/326if ! && .extensions&FencedCode != 0 { , := isFenceLine(, nil, "")if > 0 && == 0 { * |= ast.ListItemEndOfListbreak } }// evaluate how this line fits inswitch {// is this a nested list item?case (.uliPrefix() > 0 && !isHRule()) || .oliPrefix() > 0 || .dliPrefix() > 0:// if indent is 4 or more spaces on unordered or ordered lists // we need to add leadingWhiteSpaces + 1 spaces in the beginning of the chunkif >= 4 && .dliPrefix() <= 0 { := skipChar(, 0, ' ') = [+-(+1) : ] }// 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 listif <= {if .listTypeChanged(, ) { * |= ast.ListItemEndOfList } elseif { * |= ast.ListItemContainsBlock }break }if { * |= ast.ListItemContainsBlock }// is this the first item in the nested list?if == 0 { = .Len()// in the case of dliPrefix we are too late and need to search back for the definition item, which // should be on the previous line, we then adjust sublist to start there.if .dliPrefix() > 0 { = backUntilChar(.Bytes(), .Len()-1, '\n') } }// is this a nested prefix heading?case .isPrefixHeading(), .isPrefixSpecialHeading():// if the heading is not indented, it is not nested in the list // and thus ends the listif && < 4 { * |= ast.ListItemEndOfListbreak } * |= ast.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:if *&ast.ListTypeDefinition != 0 && < len()-1 {// is the next item still a part of this list? := skipUntilChar(, , '\n')for < len()-1 && [] == '\n' { ++ }if < len()-1 && [] != ':' && < len()-1 && [] != ':' { * |= ast.ListItemEndOfList } } else { * |= ast.ListItemEndOfList }break// a blank line means this should be parsed as a blockcase : .WriteByte('\n') * |= ast.ListItemContainsBlock }// if this line was preceded by one or more blanks, // re-introduce the blank into the bufferif { = false .WriteByte('\n') }// add the line into the working buffer without prefix .Write() = } := .Bytes() := &ast.ListItem{ListFlags: *,Tight: false,BulletChar: ,Delimiter: , } .AddBlock()// render the contents of the list itemif *&ast.ListItemContainsBlock != 0 && *&ast.ListTypeTerm == 0 {// intermediate render of block item, except for definition termif > 0 { .Block([:]) .Block([:]) } else { .Block() } } else {// intermediate render of inline item := &ast.Paragraph{}if > 0 { .Content = [:] } else { .Content = } .addChild()if > 0 { .Block([:]) } }return}// render a single paragraph that has already been parsed outfunc ( *Parser) ( []byte) {iflen() == 0 {return }// trim leading spaces := skipChar(, 0, ' ') := len()// trim trailing newlineif [len()-1] == '\n' { -- }// trim trailing spacesfor > && [-1] == ' ' { -- } := &ast.Paragraph{} .Content = [:] .AddBlock()}// blockMath handle block surround with $$func ( *Parser) ( []byte) int {iflen() <= 4 || [0] != '$' || [1] != '$' || [2] == '$' {return0 }// find next $$varintfor = 2; +1 < len() && ([] != '$' || [+1] != '$'); ++ { }// $$ not matchif +1 == len() {return0 }// render the display math := &ast.MathBlock{} .Literal = [2:] .AddBlock()return + 2}func ( *Parser) ( []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 linevar , , int := tabSizeDefaultif .extensions&TabSizeEight != 0 { = tabSizeDouble }// keep going until we find something to mark the end of the paragraphfor < len() {// 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?if := IsEmpty(); > 0 {// did this blank line followed by a definition list item?if .extensions&DefinitionLists != 0 {if < len()-1 && [+1] == ':' { := .list([:], ast.ListTypeDefinition, 0, '.')if > 0 {return + } } } .renderParagraph([:])return + }// an underline under some text marks a heading, so our paragraph ended on prev lineif > 0 {if := .isUnderlinedHeading(); > 0 {// render the paragraph .renderParagraph([:])// ignore leading and trailing whitespace := - 1for < && [] == ' ' { ++ }for > && [-1] == ' ' { -- } := &ast.Heading{Level: , }if .extensions&AutoHeadingIDs != 0 { .HeadingID = sanitizeHeadingID(string([:])) .allHeadingsWithAutoID = append(.allHeadingsWithAutoID, ) } .Content = [:] .AddBlock()// find the end of the underlinereturnskipUntilChar(, , '\n') } }// if the next line starts a block of HTML, then the paragraph ends hereif .extensions&LaxHTMLBlocks != 0 {if [] == '<' && .html(, false) > 0 {// rewind to before the HTML block .renderParagraph([:])return } }// if there's a prefixed heading or a horizontal rule after this, paragraph is overif .isPrefixHeading() || .isPrefixSpecialHeading() || isHRule() { .renderParagraph([:])return }// if there's a block quote, paragraph is overif .quotePrefix() > 0 { .renderParagraph([:])return }// if there's a fenced code block, paragraph is overif .extensions&FencedCode != 0 {if .fencedCodeBlock(, false) > 0 { .renderParagraph([:])return } }// if there's a figure block, paragraph is overif .extensions&Mmark != 0 {if .figureBlock(, false) > 0 { .renderParagraph([:])return } }// if there's a table, paragraph is overif .extensions&Tables != 0 {if , , := .tableHeader(, false); > 0 { .renderParagraph([:])return } }// if there's a definition list item, prev line is a definition termif .extensions&DefinitionLists != 0 {if .dliPrefix() != 0 { := .list([:], ast.ListTypeDefinition, 0, '.')return + } }// if there's a list after this, paragraph is overif .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}// skipChar advances i as long as data[i] == cfunc skipChar( []byte, int, byte) int { := len()for < && [] == { ++ }return}// like skipChar but only skips up to max charactersfunc skipCharN( []byte, int, byte, int) int { := len()for < && > 0 && [] == { ++ -- }return}// skipUntilChar advances i as long as data[i] != cfunc skipUntilChar( []byte, int, byte) int { := len()for < && [] != { ++ }return}func skipAlnum( []byte, int) int { := len()for < && IsAlnum([]) { ++ }return}func skipSpace( []byte, int) int { := len()for < && IsSpace([]) { ++ }return}func backChar( []byte, int, byte) int {for > 0 && [-1] == { -- }return}func backUntilChar( []byte, int, byte) int {for > 0 && [-1] != { -- }return}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.