all repos — grayfriday @ 679e1686dbcb2f48866df1c2cc0ac64bc6048af7

blackfriday fork with a few changes

block.go (view raw)

   1//
   2// Black Friday Markdown Processor
   3// Originally based on http://github.com/tanoku/upskirt
   4// by Russ Ross <russ@russross.com>
   5//
   6
   7//
   8// Functions to parse block-level elements.
   9//
  10
  11package blackfriday
  12
  13import (
  14	"bytes"
  15)
  16
  17// parse block-level data
  18func parseBlock(out *bytes.Buffer, rndr *render, data []byte) {
  19	if rndr.nesting >= rndr.maxNesting {
  20		return
  21	}
  22	rndr.nesting++
  23
  24	for len(data) > 0 {
  25		if isPrefixHeader(rndr, data) {
  26			data = data[blockPrefixHeader(out, rndr, data):]
  27			continue
  28		}
  29		if data[0] == '<' && rndr.mk.blockhtml != nil {
  30			if i := blockHtml(out, rndr, data, true); i > 0 {
  31				data = data[i:]
  32				continue
  33			}
  34		}
  35		if i := isEmpty(data); i > 0 {
  36			data = data[i:]
  37			continue
  38		}
  39		if isHrule(data) {
  40			if rndr.mk.hrule != nil {
  41				rndr.mk.hrule(out, rndr.mk.opaque)
  42			}
  43			var i int
  44			for i = 0; i < len(data) && data[i] != '\n'; i++ {
  45			}
  46			data = data[i:]
  47			continue
  48		}
  49		if rndr.flags&EXTENSION_FENCED_CODE != 0 {
  50			if i := blockFencedCode(out, rndr, data); i > 0 {
  51				data = data[i:]
  52				continue
  53			}
  54		}
  55		if rndr.flags&EXTENSION_TABLES != 0 {
  56			if i := blockTable(out, rndr, data); i > 0 {
  57				data = data[i:]
  58				continue
  59			}
  60		}
  61		if blockQuotePrefix(data) > 0 {
  62			data = data[blockQuote(out, rndr, data):]
  63			continue
  64		}
  65		if blockCodePrefix(data) > 0 {
  66			data = data[blockCode(out, rndr, data):]
  67			continue
  68		}
  69		if blockUliPrefix(data) > 0 {
  70			data = data[blockList(out, rndr, data, 0):]
  71			continue
  72		}
  73		if blockOliPrefix(data) > 0 {
  74			data = data[blockList(out, rndr, data, LIST_TYPE_ORDERED):]
  75			continue
  76		}
  77
  78		data = data[blockParagraph(out, rndr, data):]
  79	}
  80
  81	rndr.nesting--
  82}
  83
  84func isPrefixHeader(rndr *render, data []byte) bool {
  85	if data[0] != '#' {
  86		return false
  87	}
  88
  89	if rndr.flags&EXTENSION_SPACE_HEADERS != 0 {
  90		level := 0
  91		for level < len(data) && level < 6 && data[level] == '#' {
  92			level++
  93		}
  94		if level < len(data) && data[level] != ' ' && data[level] != '\t' {
  95			return false
  96		}
  97	}
  98	return true
  99}
 100
 101func blockPrefixHeader(out *bytes.Buffer, rndr *render, data []byte) int {
 102	level := 0
 103	for level < len(data) && level < 6 && data[level] == '#' {
 104		level++
 105	}
 106	i, end := 0, 0
 107	for i = level; i < len(data) && (data[i] == ' ' || data[i] == '\t'); i++ {
 108	}
 109	for end = i; end < len(data) && data[end] != '\n'; end++ {
 110	}
 111	skip := end
 112	for end > 0 && data[end-1] == '#' {
 113		end--
 114	}
 115	for end > 0 && (data[end-1] == ' ' || data[end-1] == '\t') {
 116		end--
 117	}
 118	if end > i {
 119		work := bytes.NewBuffer(nil)
 120		parseInline(work, rndr, data[i:end])
 121		if rndr.mk.header != nil {
 122			rndr.mk.header(out, work.Bytes(), level, rndr.mk.opaque)
 123		}
 124	}
 125	return skip
 126}
 127
 128func isUnderlinedHeader(data []byte) int {
 129	i := 0
 130
 131	// test of level 1 header
 132	if data[i] == '=' {
 133		for i = 1; i < len(data) && data[i] == '='; i++ {
 134		}
 135		for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 136			i++
 137		}
 138		if i >= len(data) || data[i] == '\n' {
 139			return 1
 140		} else {
 141			return 0
 142		}
 143	}
 144
 145	// test of level 2 header
 146	if data[i] == '-' {
 147		for i = 1; i < len(data) && data[i] == '-'; i++ {
 148		}
 149		for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 150			i++
 151		}
 152		if i >= len(data) || data[i] == '\n' {
 153			return 2
 154		} else {
 155			return 0
 156		}
 157	}
 158
 159	return 0
 160}
 161
 162func blockHtml(out *bytes.Buffer, rndr *render, data []byte, do_render bool) int {
 163	var i, j int
 164
 165	// identify the opening tag
 166	if len(data) < 2 || data[0] != '<' {
 167		return 0
 168	}
 169	curtag, tagfound := blockHtmlFindTag(data[1:])
 170
 171	// handle special cases
 172	if !tagfound {
 173
 174		// HTML comment, laxist form
 175		if len(data) > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-' {
 176			i = 5
 177
 178			for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') {
 179				i++
 180			}
 181			i++
 182
 183			if i < len(data) {
 184				j = isEmpty(data[i:])
 185			}
 186
 187			if j > 0 {
 188				size := i + j
 189				if do_render && rndr.mk.blockhtml != nil {
 190					rndr.mk.blockhtml(out, data[:size], rndr.mk.opaque)
 191				}
 192				return size
 193			}
 194		}
 195
 196		// HR, which is the only self-closing block tag considered
 197		if len(data) > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R') {
 198			i = 3
 199			for i < len(data) && data[i] != '>' {
 200				i++
 201			}
 202
 203			if i+1 < len(data) {
 204				i++
 205				j = isEmpty(data[i:])
 206				if j > 0 {
 207					size := i + j
 208					if do_render && rndr.mk.blockhtml != nil {
 209						rndr.mk.blockhtml(out, data[:size], rndr.mk.opaque)
 210					}
 211					return size
 212				}
 213			}
 214		}
 215
 216		// no special case recognized
 217		return 0
 218	}
 219
 220	// look for an unindented matching closing tag
 221	//      followed by a blank line
 222	i = 1
 223	found := false
 224
 225	// if not found, try a second pass looking for indented match
 226	// but not if tag is "ins" or "del" (following original Markdown.pl)
 227	if curtag != "ins" && curtag != "del" {
 228		i = 1
 229		for i < len(data) {
 230			i++
 231			for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
 232				i++
 233			}
 234
 235			if i+2+len(curtag) >= len(data) {
 236				break
 237			}
 238
 239			j = blockHtmlFindEnd(curtag, rndr, data[i-1:])
 240
 241			if j > 0 {
 242				i += j - 1
 243				found = true
 244				break
 245			}
 246		}
 247	}
 248
 249	if !found {
 250		return 0
 251	}
 252
 253	// the end of the block has been found
 254	if do_render && rndr.mk.blockhtml != nil {
 255		rndr.mk.blockhtml(out, data[:i], rndr.mk.opaque)
 256	}
 257
 258	return i
 259}
 260
 261func blockHtmlFindTag(data []byte) (string, bool) {
 262	i := 0
 263	for i < len(data) && ((data[i] >= '0' && data[i] <= '9') || (data[i] >= 'A' && data[i] <= 'Z') || (data[i] >= 'a' && data[i] <= 'z')) {
 264		i++
 265	}
 266	if i >= len(data) {
 267		return "", false
 268	}
 269	key := string(data[:i])
 270	if block_tags[key] {
 271		return key, true
 272	}
 273	return "", false
 274}
 275
 276func blockHtmlFindEnd(tag string, rndr *render, data []byte) int {
 277	// assume data[0] == '<' && data[1] == '/' already tested
 278
 279	// check if tag is a match
 280	if len(tag)+3 >= len(data) || bytes.Compare(data[2:2+len(tag)], []byte(tag)) != 0 || data[len(tag)+2] != '>' {
 281		return 0
 282	}
 283
 284	// check white lines
 285	i := len(tag) + 3
 286	w := 0
 287	if i < len(data) {
 288		if w = isEmpty(data[i:]); w == 0 {
 289			return 0 // non-blank after tag
 290		}
 291	}
 292	i += w
 293	w = 0
 294
 295	if rndr.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
 296		if i < len(data) {
 297			w = isEmpty(data[i:])
 298		}
 299	} else {
 300		if i < len(data) {
 301			if w = isEmpty(data[i:]); w == 0 {
 302				return 0 // non-blank line after tag line
 303			}
 304		}
 305	}
 306
 307	return i + w
 308}
 309
 310func isEmpty(data []byte) int {
 311	var i int
 312	for i = 0; i < len(data) && data[i] != '\n'; i++ {
 313		if data[i] != ' ' && data[i] != '\t' {
 314			return 0
 315		}
 316	}
 317	return i + 1
 318}
 319
 320func isHrule(data []byte) bool {
 321	// skip initial spaces
 322	if len(data) < 3 {
 323		return false
 324	}
 325	i := 0
 326	if data[0] == ' ' {
 327		i++
 328		if data[1] == ' ' {
 329			i++
 330			if data[2] == ' ' {
 331				i++
 332			}
 333		}
 334	}
 335
 336	// look at the hrule char
 337	if i+2 >= len(data) || (data[i] != '*' && data[i] != '-' && data[i] != '_') {
 338		return false
 339	}
 340	c := data[i]
 341
 342	// the whole line must be the char or whitespace
 343	n := 0
 344	for i < len(data) && data[i] != '\n' {
 345		switch {
 346		case data[i] == c:
 347			n++
 348		case data[i] != ' ' && data[i] != '\t':
 349			return false
 350		}
 351		i++
 352	}
 353
 354	return n >= 3
 355}
 356
 357func isFencedCode(data []byte, syntax **string) int {
 358	i, n := 0, 0
 359
 360	// skip initial spaces
 361	if len(data) < 3 {
 362		return 0
 363	}
 364	if data[0] == ' ' {
 365		i++
 366		if data[1] == ' ' {
 367			i++
 368			if data[2] == ' ' {
 369				i++
 370			}
 371		}
 372	}
 373
 374	// look at the hrule char
 375	if i+2 >= len(data) || !(data[i] == '~' || data[i] == '`') {
 376		return 0
 377	}
 378
 379	c := data[i]
 380
 381	// the whole line must be the char or whitespace
 382	for i < len(data) && data[i] == c {
 383		n++
 384		i++
 385	}
 386
 387	if n < 3 {
 388		return 0
 389	}
 390
 391	if syntax != nil {
 392		syn := 0
 393
 394		for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 395			i++
 396		}
 397
 398		syntax_start := i
 399
 400		if i < len(data) && data[i] == '{' {
 401			i++
 402			syntax_start++
 403
 404			for i < len(data) && data[i] != '}' && data[i] != '\n' {
 405				syn++
 406				i++
 407			}
 408
 409			if i == len(data) || data[i] != '}' {
 410				return 0
 411			}
 412
 413			// string all whitespace at the beginning and the end
 414			// of the {} block
 415			for syn > 0 && isspace(data[syntax_start]) {
 416				syntax_start++
 417				syn--
 418			}
 419
 420			for syn > 0 && isspace(data[syntax_start+syn-1]) {
 421				syn--
 422			}
 423
 424			i++
 425		} else {
 426			for i < len(data) && !isspace(data[i]) {
 427				syn++
 428				i++
 429			}
 430		}
 431
 432		language := string(data[syntax_start : syntax_start+syn])
 433		*syntax = &language
 434	}
 435
 436	for i < len(data) && data[i] != '\n' {
 437		if !isspace(data[i]) {
 438			return 0
 439		}
 440		i++
 441	}
 442
 443	return i + 1
 444}
 445
 446func blockFencedCode(out *bytes.Buffer, rndr *render, data []byte) int {
 447	var lang *string
 448	beg := isFencedCode(data, &lang)
 449	if beg == 0 {
 450		return 0
 451	}
 452
 453	work := bytes.NewBuffer(nil)
 454
 455	for beg < len(data) {
 456		fence_end := isFencedCode(data[beg:], nil)
 457		if fence_end != 0 {
 458			beg += fence_end
 459			break
 460		}
 461
 462		var end int
 463		for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ {
 464		}
 465
 466		if beg < end {
 467			// verbatim copy to the working buffer, escaping entities
 468			if isEmpty(data[beg:]) > 0 {
 469				work.WriteByte('\n')
 470			} else {
 471				work.Write(data[beg:end])
 472			}
 473		}
 474		beg = end
 475	}
 476
 477	if work.Len() > 0 && work.Bytes()[work.Len()-1] != '\n' {
 478		work.WriteByte('\n')
 479	}
 480
 481	if rndr.mk.blockcode != nil {
 482		syntax := ""
 483		if lang != nil {
 484			syntax = *lang
 485		}
 486
 487		rndr.mk.blockcode(out, work.Bytes(), syntax, rndr.mk.opaque)
 488	}
 489
 490	return beg
 491}
 492
 493func blockTable(out *bytes.Buffer, rndr *render, data []byte) int {
 494	header_work := bytes.NewBuffer(nil)
 495	i, columns, col_data := blockTableHeader(header_work, rndr, data)
 496	if i > 0 {
 497		body_work := bytes.NewBuffer(nil)
 498
 499		for i < len(data) {
 500			pipes, row_start := 0, i
 501			for ; i < len(data) && data[i] != '\n'; i++ {
 502				if data[i] == '|' {
 503					pipes++
 504				}
 505			}
 506
 507			if pipes == 0 || i == len(data) {
 508				i = row_start
 509				break
 510			}
 511
 512			blockTableRow(body_work, rndr, data[row_start:i], columns, col_data)
 513			i++
 514		}
 515
 516		if rndr.mk.table != nil {
 517			rndr.mk.table(out, header_work.Bytes(), body_work.Bytes(), col_data, rndr.mk.opaque)
 518		}
 519	}
 520
 521	return i
 522}
 523
 524func blockTableHeader(out *bytes.Buffer, rndr *render, data []byte) (size int, columns int, column_data []int) {
 525	i, pipes := 0, 0
 526	column_data = []int{}
 527	for i = 0; i < len(data) && data[i] != '\n'; i++ {
 528		if data[i] == '|' {
 529			pipes++
 530		}
 531	}
 532
 533	if i == len(data) || pipes == 0 {
 534		return 0, 0, column_data
 535	}
 536
 537	header_end := i
 538
 539	if data[0] == '|' {
 540		pipes--
 541	}
 542
 543	if i > 2 && data[i-1] == '|' {
 544		pipes--
 545	}
 546
 547	columns = pipes + 1
 548	column_data = make([]int, columns)
 549
 550	// parse the header underline
 551	i++
 552	if i < len(data) && data[i] == '|' {
 553		i++
 554	}
 555
 556	under_end := i
 557	for under_end < len(data) && data[under_end] != '\n' {
 558		under_end++
 559	}
 560
 561	col := 0
 562	for ; col < columns && i < under_end; col++ {
 563		dashes := 0
 564
 565		for i < under_end && (data[i] == ' ' || data[i] == '\t') {
 566			i++
 567		}
 568
 569		if data[i] == ':' {
 570			i++
 571			column_data[col] |= TABLE_ALIGNMENT_LEFT
 572			dashes++
 573		}
 574
 575		for i < under_end && data[i] == '-' {
 576			i++
 577			dashes++
 578		}
 579
 580		if i < under_end && data[i] == ':' {
 581			i++
 582			column_data[col] |= TABLE_ALIGNMENT_RIGHT
 583			dashes++
 584		}
 585
 586		for i < under_end && (data[i] == ' ' || data[i] == '\t') {
 587			i++
 588		}
 589
 590		if i < under_end && data[i] != '|' {
 591			break
 592		}
 593
 594		if dashes < 3 {
 595			break
 596		}
 597
 598		i++
 599	}
 600
 601	if col < columns {
 602		return 0, 0, column_data
 603	}
 604
 605	blockTableRow(out, rndr, data[:header_end], columns, column_data)
 606	size = under_end + 1
 607	return
 608}
 609
 610func blockTableRow(out *bytes.Buffer, rndr *render, data []byte, columns int, col_data []int) {
 611	i, col := 0, 0
 612	row_work := bytes.NewBuffer(nil)
 613
 614	if i < len(data) && data[i] == '|' {
 615		i++
 616	}
 617
 618	for col = 0; col < columns && i < len(data); col++ {
 619		for i < len(data) && isspace(data[i]) {
 620			i++
 621		}
 622
 623		cell_start := i
 624
 625		for i < len(data) && data[i] != '|' {
 626			i++
 627		}
 628
 629		cell_end := i - 1
 630
 631		for cell_end > cell_start && isspace(data[cell_end]) {
 632			cell_end--
 633		}
 634
 635		cell_work := bytes.NewBuffer(nil)
 636		parseInline(cell_work, rndr, data[cell_start:cell_end+1])
 637
 638		if rndr.mk.tableCell != nil {
 639			cdata := 0
 640			if col < len(col_data) {
 641				cdata = col_data[col]
 642			}
 643			rndr.mk.tableCell(row_work, cell_work.Bytes(), cdata, rndr.mk.opaque)
 644		}
 645
 646		i++
 647	}
 648
 649	for ; col < columns; col++ {
 650		empty_cell := []byte{}
 651		if rndr.mk.tableCell != nil {
 652			cdata := 0
 653			if col < len(col_data) {
 654				cdata = col_data[col]
 655			}
 656			rndr.mk.tableCell(row_work, empty_cell, cdata, rndr.mk.opaque)
 657		}
 658	}
 659
 660	if rndr.mk.tableRow != nil {
 661		rndr.mk.tableRow(out, row_work.Bytes(), rndr.mk.opaque)
 662	}
 663}
 664
 665// returns blockquote prefix length
 666func blockQuotePrefix(data []byte) int {
 667	i := 0
 668	for i < len(data) && i < 3 && data[i] == ' ' {
 669		i++
 670	}
 671	if i < len(data) && data[i] == '>' {
 672		if i+1 < len(data) && (data[i+1] == ' ' || data[i+1] == '\t') {
 673			return i + 2
 674		}
 675		return i + 1
 676	}
 677	return 0
 678}
 679
 680// parse a blockquote fragment
 681func blockQuote(out *bytes.Buffer, rndr *render, data []byte) int {
 682	block := bytes.NewBuffer(nil)
 683	work := bytes.NewBuffer(nil)
 684	beg, end := 0, 0
 685	for beg < len(data) {
 686		for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ {
 687		}
 688
 689		if pre := blockQuotePrefix(data[beg:]); pre > 0 {
 690			beg += pre // skip prefix
 691		} else {
 692			// empty line followed by non-quote line
 693			if isEmpty(data[beg:]) > 0 && (end >= len(data) || (blockQuotePrefix(data[end:]) == 0 && isEmpty(data[end:]) == 0)) {
 694				break
 695			}
 696		}
 697
 698		if beg < end { // copy into the in-place working buffer
 699			work.Write(data[beg:end])
 700		}
 701		beg = end
 702	}
 703
 704	parseBlock(block, rndr, work.Bytes())
 705	if rndr.mk.blockquote != nil {
 706		rndr.mk.blockquote(out, block.Bytes(), rndr.mk.opaque)
 707	}
 708	return end
 709}
 710
 711// returns prefix length for block code
 712func blockCodePrefix(data []byte) int {
 713	if len(data) > 0 && data[0] == '\t' {
 714		return 1
 715	}
 716	if len(data) > 3 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
 717		return 4
 718	}
 719	return 0
 720}
 721
 722func blockCode(out *bytes.Buffer, rndr *render, data []byte) int {
 723	work := bytes.NewBuffer(nil)
 724
 725	beg, end := 0, 0
 726	for beg < len(data) {
 727		for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ {
 728		}
 729
 730		if pre := blockCodePrefix(data[beg:end]); pre > 0 {
 731			beg += pre
 732		} else {
 733			if isEmpty(data[beg:end]) == 0 {
 734				// non-empty non-prefixed line breaks the pre
 735				break
 736			}
 737		}
 738
 739		if beg < end {
 740			// verbatim copy to the working buffer, escaping entities
 741			if isEmpty(data[beg:end]) > 0 {
 742				work.WriteByte('\n')
 743			} else {
 744				work.Write(data[beg:end])
 745			}
 746		}
 747		beg = end
 748	}
 749
 750	// trim all the \n off the end of work
 751	workbytes := work.Bytes()
 752	n := 0
 753	for len(workbytes) > n && workbytes[len(workbytes)-n-1] == '\n' {
 754		n++
 755	}
 756	if n > 0 {
 757		work = bytes.NewBuffer(workbytes[:len(workbytes)-n])
 758	}
 759
 760	work.WriteByte('\n')
 761
 762	if rndr.mk.blockcode != nil {
 763		rndr.mk.blockcode(out, work.Bytes(), "", rndr.mk.opaque)
 764	}
 765
 766	return beg
 767}
 768
 769// returns unordered list item prefix
 770func blockUliPrefix(data []byte) int {
 771	i := 0
 772	for i < len(data) && i < 3 && data[i] == ' ' {
 773		i++
 774	}
 775	if i+1 >= len(data) || (data[i] != '*' && data[i] != '+' && data[i] != '-') || (data[i+1] != ' ' && data[i+1] != '\t') {
 776		return 0
 777	}
 778	return i + 2
 779}
 780
 781// returns ordered list item prefix
 782func blockOliPrefix(data []byte) int {
 783	i := 0
 784	for i < len(data) && i < 3 && data[i] == ' ' {
 785		i++
 786	}
 787	if i >= len(data) || data[i] < '0' || data[i] > '9' {
 788		return 0
 789	}
 790	for i < len(data) && data[i] >= '0' && data[i] <= '9' {
 791		i++
 792	}
 793	if i+1 >= len(data) || data[i] != '.' || (data[i+1] != ' ' && data[i+1] != '\t') {
 794		return 0
 795	}
 796	return i + 2
 797}
 798
 799// parse ordered or unordered list block
 800func blockList(out *bytes.Buffer, rndr *render, data []byte, flags int) int {
 801	work := bytes.NewBuffer(nil)
 802
 803	i, j := 0, 0
 804	for i < len(data) {
 805		j = blockListItem(work, rndr, data[i:], &flags)
 806		i += j
 807
 808		if j == 0 || flags&LIST_ITEM_END_OF_LIST != 0 {
 809			break
 810		}
 811	}
 812
 813	if rndr.mk.list != nil {
 814		rndr.mk.list(out, work.Bytes(), flags, rndr.mk.opaque)
 815	}
 816	return i
 817}
 818
 819// parse a single list item
 820// assumes initial prefix is already removed
 821func blockListItem(out *bytes.Buffer, rndr *render, data []byte, flags *int) int {
 822	// keep track of the first indentation prefix
 823	beg, end, pre, sublist, orgpre, i := 0, 0, 0, 0, 0, 0
 824
 825	for orgpre < 3 && orgpre < len(data) && data[orgpre] == ' ' {
 826		orgpre++
 827	}
 828
 829	beg = blockUliPrefix(data)
 830	if beg == 0 {
 831		beg = blockOliPrefix(data)
 832	}
 833	if beg == 0 {
 834		return 0
 835	}
 836
 837	// skip leading whitespace on first line
 838	for beg < len(data) && data[beg] == ' ' {
 839		beg++
 840	}
 841
 842	// skip to the beginning of the following line
 843	end = beg
 844	for end < len(data) && data[end-1] != '\n' {
 845		end++
 846	}
 847
 848	// get working buffers
 849	work := bytes.NewBuffer(nil)
 850	inter := bytes.NewBuffer(nil)
 851
 852	// put the first line into the working buffer
 853	work.Write(data[beg:end])
 854	beg = end
 855
 856	// process the following lines
 857	in_empty, has_inside_empty := false, false
 858	for beg < len(data) {
 859		end++
 860
 861		for end < len(data) && data[end-1] != '\n' {
 862			end++
 863		}
 864
 865		// process an empty line
 866		if isEmpty(data[beg:end]) > 0 {
 867			in_empty = true
 868			beg = end
 869			continue
 870		}
 871
 872		// calculate the indentation
 873		i = 0
 874		for i < 4 && beg+i < end && data[beg+i] == ' ' {
 875			i++
 876		}
 877
 878		pre = i
 879		if data[beg] == '\t' {
 880			i = 1
 881			pre = 8
 882		}
 883
 884		// check for a new item
 885		chunk := data[beg+i : end]
 886		if (blockUliPrefix(chunk) > 0 && !isHrule(chunk)) || blockOliPrefix(chunk) > 0 {
 887			if in_empty {
 888				has_inside_empty = true
 889			}
 890
 891			if pre == orgpre { // the following item must have the same indentation
 892				break
 893			}
 894
 895			if sublist == 0 {
 896				sublist = work.Len()
 897			}
 898		} else {
 899			// only join indented stuff after empty lines
 900			if in_empty && i < 4 && data[beg] != '\t' {
 901				*flags |= LIST_ITEM_END_OF_LIST
 902				break
 903			} else {
 904				if in_empty {
 905					work.WriteByte('\n')
 906					has_inside_empty = true
 907				}
 908			}
 909		}
 910
 911		in_empty = false
 912
 913		// add the line into the working buffer without prefix
 914		work.Write(data[beg+i : end])
 915		beg = end
 916	}
 917
 918	// render li contents
 919	if has_inside_empty {
 920		*flags |= LIST_ITEM_CONTAINS_BLOCK
 921	}
 922
 923	workbytes := work.Bytes()
 924	if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 {
 925		// intermediate render of block li
 926		if sublist > 0 && sublist < len(workbytes) {
 927			parseBlock(inter, rndr, workbytes[:sublist])
 928			parseBlock(inter, rndr, workbytes[sublist:])
 929		} else {
 930			parseBlock(inter, rndr, workbytes)
 931		}
 932	} else {
 933		// intermediate render of inline li
 934		if sublist > 0 && sublist < len(workbytes) {
 935			parseInline(inter, rndr, workbytes[:sublist])
 936			parseBlock(inter, rndr, workbytes[sublist:])
 937		} else {
 938			parseInline(inter, rndr, workbytes)
 939		}
 940	}
 941
 942	// render li itself
 943	if rndr.mk.listitem != nil {
 944		rndr.mk.listitem(out, inter.Bytes(), *flags, rndr.mk.opaque)
 945	}
 946
 947	return beg
 948}
 949
 950func blockParagraph(out *bytes.Buffer, rndr *render, data []byte) int {
 951	i, end, level := 0, 0, 0
 952
 953	for i < len(data) {
 954		for end = i + 1; end < len(data) && data[end-1] != '\n'; end++ {
 955		}
 956
 957		if isEmpty(data[i:]) > 0 {
 958			break
 959		}
 960		if level = isUnderlinedHeader(data[i:]); level > 0 {
 961			break
 962		}
 963
 964		if rndr.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
 965			if data[i] == '<' && rndr.mk.blockhtml != nil && blockHtml(out, rndr, data[i:], false) > 0 {
 966				end = i
 967				break
 968			}
 969		}
 970
 971		if isPrefixHeader(rndr, data[i:]) || isHrule(data[i:]) {
 972			end = i
 973			break
 974		}
 975
 976		i = end
 977	}
 978
 979	work := data
 980	size := i
 981	for size > 0 && work[size-1] == '\n' {
 982		size--
 983	}
 984
 985	if level == 0 {
 986		tmp := bytes.NewBuffer(nil)
 987		parseInline(tmp, rndr, work[:size])
 988		if rndr.mk.paragraph != nil {
 989			rndr.mk.paragraph(out, tmp.Bytes(), rndr.mk.opaque)
 990		}
 991	} else {
 992		if size > 0 {
 993			beg := 0
 994			i = size
 995			size--
 996
 997			for size > 0 && work[size] != '\n' {
 998				size--
 999			}
1000
1001			beg = size + 1
1002			for size > 0 && work[size-1] == '\n' {
1003				size--
1004			}
1005
1006			if size > 0 {
1007				tmp := bytes.NewBuffer(nil)
1008				parseInline(tmp, rndr, work[:size])
1009				if rndr.mk.paragraph != nil {
1010					rndr.mk.paragraph(out, tmp.Bytes(), rndr.mk.opaque)
1011				}
1012
1013				work = work[beg:]
1014				size = i - beg
1015			} else {
1016				size = i
1017			}
1018		}
1019
1020		header_work := bytes.NewBuffer(nil)
1021		parseInline(header_work, rndr, work[:size])
1022
1023		if rndr.mk.header != nil {
1024			rndr.mk.header(out, header_work.Bytes(), level, rndr.mk.opaque)
1025		}
1026	}
1027
1028	return end
1029}