// Copyright 2015 The Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package markdown import ( "bytes" "unicode/utf8" ) type ParserBlock struct{} type BlockRule func(*StateBlock, int, int, bool) bool var blockRules []BlockRule var nl = []byte{'\n'} func normalizeNewlines(src []byte) ([]byte, int) { if bytes.IndexByte(src, '\r') == -1 { return src, bytes.Count(src, nl) } n := 0 buf := make([]byte, 0, len(src)) for i := 0; i < len(src); i++ { switch ch := src[i]; ch { case '\n': n++ buf = append(buf, '\n') case '\r': buf = append(buf, '\n') n++ if i < len(src)-1 && src[i+1] == '\n' { i++ } default: buf = append(buf, ch) } } return buf, n } func (b ParserBlock) Parse(src []byte, md *Markdown, env *Environment) []Token { src, n := normalizeNewlines(src) if len(src) == 0 || src[len(src)-1] != '\n' { n++ } n++ indentFound := false start := 0 indent := 0 offset := 0 mem := make([]int, 0, n*5) bMarks := mem[0:0:n] eMarks := mem[n : n : n*2] tShift := mem[n*2 : n*2 : n*3] sCount := mem[n*3 : n*3 : n*4] bsCount := mem[n*4 : n*4 : n*5] _, lastRuneLen := utf8.DecodeLastRune(src) lastRunePos := len(src) - lastRuneLen for pos, r := range string(src) { if !indentFound { if runeIsSpace(r) { indent++ if r == '\t' { offset += 4 - offset%4 } else { offset++ } continue } indentFound = true } if r == '\n' || pos == lastRunePos { if r != '\n' { pos = len(src) } bMarks = append(bMarks, start) eMarks = append(eMarks, pos) tShift = append(tShift, indent) sCount = append(sCount, offset) bsCount = append(bsCount, 0) indentFound = false indent = 0 offset = 0 start = pos + 1 } } bMarks = append(bMarks, len(src)) eMarks = append(eMarks, len(src)) tShift = append(tShift, 0) sCount = append(sCount, 0) bsCount = append(bsCount, 0) var s StateBlock s.BMarks = bMarks s.EMarks = eMarks s.TShift = tShift s.SCount = sCount s.BSCount = bsCount s.LineMax = n - 1 s.Src = string(src) s.Md = md s.Env = env b.Tokenize(&s, s.Line, s.LineMax) return s.Tokens } func (ParserBlock) Tokenize(s *StateBlock, startLine, endLine int) { line := startLine hasEmptyLines := false maxNesting := s.Md.MaxNesting for line < endLine { line = s.SkipEmptyLines(line) s.Line = line if line >= endLine { break } if s.SCount[line] < s.BlkIndent { break } if s.Level >= maxNesting { s.Line = endLine break } for _, r := range blockRules { if r(s, line, endLine, false) { break } } s.Tight = !hasEmptyLines if s.IsLineEmpty(s.Line - 1) { hasEmptyLines = true } line = s.Line if line < endLine && s.IsLineEmpty(line) { hasEmptyLines = true line++ s.Line = line } } }