...

Source file src/github.com/russross/blackfriday/v2/block.go

Documentation: github.com/russross/blackfriday/v2

     1  //
     2  // Blackfriday Markdown Processor
     3  // Available at http://github.com/russross/blackfriday
     4  //
     5  // Copyright © 2011 Russ Ross <russ@russross.com>.
     6  // Distributed under the Simplified BSD License.
     7  // See README.md for details.
     8  //
     9  
    10  //
    11  // Functions to parse block-level elements.
    12  //
    13  
    14  package blackfriday
    15  
    16  import (
    17  	"bytes"
    18  	"html"
    19  	"regexp"
    20  	"strings"
    21  	"unicode"
    22  )
    23  
    24  const (
    25  	charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"
    26  	escapable  = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]"
    27  )
    28  
    29  var (
    30  	reBackslashOrAmp      = regexp.MustCompile("[\\&]")
    31  	reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
    32  )
    33  
    34  // Parse block-level data.
    35  // Note: this function and many that it calls assume that
    36  // the input buffer ends with a newline.
    37  func (p *Markdown) block(data []byte) {
    38  	// this is called recursively: enforce a maximum depth
    39  	if p.nesting >= p.maxNesting {
    40  		return
    41  	}
    42  	p.nesting++
    43  
    44  	// parse out one block-level construct at a time
    45  	for len(data) > 0 {
    46  		// prefixed heading:
    47  		//
    48  		// # Heading 1
    49  		// ## Heading 2
    50  		// ...
    51  		// ###### Heading 6
    52  		if p.isPrefixHeading(data) {
    53  			data = data[p.prefixHeading(data):]
    54  			continue
    55  		}
    56  
    57  		// block of preformatted HTML:
    58  		//
    59  		// <div>
    60  		//     ...
    61  		// </div>
    62  		if data[0] == '<' {
    63  			if i := p.html(data, true); i > 0 {
    64  				data = data[i:]
    65  				continue
    66  			}
    67  		}
    68  
    69  		// title block
    70  		//
    71  		// % stuff
    72  		// % more stuff
    73  		// % even more stuff
    74  		if p.extensions&Titleblock != 0 {
    75  			if data[0] == '%' {
    76  				if i := p.titleBlock(data, true); i > 0 {
    77  					data = data[i:]
    78  					continue
    79  				}
    80  			}
    81  		}
    82  
    83  		// blank lines.  note: returns the # of bytes to skip
    84  		if i := p.isEmpty(data); i > 0 {
    85  			data = data[i:]
    86  			continue
    87  		}
    88  
    89  		// indented code block:
    90  		//
    91  		//     func max(a, b int) int {
    92  		//         if a > b {
    93  		//             return a
    94  		//         }
    95  		//         return b
    96  		//      }
    97  		if p.codePrefix(data) > 0 {
    98  			data = data[p.code(data):]
    99  			continue
   100  		}
   101  
   102  		// fenced code block:
   103  		//
   104  		// ``` go
   105  		// func fact(n int) int {
   106  		//     if n <= 1 {
   107  		//         return n
   108  		//     }
   109  		//     return n * fact(n-1)
   110  		// }
   111  		// ```
   112  		if p.extensions&FencedCode != 0 {
   113  			if i := p.fencedCodeBlock(data, true); i > 0 {
   114  				data = data[i:]
   115  				continue
   116  			}
   117  		}
   118  
   119  		// horizontal rule:
   120  		//
   121  		// ------
   122  		// or
   123  		// ******
   124  		// or
   125  		// ______
   126  		if p.isHRule(data) {
   127  			p.addBlock(HorizontalRule, nil)
   128  			var i int
   129  			for i = 0; i < len(data) && data[i] != '\n'; i++ {
   130  			}
   131  			data = data[i:]
   132  			continue
   133  		}
   134  
   135  		// block quote:
   136  		//
   137  		// > A big quote I found somewhere
   138  		// > on the web
   139  		if p.quotePrefix(data) > 0 {
   140  			data = data[p.quote(data):]
   141  			continue
   142  		}
   143  
   144  		// table:
   145  		//
   146  		// Name  | Age | Phone
   147  		// ------|-----|---------
   148  		// Bob   | 31  | 555-1234
   149  		// Alice | 27  | 555-4321
   150  		if p.extensions&Tables != 0 {
   151  			if i := p.table(data); i > 0 {
   152  				data = data[i:]
   153  				continue
   154  			}
   155  		}
   156  
   157  		// an itemized/unordered list:
   158  		//
   159  		// * Item 1
   160  		// * Item 2
   161  		//
   162  		// also works with + or -
   163  		if p.uliPrefix(data) > 0 {
   164  			data = data[p.list(data, 0):]
   165  			continue
   166  		}
   167  
   168  		// a numbered/ordered list:
   169  		//
   170  		// 1. Item 1
   171  		// 2. Item 2
   172  		if p.oliPrefix(data) > 0 {
   173  			data = data[p.list(data, ListTypeOrdered):]
   174  			continue
   175  		}
   176  
   177  		// definition lists:
   178  		//
   179  		// Term 1
   180  		// :   Definition a
   181  		// :   Definition b
   182  		//
   183  		// Term 2
   184  		// :   Definition c
   185  		if p.extensions&DefinitionLists != 0 {
   186  			if p.dliPrefix(data) > 0 {
   187  				data = data[p.list(data, ListTypeDefinition):]
   188  				continue
   189  			}
   190  		}
   191  
   192  		// anything else must look like a normal paragraph
   193  		// note: this finds underlined headings, too
   194  		data = data[p.paragraph(data):]
   195  	}
   196  
   197  	p.nesting--
   198  }
   199  
   200  func (p *Markdown) addBlock(typ NodeType, content []byte) *Node {
   201  	p.closeUnmatchedBlocks()
   202  	container := p.addChild(typ, 0)
   203  	container.content = content
   204  	return container
   205  }
   206  
   207  func (p *Markdown) isPrefixHeading(data []byte) bool {
   208  	if data[0] != '#' {
   209  		return false
   210  	}
   211  
   212  	if p.extensions&SpaceHeadings != 0 {
   213  		level := 0
   214  		for level < 6 && level < len(data) && data[level] == '#' {
   215  			level++
   216  		}
   217  		if level == len(data) || data[level] != ' ' {
   218  			return false
   219  		}
   220  	}
   221  	return true
   222  }
   223  
   224  func (p *Markdown) prefixHeading(data []byte) int {
   225  	level := 0
   226  	for level < 6 && level < len(data) && data[level] == '#' {
   227  		level++
   228  	}
   229  	i := skipChar(data, level, ' ')
   230  	end := skipUntilChar(data, i, '\n')
   231  	skip := end
   232  	id := ""
   233  	if p.extensions&HeadingIDs != 0 {
   234  		j, k := 0, 0
   235  		// find start/end of heading id
   236  		for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
   237  		}
   238  		for k = j + 1; k < end && data[k] != '}'; k++ {
   239  		}
   240  		// extract heading id iff found
   241  		if j < end && k < end {
   242  			id = string(data[j+2 : k])
   243  			end = j
   244  			skip = k + 1
   245  			for end > 0 && data[end-1] == ' ' {
   246  				end--
   247  			}
   248  		}
   249  	}
   250  	for end > 0 && data[end-1] == '#' {
   251  		if isBackslashEscaped(data, end-1) {
   252  			break
   253  		}
   254  		end--
   255  	}
   256  	for end > 0 && data[end-1] == ' ' {
   257  		end--
   258  	}
   259  	if end > i {
   260  		if id == "" && p.extensions&AutoHeadingIDs != 0 {
   261  			id = SanitizedAnchorName(string(data[i:end]))
   262  		}
   263  		block := p.addBlock(Heading, data[i:end])
   264  		block.HeadingID = id
   265  		block.Level = level
   266  	}
   267  	return skip
   268  }
   269  
   270  func (p *Markdown) isUnderlinedHeading(data []byte) int {
   271  	// test of level 1 heading
   272  	if data[0] == '=' {
   273  		i := skipChar(data, 1, '=')
   274  		i = skipChar(data, i, ' ')
   275  		if i < len(data) && data[i] == '\n' {
   276  			return 1
   277  		}
   278  		return 0
   279  	}
   280  
   281  	// test of level 2 heading
   282  	if data[0] == '-' {
   283  		i := skipChar(data, 1, '-')
   284  		i = skipChar(data, i, ' ')
   285  		if i < len(data) && data[i] == '\n' {
   286  			return 2
   287  		}
   288  		return 0
   289  	}
   290  
   291  	return 0
   292  }
   293  
   294  func (p *Markdown) titleBlock(data []byte, doRender bool) int {
   295  	if data[0] != '%' {
   296  		return 0
   297  	}
   298  	splitData := bytes.Split(data, []byte("\n"))
   299  	var i int
   300  	for idx, b := range splitData {
   301  		if !bytes.HasPrefix(b, []byte("%")) {
   302  			i = idx // - 1
   303  			break
   304  		}
   305  	}
   306  
   307  	data = bytes.Join(splitData[0:i], []byte("\n"))
   308  	consumed := len(data)
   309  	data = bytes.TrimPrefix(data, []byte("% "))
   310  	data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
   311  	block := p.addBlock(Heading, data)
   312  	block.Level = 1
   313  	block.IsTitleblock = true
   314  
   315  	return consumed
   316  }
   317  
   318  func (p *Markdown) html(data []byte, doRender bool) int {
   319  	var i, j int
   320  
   321  	// identify the opening tag
   322  	if data[0] != '<' {
   323  		return 0
   324  	}
   325  	curtag, tagfound := p.htmlFindTag(data[1:])
   326  
   327  	// handle special cases
   328  	if !tagfound {
   329  		// check for an HTML comment
   330  		if size := p.htmlComment(data, doRender); size > 0 {
   331  			return size
   332  		}
   333  
   334  		// check for an <hr> tag
   335  		if size := p.htmlHr(data, doRender); size > 0 {
   336  			return size
   337  		}
   338  
   339  		// no special case recognized
   340  		return 0
   341  	}
   342  
   343  	// look for an unindented matching closing tag
   344  	// followed by a blank line
   345  	found := false
   346  	/*
   347  		closetag := []byte("\n</" + curtag + ">")
   348  		j = len(curtag) + 1
   349  		for !found {
   350  			// scan for a closing tag at the beginning of a line
   351  			if skip := bytes.Index(data[j:], closetag); skip >= 0 {
   352  				j += skip + len(closetag)
   353  			} else {
   354  				break
   355  			}
   356  
   357  			// see if it is the only thing on the line
   358  			if skip := p.isEmpty(data[j:]); skip > 0 {
   359  				// see if it is followed by a blank line/eof
   360  				j += skip
   361  				if j >= len(data) {
   362  					found = true
   363  					i = j
   364  				} else {
   365  					if skip := p.isEmpty(data[j:]); skip > 0 {
   366  						j += skip
   367  						found = true
   368  						i = j
   369  					}
   370  				}
   371  			}
   372  		}
   373  	*/
   374  
   375  	// if not found, try a second pass looking for indented match
   376  	// but not if tag is "ins" or "del" (following original Markdown.pl)
   377  	if !found && curtag != "ins" && curtag != "del" {
   378  		i = 1
   379  		for i < len(data) {
   380  			i++
   381  			for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
   382  				i++
   383  			}
   384  
   385  			if i+2+len(curtag) >= len(data) {
   386  				break
   387  			}
   388  
   389  			j = p.htmlFindEnd(curtag, data[i-1:])
   390  
   391  			if j > 0 {
   392  				i += j - 1
   393  				found = true
   394  				break
   395  			}
   396  		}
   397  	}
   398  
   399  	if !found {
   400  		return 0
   401  	}
   402  
   403  	// the end of the block has been found
   404  	if doRender {
   405  		// trim newlines
   406  		end := i
   407  		for end > 0 && data[end-1] == '\n' {
   408  			end--
   409  		}
   410  		finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
   411  	}
   412  
   413  	return i
   414  }
   415  
   416  func finalizeHTMLBlock(block *Node) {
   417  	block.Literal = block.content
   418  	block.content = nil
   419  }
   420  
   421  // HTML comment, lax form
   422  func (p *Markdown) htmlComment(data []byte, doRender bool) int {
   423  	i := p.inlineHTMLComment(data)
   424  	// needs to end with a blank line
   425  	if j := p.isEmpty(data[i:]); j > 0 {
   426  		size := i + j
   427  		if doRender {
   428  			// trim trailing newlines
   429  			end := size
   430  			for end > 0 && data[end-1] == '\n' {
   431  				end--
   432  			}
   433  			block := p.addBlock(HTMLBlock, data[:end])
   434  			finalizeHTMLBlock(block)
   435  		}
   436  		return size
   437  	}
   438  	return 0
   439  }
   440  
   441  // HR, which is the only self-closing block tag considered
   442  func (p *Markdown) htmlHr(data []byte, doRender bool) int {
   443  	if len(data) < 4 {
   444  		return 0
   445  	}
   446  	if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
   447  		return 0
   448  	}
   449  	if data[3] != ' ' && data[3] != '/' && data[3] != '>' {
   450  		// not an <hr> tag after all; at least not a valid one
   451  		return 0
   452  	}
   453  	i := 3
   454  	for i < len(data) && data[i] != '>' && data[i] != '\n' {
   455  		i++
   456  	}
   457  	if i < len(data) && data[i] == '>' {
   458  		i++
   459  		if j := p.isEmpty(data[i:]); j > 0 {
   460  			size := i + j
   461  			if doRender {
   462  				// trim newlines
   463  				end := size
   464  				for end > 0 && data[end-1] == '\n' {
   465  					end--
   466  				}
   467  				finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
   468  			}
   469  			return size
   470  		}
   471  	}
   472  	return 0
   473  }
   474  
   475  func (p *Markdown) htmlFindTag(data []byte) (string, bool) {
   476  	i := 0
   477  	for i < len(data) && isalnum(data[i]) {
   478  		i++
   479  	}
   480  	key := string(data[:i])
   481  	if _, ok := blockTags[key]; ok {
   482  		return key, true
   483  	}
   484  	return "", false
   485  }
   486  
   487  func (p *Markdown) htmlFindEnd(tag string, data []byte) int {
   488  	// assume data[0] == '<' && data[1] == '/' already tested
   489  	if tag == "hr" {
   490  		return 2
   491  	}
   492  	// check if tag is a match
   493  	closetag := []byte("</" + tag + ">")
   494  	if !bytes.HasPrefix(data, closetag) {
   495  		return 0
   496  	}
   497  	i := len(closetag)
   498  
   499  	// check that the rest of the line is blank
   500  	skip := 0
   501  	if skip = p.isEmpty(data[i:]); skip == 0 {
   502  		return 0
   503  	}
   504  	i += skip
   505  	skip = 0
   506  
   507  	if i >= len(data) {
   508  		return i
   509  	}
   510  
   511  	if p.extensions&LaxHTMLBlocks != 0 {
   512  		return i
   513  	}
   514  	if skip = p.isEmpty(data[i:]); skip == 0 {
   515  		// following line must be blank
   516  		return 0
   517  	}
   518  
   519  	return i + skip
   520  }
   521  
   522  func (*Markdown) isEmpty(data []byte) int {
   523  	// it is okay to call isEmpty on an empty buffer
   524  	if len(data) == 0 {
   525  		return 0
   526  	}
   527  
   528  	var i int
   529  	for i = 0; i < len(data) && data[i] != '\n'; i++ {
   530  		if data[i] != ' ' && data[i] != '\t' {
   531  			return 0
   532  		}
   533  	}
   534  	if i < len(data) && data[i] == '\n' {
   535  		i++
   536  	}
   537  	return i
   538  }
   539  
   540  func (*Markdown) isHRule(data []byte) bool {
   541  	i := 0
   542  
   543  	// skip up to three spaces
   544  	for i < 3 && data[i] == ' ' {
   545  		i++
   546  	}
   547  
   548  	// look at the hrule char
   549  	if data[i] != '*' && data[i] != '-' && data[i] != '_' {
   550  		return false
   551  	}
   552  	c := data[i]
   553  
   554  	// the whole line must be the char or whitespace
   555  	n := 0
   556  	for i < len(data) && data[i] != '\n' {
   557  		switch {
   558  		case data[i] == c:
   559  			n++
   560  		case data[i] != ' ':
   561  			return false
   562  		}
   563  		i++
   564  	}
   565  
   566  	return n >= 3
   567  }
   568  
   569  // isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
   570  // and returns the end index if so, or 0 otherwise. It also returns the marker found.
   571  // If info is not nil, it gets set to the syntax specified in the fence line.
   572  func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
   573  	i, size := 0, 0
   574  
   575  	// skip up to three spaces
   576  	for i < len(data) && i < 3 && data[i] == ' ' {
   577  		i++
   578  	}
   579  
   580  	// check for the marker characters: ~ or `
   581  	if i >= len(data) {
   582  		return 0, ""
   583  	}
   584  	if data[i] != '~' && data[i] != '`' {
   585  		return 0, ""
   586  	}
   587  
   588  	c := data[i]
   589  
   590  	// the whole line must be the same char or whitespace
   591  	for i < len(data) && data[i] == c {
   592  		size++
   593  		i++
   594  	}
   595  
   596  	// the marker char must occur at least 3 times
   597  	if size < 3 {
   598  		return 0, ""
   599  	}
   600  	marker = string(data[i-size : i])
   601  
   602  	// if this is the end marker, it must match the beginning marker
   603  	if oldmarker != "" && marker != oldmarker {
   604  		return 0, ""
   605  	}
   606  
   607  	// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
   608  	// into one, always get the info string, and discard it if the caller doesn't care.
   609  	if info != nil {
   610  		infoLength := 0
   611  		i = skipChar(data, i, ' ')
   612  
   613  		if i >= len(data) {
   614  			if i == len(data) {
   615  				return i, marker
   616  			}
   617  			return 0, ""
   618  		}
   619  
   620  		infoStart := i
   621  
   622  		if data[i] == '{' {
   623  			i++
   624  			infoStart++
   625  
   626  			for i < len(data) && data[i] != '}' && data[i] != '\n' {
   627  				infoLength++
   628  				i++
   629  			}
   630  
   631  			if i >= len(data) || data[i] != '}' {
   632  				return 0, ""
   633  			}
   634  
   635  			// strip all whitespace at the beginning and the end
   636  			// of the {} block
   637  			for infoLength > 0 && isspace(data[infoStart]) {
   638  				infoStart++
   639  				infoLength--
   640  			}
   641  
   642  			for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
   643  				infoLength--
   644  			}
   645  			i++
   646  			i = skipChar(data, i, ' ')
   647  		} else {
   648  			for i < len(data) && !isverticalspace(data[i]) {
   649  				infoLength++
   650  				i++
   651  			}
   652  		}
   653  
   654  		*info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
   655  	}
   656  
   657  	if i == len(data) {
   658  		return i, marker
   659  	}
   660  	if i > len(data) || data[i] != '\n' {
   661  		return 0, ""
   662  	}
   663  	return i + 1, marker // Take newline into account.
   664  }
   665  
   666  // fencedCodeBlock returns the end index if data contains a fenced code block at the beginning,
   667  // or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
   668  // If doRender is true, a final newline is mandatory to recognize the fenced code block.
   669  func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
   670  	var info string
   671  	beg, marker := isFenceLine(data, &info, "")
   672  	if beg == 0 || beg >= len(data) {
   673  		return 0
   674  	}
   675  	fenceLength := beg - 1
   676  
   677  	var work bytes.Buffer
   678  	work.Write([]byte(info))
   679  	work.WriteByte('\n')
   680  
   681  	for {
   682  		// safe to assume beg < len(data)
   683  
   684  		// check for the end of the code block
   685  		fenceEnd, _ := isFenceLine(data[beg:], nil, marker)
   686  		if fenceEnd != 0 {
   687  			beg += fenceEnd
   688  			break
   689  		}
   690  
   691  		// copy the current line
   692  		end := skipUntilChar(data, beg, '\n') + 1
   693  
   694  		// did we reach the end of the buffer without a closing marker?
   695  		if end >= len(data) {
   696  			return 0
   697  		}
   698  
   699  		// verbatim copy to the working buffer
   700  		if doRender {
   701  			work.Write(data[beg:end])
   702  		}
   703  		beg = end
   704  	}
   705  
   706  	if doRender {
   707  		block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
   708  		block.IsFenced = true
   709  		block.FenceLength = fenceLength
   710  		finalizeCodeBlock(block)
   711  	}
   712  
   713  	return beg
   714  }
   715  
   716  func unescapeChar(str []byte) []byte {
   717  	if str[0] == '\\' {
   718  		return []byte{str[1]}
   719  	}
   720  	return []byte(html.UnescapeString(string(str)))
   721  }
   722  
   723  func unescapeString(str []byte) []byte {
   724  	if reBackslashOrAmp.Match(str) {
   725  		return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar)
   726  	}
   727  	return str
   728  }
   729  
   730  func finalizeCodeBlock(block *Node) {
   731  	if block.IsFenced {
   732  		newlinePos := bytes.IndexByte(block.content, '\n')
   733  		firstLine := block.content[:newlinePos]
   734  		rest := block.content[newlinePos+1:]
   735  		block.Info = unescapeString(bytes.Trim(firstLine, "\n"))
   736  		block.Literal = rest
   737  	} else {
   738  		block.Literal = block.content
   739  	}
   740  	block.content = nil
   741  }
   742  
   743  func (p *Markdown) table(data []byte) int {
   744  	table := p.addBlock(Table, nil)
   745  	i, columns := p.tableHeader(data)
   746  	if i == 0 {
   747  		p.tip = table.Parent
   748  		table.Unlink()
   749  		return 0
   750  	}
   751  
   752  	p.addBlock(TableBody, nil)
   753  
   754  	for i < len(data) {
   755  		pipes, rowStart := 0, i
   756  		for ; i < len(data) && data[i] != '\n'; i++ {
   757  			if data[i] == '|' {
   758  				pipes++
   759  			}
   760  		}
   761  
   762  		if pipes == 0 {
   763  			i = rowStart
   764  			break
   765  		}
   766  
   767  		// include the newline in data sent to tableRow
   768  		if i < len(data) && data[i] == '\n' {
   769  			i++
   770  		}
   771  		p.tableRow(data[rowStart:i], columns, false)
   772  	}
   773  
   774  	return i
   775  }
   776  
   777  // check if the specified position is preceded by an odd number of backslashes
   778  func isBackslashEscaped(data []byte, i int) bool {
   779  	backslashes := 0
   780  	for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
   781  		backslashes++
   782  	}
   783  	return backslashes&1 == 1
   784  }
   785  
   786  func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) {
   787  	i := 0
   788  	colCount := 1
   789  	for i = 0; i < len(data) && data[i] != '\n'; i++ {
   790  		if data[i] == '|' && !isBackslashEscaped(data, i) {
   791  			colCount++
   792  		}
   793  	}
   794  
   795  	// doesn't look like a table header
   796  	if colCount == 1 {
   797  		return
   798  	}
   799  
   800  	// include the newline in the data sent to tableRow
   801  	j := i
   802  	if j < len(data) && data[j] == '\n' {
   803  		j++
   804  	}
   805  	header := data[:j]
   806  
   807  	// column count ignores pipes at beginning or end of line
   808  	if data[0] == '|' {
   809  		colCount--
   810  	}
   811  	if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) {
   812  		colCount--
   813  	}
   814  
   815  	columns = make([]CellAlignFlags, colCount)
   816  
   817  	// move on to the header underline
   818  	i++
   819  	if i >= len(data) {
   820  		return
   821  	}
   822  
   823  	if data[i] == '|' && !isBackslashEscaped(data, i) {
   824  		i++
   825  	}
   826  	i = skipChar(data, i, ' ')
   827  
   828  	// each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
   829  	// and trailing | optional on last column
   830  	col := 0
   831  	for i < len(data) && data[i] != '\n' {
   832  		dashes := 0
   833  
   834  		if data[i] == ':' {
   835  			i++
   836  			columns[col] |= TableAlignmentLeft
   837  			dashes++
   838  		}
   839  		for i < len(data) && data[i] == '-' {
   840  			i++
   841  			dashes++
   842  		}
   843  		if i < len(data) && data[i] == ':' {
   844  			i++
   845  			columns[col] |= TableAlignmentRight
   846  			dashes++
   847  		}
   848  		for i < len(data) && data[i] == ' ' {
   849  			i++
   850  		}
   851  		if i == len(data) {
   852  			return
   853  		}
   854  		// end of column test is messy
   855  		switch {
   856  		case dashes < 3:
   857  			// not a valid column
   858  			return
   859  
   860  		case data[i] == '|' && !isBackslashEscaped(data, i):
   861  			// marker found, now skip past trailing whitespace
   862  			col++
   863  			i++
   864  			for i < len(data) && data[i] == ' ' {
   865  				i++
   866  			}
   867  
   868  			// trailing junk found after last column
   869  			if col >= colCount && i < len(data) && data[i] != '\n' {
   870  				return
   871  			}
   872  
   873  		case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
   874  			// something else found where marker was required
   875  			return
   876  
   877  		case data[i] == '\n':
   878  			// marker is optional for the last column
   879  			col++
   880  
   881  		default:
   882  			// trailing junk found after last column
   883  			return
   884  		}
   885  	}
   886  	if col != colCount {
   887  		return
   888  	}
   889  
   890  	p.addBlock(TableHead, nil)
   891  	p.tableRow(header, columns, true)
   892  	size = i
   893  	if size < len(data) && data[size] == '\n' {
   894  		size++
   895  	}
   896  	return
   897  }
   898  
   899  func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) {
   900  	p.addBlock(TableRow, nil)
   901  	i, col := 0, 0
   902  
   903  	if data[i] == '|' && !isBackslashEscaped(data, i) {
   904  		i++
   905  	}
   906  
   907  	for col = 0; col < len(columns) && i < len(data); col++ {
   908  		for i < len(data) && data[i] == ' ' {
   909  			i++
   910  		}
   911  
   912  		cellStart := i
   913  
   914  		for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
   915  			i++
   916  		}
   917  
   918  		cellEnd := i
   919  
   920  		// skip the end-of-cell marker, possibly taking us past end of buffer
   921  		i++
   922  
   923  		for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' {
   924  			cellEnd--
   925  		}
   926  
   927  		cell := p.addBlock(TableCell, data[cellStart:cellEnd])
   928  		cell.IsHeader = header
   929  		cell.Align = columns[col]
   930  	}
   931  
   932  	// pad it out with empty columns to get the right number
   933  	for ; col < len(columns); col++ {
   934  		cell := p.addBlock(TableCell, nil)
   935  		cell.IsHeader = header
   936  		cell.Align = columns[col]
   937  	}
   938  
   939  	// silently ignore rows with too many cells
   940  }
   941  
   942  // returns blockquote prefix length
   943  func (p *Markdown) quotePrefix(data []byte) int {
   944  	i := 0
   945  	for i < 3 && i < len(data) && data[i] == ' ' {
   946  		i++
   947  	}
   948  	if i < len(data) && data[i] == '>' {
   949  		if i+1 < len(data) && data[i+1] == ' ' {
   950  			return i + 2
   951  		}
   952  		return i + 1
   953  	}
   954  	return 0
   955  }
   956  
   957  // blockquote ends with at least one blank line
   958  // followed by something without a blockquote prefix
   959  func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool {
   960  	if p.isEmpty(data[beg:]) <= 0 {
   961  		return false
   962  	}
   963  	if end >= len(data) {
   964  		return true
   965  	}
   966  	return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0
   967  }
   968  
   969  // parse a blockquote fragment
   970  func (p *Markdown) quote(data []byte) int {
   971  	block := p.addBlock(BlockQuote, nil)
   972  	var raw bytes.Buffer
   973  	beg, end := 0, 0
   974  	for beg < len(data) {
   975  		end = beg
   976  		// Step over whole lines, collecting them. While doing that, check for
   977  		// fenced code and if one's found, incorporate it altogether,
   978  		// irregardless of any contents inside it
   979  		for end < len(data) && data[end] != '\n' {
   980  			if p.extensions&FencedCode != 0 {
   981  				if i := p.fencedCodeBlock(data[end:], false); i > 0 {
   982  					// -1 to compensate for the extra end++ after the loop:
   983  					end += i - 1
   984  					break
   985  				}
   986  			}
   987  			end++
   988  		}
   989  		if end < len(data) && data[end] == '\n' {
   990  			end++
   991  		}
   992  		if pre := p.quotePrefix(data[beg:]); pre > 0 {
   993  			// skip the prefix
   994  			beg += pre
   995  		} else if p.terminateBlockquote(data, beg, end) {
   996  			break
   997  		}
   998  		// this line is part of the blockquote
   999  		raw.Write(data[beg:end])
  1000  		beg = end
  1001  	}
  1002  	p.block(raw.Bytes())
  1003  	p.finalize(block)
  1004  	return end
  1005  }
  1006  
  1007  // returns prefix length for block code
  1008  func (p *Markdown) codePrefix(data []byte) int {
  1009  	if len(data) >= 1 && data[0] == '\t' {
  1010  		return 1
  1011  	}
  1012  	if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
  1013  		return 4
  1014  	}
  1015  	return 0
  1016  }
  1017  
  1018  func (p *Markdown) code(data []byte) int {
  1019  	var work bytes.Buffer
  1020  
  1021  	i := 0
  1022  	for i < len(data) {
  1023  		beg := i
  1024  		for i < len(data) && data[i] != '\n' {
  1025  			i++
  1026  		}
  1027  		if i < len(data) && data[i] == '\n' {
  1028  			i++
  1029  		}
  1030  
  1031  		blankline := p.isEmpty(data[beg:i]) > 0
  1032  		if pre := p.codePrefix(data[beg:i]); pre > 0 {
  1033  			beg += pre
  1034  		} else if !blankline {
  1035  			// non-empty, non-prefixed line breaks the pre
  1036  			i = beg
  1037  			break
  1038  		}
  1039  
  1040  		// verbatim copy to the working buffer
  1041  		if blankline {
  1042  			work.WriteByte('\n')
  1043  		} else {
  1044  			work.Write(data[beg:i])
  1045  		}
  1046  	}
  1047  
  1048  	// trim all the \n off the end of work
  1049  	workbytes := work.Bytes()
  1050  	eol := len(workbytes)
  1051  	for eol > 0 && workbytes[eol-1] == '\n' {
  1052  		eol--
  1053  	}
  1054  	if eol != len(workbytes) {
  1055  		work.Truncate(eol)
  1056  	}
  1057  
  1058  	work.WriteByte('\n')
  1059  
  1060  	block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
  1061  	block.IsFenced = false
  1062  	finalizeCodeBlock(block)
  1063  
  1064  	return i
  1065  }
  1066  
  1067  // returns unordered list item prefix
  1068  func (p *Markdown) uliPrefix(data []byte) int {
  1069  	i := 0
  1070  	// start with up to 3 spaces
  1071  	for i < len(data) && i < 3 && data[i] == ' ' {
  1072  		i++
  1073  	}
  1074  	if i >= len(data)-1 {
  1075  		return 0
  1076  	}
  1077  	// need one of {'*', '+', '-'} followed by a space or a tab
  1078  	if (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
  1079  		(data[i+1] != ' ' && data[i+1] != '\t') {
  1080  		return 0
  1081  	}
  1082  	return i + 2
  1083  }
  1084  
  1085  // returns ordered list item prefix
  1086  func (p *Markdown) oliPrefix(data []byte) int {
  1087  	i := 0
  1088  
  1089  	// start with up to 3 spaces
  1090  	for i < 3 && i < len(data) && data[i] == ' ' {
  1091  		i++
  1092  	}
  1093  
  1094  	// count the digits
  1095  	start := i
  1096  	for i < len(data) && data[i] >= '0' && data[i] <= '9' {
  1097  		i++
  1098  	}
  1099  	if start == i || i >= len(data)-1 {
  1100  		return 0
  1101  	}
  1102  
  1103  	// we need >= 1 digits followed by a dot and a space or a tab
  1104  	if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') {
  1105  		return 0
  1106  	}
  1107  	return i + 2
  1108  }
  1109  
  1110  // returns definition list item prefix
  1111  func (p *Markdown) dliPrefix(data []byte) int {
  1112  	if len(data) < 2 {
  1113  		return 0
  1114  	}
  1115  	i := 0
  1116  	// need a ':' followed by a space or a tab
  1117  	if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') {
  1118  		return 0
  1119  	}
  1120  	for i < len(data) && data[i] == ' ' {
  1121  		i++
  1122  	}
  1123  	return i + 2
  1124  }
  1125  
  1126  // parse ordered or unordered list block
  1127  func (p *Markdown) list(data []byte, flags ListType) int {
  1128  	i := 0
  1129  	flags |= ListItemBeginningOfList
  1130  	block := p.addBlock(List, nil)
  1131  	block.ListFlags = flags
  1132  	block.Tight = true
  1133  
  1134  	for i < len(data) {
  1135  		skip := p.listItem(data[i:], &flags)
  1136  		if flags&ListItemContainsBlock != 0 {
  1137  			block.ListData.Tight = false
  1138  		}
  1139  		i += skip
  1140  		if skip == 0 || flags&ListItemEndOfList != 0 {
  1141  			break
  1142  		}
  1143  		flags &= ^ListItemBeginningOfList
  1144  	}
  1145  
  1146  	above := block.Parent
  1147  	finalizeList(block)
  1148  	p.tip = above
  1149  	return i
  1150  }
  1151  
  1152  // Returns true if the list item is not the same type as its parent list
  1153  func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool {
  1154  	if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 {
  1155  		return true
  1156  	} else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 {
  1157  		return true
  1158  	} else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) {
  1159  		return true
  1160  	}
  1161  	return false
  1162  }
  1163  
  1164  // Returns true if block ends with a blank line, descending if needed
  1165  // into lists and sublists.
  1166  func endsWithBlankLine(block *Node) bool {
  1167  	// TODO: figure this out. Always false now.
  1168  	for block != nil {
  1169  		//if block.lastLineBlank {
  1170  		//return true
  1171  		//}
  1172  		t := block.Type
  1173  		if t == List || t == Item {
  1174  			block = block.LastChild
  1175  		} else {
  1176  			break
  1177  		}
  1178  	}
  1179  	return false
  1180  }
  1181  
  1182  func finalizeList(block *Node) {
  1183  	block.open = false
  1184  	item := block.FirstChild
  1185  	for item != nil {
  1186  		// check for non-final list item ending with blank line:
  1187  		if endsWithBlankLine(item) && item.Next != nil {
  1188  			block.ListData.Tight = false
  1189  			break
  1190  		}
  1191  		// recurse into children of list item, to see if there are spaces
  1192  		// between any of them:
  1193  		subItem := item.FirstChild
  1194  		for subItem != nil {
  1195  			if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) {
  1196  				block.ListData.Tight = false
  1197  				break
  1198  			}
  1199  			subItem = subItem.Next
  1200  		}
  1201  		item = item.Next
  1202  	}
  1203  }
  1204  
  1205  // Parse a single list item.
  1206  // Assumes initial prefix is already removed if this is a sublist.
  1207  func (p *Markdown) listItem(data []byte, flags *ListType) int {
  1208  	// keep track of the indentation of the first line
  1209  	itemIndent := 0
  1210  	if data[0] == '\t' {
  1211  		itemIndent += 4
  1212  	} else {
  1213  		for itemIndent < 3 && data[itemIndent] == ' ' {
  1214  			itemIndent++
  1215  		}
  1216  	}
  1217  
  1218  	var bulletChar byte = '*'
  1219  	i := p.uliPrefix(data)
  1220  	if i == 0 {
  1221  		i = p.oliPrefix(data)
  1222  	} else {
  1223  		bulletChar = data[i-2]
  1224  	}
  1225  	if i == 0 {
  1226  		i = p.dliPrefix(data)
  1227  		// reset definition term flag
  1228  		if i > 0 {
  1229  			*flags &= ^ListTypeTerm
  1230  		}
  1231  	}
  1232  	if i == 0 {
  1233  		// if in definition list, set term flag and continue
  1234  		if *flags&ListTypeDefinition != 0 {
  1235  			*flags |= ListTypeTerm
  1236  		} else {
  1237  			return 0
  1238  		}
  1239  	}
  1240  
  1241  	// skip leading whitespace on first line
  1242  	for i < len(data) && data[i] == ' ' {
  1243  		i++
  1244  	}
  1245  
  1246  	// find the end of the line
  1247  	line := i
  1248  	for i > 0 && i < len(data) && data[i-1] != '\n' {
  1249  		i++
  1250  	}
  1251  
  1252  	// get working buffer
  1253  	var raw bytes.Buffer
  1254  
  1255  	// put the first line into the working buffer
  1256  	raw.Write(data[line:i])
  1257  	line = i
  1258  
  1259  	// process the following lines
  1260  	containsBlankLine := false
  1261  	sublist := 0
  1262  	codeBlockMarker := ""
  1263  
  1264  gatherlines:
  1265  	for line < len(data) {
  1266  		i++
  1267  
  1268  		// find the end of this line
  1269  		for i < len(data) && data[i-1] != '\n' {
  1270  			i++
  1271  		}
  1272  
  1273  		// if it is an empty line, guess that it is part of this item
  1274  		// and move on to the next line
  1275  		if p.isEmpty(data[line:i]) > 0 {
  1276  			containsBlankLine = true
  1277  			line = i
  1278  			continue
  1279  		}
  1280  
  1281  		// calculate the indentation
  1282  		indent := 0
  1283  		indentIndex := 0
  1284  		if data[line] == '\t' {
  1285  			indentIndex++
  1286  			indent += 4
  1287  		} else {
  1288  			for indent < 4 && line+indent < i && data[line+indent] == ' ' {
  1289  				indent++
  1290  				indentIndex++
  1291  			}
  1292  		}
  1293  
  1294  		chunk := data[line+indentIndex : i]
  1295  
  1296  		if p.extensions&FencedCode != 0 {
  1297  			// determine if in or out of codeblock
  1298  			// if in codeblock, ignore normal list processing
  1299  			_, marker := isFenceLine(chunk, nil, codeBlockMarker)
  1300  			if marker != "" {
  1301  				if codeBlockMarker == "" {
  1302  					// start of codeblock
  1303  					codeBlockMarker = marker
  1304  				} else {
  1305  					// end of codeblock.
  1306  					codeBlockMarker = ""
  1307  				}
  1308  			}
  1309  			// we are in a codeblock, write line, and continue
  1310  			if codeBlockMarker != "" || marker != "" {
  1311  				raw.Write(data[line+indentIndex : i])
  1312  				line = i
  1313  				continue gatherlines
  1314  			}
  1315  		}
  1316  
  1317  		// evaluate how this line fits in
  1318  		switch {
  1319  		// is this a nested list item?
  1320  		case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) ||
  1321  			p.oliPrefix(chunk) > 0 ||
  1322  			p.dliPrefix(chunk) > 0:
  1323  
  1324  			// to be a nested list, it must be indented more
  1325  			// if not, it is either a different kind of list
  1326  			// or the next item in the same list
  1327  			if indent <= itemIndent {
  1328  				if p.listTypeChanged(chunk, flags) {
  1329  					*flags |= ListItemEndOfList
  1330  				} else if containsBlankLine {
  1331  					*flags |= ListItemContainsBlock
  1332  				}
  1333  
  1334  				break gatherlines
  1335  			}
  1336  
  1337  			if containsBlankLine {
  1338  				*flags |= ListItemContainsBlock
  1339  			}
  1340  
  1341  			// is this the first item in the nested list?
  1342  			if sublist == 0 {
  1343  				sublist = raw.Len()
  1344  			}
  1345  
  1346  		// is this a nested prefix heading?
  1347  		case p.isPrefixHeading(chunk):
  1348  			// if the heading is not indented, it is not nested in the list
  1349  			// and thus ends the list
  1350  			if containsBlankLine && indent < 4 {
  1351  				*flags |= ListItemEndOfList
  1352  				break gatherlines
  1353  			}
  1354  			*flags |= ListItemContainsBlock
  1355  
  1356  		// anything following an empty line is only part
  1357  		// of this item if it is indented 4 spaces
  1358  		// (regardless of the indentation of the beginning of the item)
  1359  		case containsBlankLine && indent < 4:
  1360  			if *flags&ListTypeDefinition != 0 && i < len(data)-1 {
  1361  				// is the next item still a part of this list?
  1362  				next := i
  1363  				for next < len(data) && data[next] != '\n' {
  1364  					next++
  1365  				}
  1366  				for next < len(data)-1 && data[next] == '\n' {
  1367  					next++
  1368  				}
  1369  				if i < len(data)-1 && data[i] != ':' && data[next] != ':' {
  1370  					*flags |= ListItemEndOfList
  1371  				}
  1372  			} else {
  1373  				*flags |= ListItemEndOfList
  1374  			}
  1375  			break gatherlines
  1376  
  1377  		// a blank line means this should be parsed as a block
  1378  		case containsBlankLine:
  1379  			raw.WriteByte('\n')
  1380  			*flags |= ListItemContainsBlock
  1381  		}
  1382  
  1383  		// if this line was preceded by one or more blanks,
  1384  		// re-introduce the blank into the buffer
  1385  		if containsBlankLine {
  1386  			containsBlankLine = false
  1387  			raw.WriteByte('\n')
  1388  		}
  1389  
  1390  		// add the line into the working buffer without prefix
  1391  		raw.Write(data[line+indentIndex : i])
  1392  
  1393  		line = i
  1394  	}
  1395  
  1396  	rawBytes := raw.Bytes()
  1397  
  1398  	block := p.addBlock(Item, nil)
  1399  	block.ListFlags = *flags
  1400  	block.Tight = false
  1401  	block.BulletChar = bulletChar
  1402  	block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark
  1403  
  1404  	// render the contents of the list item
  1405  	if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 {
  1406  		// intermediate render of block item, except for definition term
  1407  		if sublist > 0 {
  1408  			p.block(rawBytes[:sublist])
  1409  			p.block(rawBytes[sublist:])
  1410  		} else {
  1411  			p.block(rawBytes)
  1412  		}
  1413  	} else {
  1414  		// intermediate render of inline item
  1415  		if sublist > 0 {
  1416  			child := p.addChild(Paragraph, 0)
  1417  			child.content = rawBytes[:sublist]
  1418  			p.block(rawBytes[sublist:])
  1419  		} else {
  1420  			child := p.addChild(Paragraph, 0)
  1421  			child.content = rawBytes
  1422  		}
  1423  	}
  1424  	return line
  1425  }
  1426  
  1427  // render a single paragraph that has already been parsed out
  1428  func (p *Markdown) renderParagraph(data []byte) {
  1429  	if len(data) == 0 {
  1430  		return
  1431  	}
  1432  
  1433  	// trim leading spaces
  1434  	beg := 0
  1435  	for data[beg] == ' ' {
  1436  		beg++
  1437  	}
  1438  
  1439  	end := len(data)
  1440  	// trim trailing newline
  1441  	if data[len(data)-1] == '\n' {
  1442  		end--
  1443  	}
  1444  
  1445  	// trim trailing spaces
  1446  	for end > beg && data[end-1] == ' ' {
  1447  		end--
  1448  	}
  1449  
  1450  	p.addBlock(Paragraph, data[beg:end])
  1451  }
  1452  
  1453  func (p *Markdown) paragraph(data []byte) int {
  1454  	// prev: index of 1st char of previous line
  1455  	// line: index of 1st char of current line
  1456  	// i: index of cursor/end of current line
  1457  	var prev, line, i int
  1458  	tabSize := TabSizeDefault
  1459  	if p.extensions&TabSizeEight != 0 {
  1460  		tabSize = TabSizeDouble
  1461  	}
  1462  	// keep going until we find something to mark the end of the paragraph
  1463  	for i < len(data) {
  1464  		// mark the beginning of the current line
  1465  		prev = line
  1466  		current := data[i:]
  1467  		line = i
  1468  
  1469  		// did we find a reference or a footnote? If so, end a paragraph
  1470  		// preceding it and report that we have consumed up to the end of that
  1471  		// reference:
  1472  		if refEnd := isReference(p, current, tabSize); refEnd > 0 {
  1473  			p.renderParagraph(data[:i])
  1474  			return i + refEnd
  1475  		}
  1476  
  1477  		// did we find a blank line marking the end of the paragraph?
  1478  		if n := p.isEmpty(current); n > 0 {
  1479  			// did this blank line followed by a definition list item?
  1480  			if p.extensions&DefinitionLists != 0 {
  1481  				if i < len(data)-1 && data[i+1] == ':' {
  1482  					return p.list(data[prev:], ListTypeDefinition)
  1483  				}
  1484  			}
  1485  
  1486  			p.renderParagraph(data[:i])
  1487  			return i + n
  1488  		}
  1489  
  1490  		// an underline under some text marks a heading, so our paragraph ended on prev line
  1491  		if i > 0 {
  1492  			if level := p.isUnderlinedHeading(current); level > 0 {
  1493  				// render the paragraph
  1494  				p.renderParagraph(data[:prev])
  1495  
  1496  				// ignore leading and trailing whitespace
  1497  				eol := i - 1
  1498  				for prev < eol && data[prev] == ' ' {
  1499  					prev++
  1500  				}
  1501  				for eol > prev && data[eol-1] == ' ' {
  1502  					eol--
  1503  				}
  1504  
  1505  				id := ""
  1506  				if p.extensions&AutoHeadingIDs != 0 {
  1507  					id = SanitizedAnchorName(string(data[prev:eol]))
  1508  				}
  1509  
  1510  				block := p.addBlock(Heading, data[prev:eol])
  1511  				block.Level = level
  1512  				block.HeadingID = id
  1513  
  1514  				// find the end of the underline
  1515  				for i < len(data) && data[i] != '\n' {
  1516  					i++
  1517  				}
  1518  				return i
  1519  			}
  1520  		}
  1521  
  1522  		// if the next line starts a block of HTML, then the paragraph ends here
  1523  		if p.extensions&LaxHTMLBlocks != 0 {
  1524  			if data[i] == '<' && p.html(current, false) > 0 {
  1525  				// rewind to before the HTML block
  1526  				p.renderParagraph(data[:i])
  1527  				return i
  1528  			}
  1529  		}
  1530  
  1531  		// if there's a prefixed heading or a horizontal rule after this, paragraph is over
  1532  		if p.isPrefixHeading(current) || p.isHRule(current) {
  1533  			p.renderParagraph(data[:i])
  1534  			return i
  1535  		}
  1536  
  1537  		// if there's a fenced code block, paragraph is over
  1538  		if p.extensions&FencedCode != 0 {
  1539  			if p.fencedCodeBlock(current, false) > 0 {
  1540  				p.renderParagraph(data[:i])
  1541  				return i
  1542  			}
  1543  		}
  1544  
  1545  		// if there's a definition list item, prev line is a definition term
  1546  		if p.extensions&DefinitionLists != 0 {
  1547  			if p.dliPrefix(current) != 0 {
  1548  				ret := p.list(data[prev:], ListTypeDefinition)
  1549  				return ret
  1550  			}
  1551  		}
  1552  
  1553  		// if there's a list after this, paragraph is over
  1554  		if p.extensions&NoEmptyLineBeforeBlock != 0 {
  1555  			if p.uliPrefix(current) != 0 ||
  1556  				p.oliPrefix(current) != 0 ||
  1557  				p.quotePrefix(current) != 0 ||
  1558  				p.codePrefix(current) != 0 {
  1559  				p.renderParagraph(data[:i])
  1560  				return i
  1561  			}
  1562  		}
  1563  
  1564  		// otherwise, scan to the beginning of the next line
  1565  		nl := bytes.IndexByte(data[i:], '\n')
  1566  		if nl >= 0 {
  1567  			i += nl + 1
  1568  		} else {
  1569  			i += len(data[i:])
  1570  		}
  1571  	}
  1572  
  1573  	p.renderParagraph(data[:i])
  1574  	return i
  1575  }
  1576  
  1577  func skipChar(data []byte, start int, char byte) int {
  1578  	i := start
  1579  	for i < len(data) && data[i] == char {
  1580  		i++
  1581  	}
  1582  	return i
  1583  }
  1584  
  1585  func skipUntilChar(text []byte, start int, char byte) int {
  1586  	i := start
  1587  	for i < len(text) && text[i] != char {
  1588  		i++
  1589  	}
  1590  	return i
  1591  }
  1592  
  1593  // SanitizedAnchorName returns a sanitized anchor name for the given text.
  1594  //
  1595  // It implements the algorithm specified in the package comment.
  1596  func SanitizedAnchorName(text string) string {
  1597  	var anchorName []rune
  1598  	futureDash := false
  1599  	for _, r := range text {
  1600  		switch {
  1601  		case unicode.IsLetter(r) || unicode.IsNumber(r):
  1602  			if futureDash && len(anchorName) > 0 {
  1603  				anchorName = append(anchorName, '-')
  1604  			}
  1605  			futureDash = false
  1606  			anchorName = append(anchorName, unicode.ToLower(r))
  1607  		default:
  1608  			futureDash = true
  1609  		}
  1610  	}
  1611  	return string(anchorName)
  1612  }
  1613  

View as plain text