Beispiel #1
0
func (p ParagraphDetector) Detect(first, second Line, detectors Detectors) Handler {
	block := md.ParagraphBlock{}
	return HandlerFunc(func(next Line, ctx Context) (bool, error) {
		if next.EOF() {
			return p.close(block, ctx)
		}
		if len(block.Raw) == 0 {
			block.Raw = append(block.Raw, md.Run(next))
			return true, nil
		}
		prev := Line(block.Raw[len(block.Raw)-1])
		// TODO(akavel): support HTML parser & related interactions [#paragraph-line-sequence]
		if prev.isBlank() {
			return p.close(block, ctx)
		}
		nextBytes := bytes.TrimRight(next.Bytes, "\n")
		if !next.hasFourSpacePrefix() {
			if reHorizontalRule.Match(nextBytes) ||
				(p.InQuote && bytes.HasPrefix(bytes.TrimLeft(next.Bytes, " "), []byte(">"))) ||
				(p.InList && reOrderedList.Match(nextBytes)) ||
				(p.InList && reUnorderedList.Match(nextBytes)) {
				return p.close(block, ctx)
			}
		}
		block.Raw = append(block.Raw, md.Run(next))
		return true, nil
	})
}
Beispiel #2
0
func DetectCode(first, second Line, detectors Detectors) Handler {
	if !first.hasFourSpacePrefix() {
		return nil
	}
	block := md.CodeBlock{}
	var paused *Line
	return HandlerFunc(func(next Line, ctx Context) (bool, error) {
		if next.EOF() {
			ctx.Emit(block)
			ctx.Emit(md.End{})
			return maybeNull(paused, ctx)
		}
		// TODO(akavel): verify it's coded ok, it was converted from a different approach
		fourspace := []byte("    ")
		switch {
		// previous was blank, next is not tab-indented. Reject both.
		case paused != nil && !next.hasFourSpacePrefix():
			ctx.Emit(block)
			ctx.Emit(md.End{})
			return maybeNull(paused, ctx)
		case next.isBlank():
			if paused != nil {
				block.Raw = append(block.Raw, md.Run(*paused))
				block.Prose = append(block.Prose, md.Run{
					paused.Line, bytes.TrimPrefix(paused.Bytes, fourspace)})
			}
			paused = &next // note: only case where we pause a line
			return true, nil
		case next.hasFourSpacePrefix():
			if paused != nil {
				block.Raw = append(block.Raw, md.Run(*paused))
				block.Prose = append(block.Prose, md.Run{
					paused.Line, bytes.TrimPrefix(paused.Bytes, fourspace)})
				paused = nil
			}
			block.Raw = append(block.Raw, md.Run(next))
			block.Prose = append(block.Prose, md.Run{
				next.Line, bytes.TrimPrefix(next.Bytes, fourspace)})
			return true, nil
		// next not blank & not indented. End the block.
		default:
			if paused != nil {
				block.Raw = append(block.Raw, md.Run(*paused))
				block.Prose = append(block.Prose, md.Run{
					paused.Line, bytes.TrimPrefix(paused.Bytes, fourspace)})
			}
			ctx.Emit(block)
			ctx.Emit(md.End{})
			return false, nil
		}
	})
}
Beispiel #3
0
func DetectSetextHeader(first, second Line, detectors Detectors) Handler {
	if second.EOF() {
		return nil
	}
	if !reSetextHeader.Match(bytes.TrimRight(second.Bytes, "\n")) {
		return nil
	}
	block := md.SetextHeaderBlock{}
	switch second.Bytes[0] {
	case '=':
		block.Level = 1
	case '-':
		block.Level = 2
	}
	done := 0
	return HandlerFunc(func(next Line, ctx Context) (bool, error) {
		if done == 2 {
			ctx.Emit(block)
			parseSpans(trim(md.Raw{block.Raw[0]}), ctx)
			ctx.Emit(md.End{})
			return false, nil
		}
		done++
		block.Raw = append(block.Raw, md.Run(next))
		return true, nil
	})
}
Beispiel #4
0
func DetectAtxHeader(first, second Line, detectors Detectors) Handler {
	if !bytes.HasPrefix(first.Bytes, []byte("#")) {
		return nil
	}
	done := false
	return HandlerFunc(func(line Line, ctx Context) (bool, error) {
		if done {
			return false, nil
		}
		done = true
		block := md.AtxHeaderBlock{
			Raw: md.Raw{md.Run(line)},
		}
		text := bytes.TrimRight(line.Bytes, "\n")
		text = bytes.Trim(text, "#")
		if len(text) > 0 {
			block.Level, _ = mdutils.OffsetIn(line.Bytes, text)
		} else {
			block.Level = len(bytes.TrimRight(line.Bytes, "\n"))
		}
		if block.Level > 6 {
			block.Level = 6
		}

		spanRegion := md.Raw{md.Run{
			Line:  line.Line,
			Bytes: bytes.Trim(text, mdutils.Whites),
		}}
		ctx.Emit(block)
		parseSpans(spanRegion, ctx)
		ctx.Emit(md.End{})
		return true, nil
	})
}
Beispiel #5
0
func maybeNull(paused *Line, ctx Context) (bool, error) {
	if paused != nil {
		ctx.Emit(md.NullBlock{
			Raw: md.Raw{md.Run(*paused)},
		})
		ctx.Emit(md.End{})
	}
	return false, nil
}
Beispiel #6
0
func DetectHorizontalRule(first, second Line, detectors Detectors) Handler {
	if !reHorizontalRule.Match(bytes.TrimRight(first.Bytes, "\n")) {
		return nil
	}
	done := false
	return HandlerFunc(func(next Line, ctx Context) (bool, error) {
		if done {
			return false, nil
		}
		done = true
		ctx.Emit(md.HorizontalRuleBlock{
			Raw: md.Raw{md.Run(next)},
		})
		ctx.Emit(md.End{})
		return true, nil
	})
}
Beispiel #7
0
func DetectNull(first, second Line, detectors Detectors) Handler {
	if !first.isBlank() {
		return nil
	}
	done := false
	return HandlerFunc(func(line Line, ctx Context) (bool, error) {
		if done {
			return false, nil
		}
		done = true
		ctx.Emit(md.NullBlock{
			Raw: md.Raw{md.Run(line)},
		})
		ctx.Emit(md.End{})
		return true, nil
	})
}
Beispiel #8
0
func DetectOrderedList(start, second Line, detectors Detectors) Handler {
	m := reOrderedList.FindSubmatch(start.Bytes)
	if m == nil {
		return nil
	}
	var buf *defaultContext
	block := &md.OrderedListBlock{
		Starter: md.Run{start.Line, m[1]},
		// firstNumber, _ := strconv.Atoi(string(m[2]))
	}
	var item *md.ItemBlock
	var carry *Line
	var parser *Parser
	return HandlerFunc(func(next Line, ctx Context) (bool, error) {
		if next.EOF() {
			return listEnd2(parser, buf, ctx)
		}
		prev := carry
		carry = &next
		if prev == nil {
			buf = &defaultContext{
				mode:          ctx.GetMode(),
				detectors:     changedParagraphDetector(ctx, false, true),
				spanDetectors: ctx.GetSpanDetectors(),
			}
			block.Raw = append(block.Raw, md.Run(next))
			buf.Emit(block)
			if ctx.GetMode() != TopBlocks {
				item = &md.ItemBlock{}
				item.Raw = append(item.Raw, md.Run(next))
				buf.Emit(item)
				parser = &Parser{
					Context: buf,
				}
			}
			return pass(parser, next, next.Bytes[len(block.Starter.Bytes):])
		}

		nextBytes := bytes.TrimRight(next.Bytes, "\n")
		if prev.isBlank() {
			if next.isBlank() {
				return listEnd2(parser, buf, ctx)
			}
			if !reOrderedList.Match(nextBytes) &&
				next.hasNonSpaceInPrefix(len(block.Starter.Bytes)) {
				return listEnd2(parser, buf, ctx)
			}
		} else {
			if !reOrderedList.Match(nextBytes) &&
				next.hasNonSpaceInPrefix(len(block.Starter.Bytes)) &&
				!next.hasFourSpacePrefix() &&
				(reUnorderedList.Match(nextBytes) ||
					reHorizontalRule.Match(nextBytes)) {
				return listEnd2(parser, buf, ctx)
			}
		}

		block.Raw = append(block.Raw, md.Run(next))
		m := reOrderedList.FindSubmatch(next.Bytes)
		if m != nil {
			text := bytes.TrimLeft(m[1], " ")
			spaces, _ := mdutils.OffsetIn(m[1], text)
			if spaces >= len(block.Starter.Bytes) {
				m = nil
			}
		}
		if m != nil {
			if ctx.GetMode() != TopBlocks {
				_, err := end(parser, buf)
				if err != nil {
					return false, err
				}
				item = &md.ItemBlock{}
				item.Raw = append(item.Raw, md.Run(next))
				buf.Emit(item)
				parser = &Parser{
					Context: buf,
				}
			}
			return pass(parser, next, next.Bytes[len(m[1]):])
		}
		if ctx.GetMode() != TopBlocks {
			item.Raw = append(item.Raw, md.Run(next))
		}
		return pass(parser, next, trimLeftN(next.Bytes, " ", len(block.Starter.Bytes)))
	})
}
Beispiel #9
0
func DetectReferenceResolution(start, second Line, detectors Detectors) Handler {
	if start.hasFourSpacePrefix() {
		return nil
	}
	m := reRefResolution1.FindSubmatch(bytes.TrimRight(start.Bytes, "\n"))
	if len(m) == 0 {
		return nil
	}
	b := md.ReferenceResolutionBlock{}
	unprocessedReferenceID := m[1]
	b.ReferenceID = mdutils.Simplify(unprocessedReferenceID)
	refValueSequence := m[9] // TODO(akavel): verify if right one
	m = reRefResolution2.FindSubmatch(refValueSequence)
	if len(m) == 0 {
		return nil
	}
	unprocessedURL := m[1]
	{
		tmp := make([]byte, 0, len(unprocessedURL))
		for _, c := range unprocessedURL {
			if c != ' ' && c != '<' && c != '>' {
				tmp = append(tmp, c)
			}
		}
		b.URL = string(tmp)
	}
	refDefinitionTrailingSequence := m[2]

	// Detected ok. Now check if 1 or 2 lines.
	var nlines int
	titleContainer := ""
	if bytes.IndexAny(refDefinitionTrailingSequence, " ") == -1 &&
		!second.EOF() &&
		reRefResolution3.Match(bytes.TrimRight(second.Bytes, "\n")) {
		nlines = 2
		titleContainer = string(bytes.TrimRight(second.Bytes, "\n"))
	} else {
		nlines = 1
		titleContainer = string(refDefinitionTrailingSequence)
	}

	// NOTE(akavel): below line seems not in the spec, but seems necessary (refDefinitionTrailingSequence always starts with space IIUC).
	titleContainer = strings.TrimLeft(titleContainer, " ")
	if m := reRefResolution4.FindStringSubmatch(titleContainer); len(m) != 0 {
		b.Title = mdutils.DeEscape(m[1])
	}
	if s := hasQuotedStringPrefix(titleContainer); s != "" {
		b.Title = mdutils.DeEscape(s[1 : len(s)-1])
	}

	return HandlerFunc(func(line Line, ctx Context) (bool, error) {
		if nlines == 0 {
			return false, nil
		}
		nlines--
		b.Raw = append(b.Raw, md.Run(line))
		if nlines == 0 {
			ctx.Emit(b)
			ctx.Emit(md.End{})
		}
		return true, nil
	})
}
Beispiel #10
0
func DetectUnorderedList(start, second Line, detectors Detectors) Handler {
	m := reUnorderedList.FindSubmatch(start.Bytes)
	if m == nil {
		return nil
	}
	var buf *defaultContext
	block := &md.UnorderedListBlock{
		Starter: md.Run{start.Line, m[1]},
	}
	var item *md.ItemBlock
	var carry *Line
	var parser *Parser
	return HandlerFunc(func(next Line, ctx Context) (bool, error) {
		if next.EOF() {
			return listEnd2(parser, buf, ctx)
		}
		prev := carry
		carry = &next
		// First line? Init stuff and accept unconditionally, already tested.
		if prev == nil {
			buf = &defaultContext{
				mode:          ctx.GetMode(),
				detectors:     changedParagraphDetector(ctx, false, true),
				spanDetectors: ctx.GetSpanDetectors(),
			}
			block.Raw = append(block.Raw, md.Run(next))
			buf.Emit(block)
			if ctx.GetMode() != TopBlocks {
				item = &md.ItemBlock{}
				item.Raw = append(item.Raw, md.Run(next))
				buf.Emit(item)
				parser = &Parser{
					Context: buf,
				}
			}
			return pass(parser, next, next.Bytes[len(block.Starter.Bytes):])
		}

		if prev.isBlank() {
			if next.isBlank() {
				return listEnd2(parser, buf, ctx)
			}
			if !bytes.HasPrefix(next.Bytes, block.Starter.Bytes) &&
				// FIXME(akavel): spec refers to runes ("characters"), not bytes; fix this everywhere
				next.hasNonSpaceInPrefix(len(block.Starter.Bytes)) {
				return listEnd2(parser, buf, ctx)
			}
		} else {
			nextBytes := bytes.TrimRight(next.Bytes, "\n")
			if !bytes.HasPrefix(next.Bytes, block.Starter.Bytes) &&
				next.hasNonSpaceInPrefix(len(block.Starter.Bytes)) &&
				!next.hasFourSpacePrefix() &&
				(reUnorderedList.Match(nextBytes) ||
					reOrderedList.Match(nextBytes) ||
					reHorizontalRule.Match(nextBytes)) {
				return listEnd2(parser, buf, ctx)
			}
		}

		block.Raw = append(block.Raw, md.Run(next))
		if bytes.HasPrefix(next.Bytes, block.Starter.Bytes) {
			if ctx.GetMode() != TopBlocks {
				_, err := end(parser, buf)
				if err != nil {
					return false, err
				}
				item = &md.ItemBlock{}
				item.Raw = append(item.Raw, md.Run(next))
				buf.Emit(item)
				parser = &Parser{
					Context: buf,
				}
			}
			return pass(parser, next, next.Bytes[len(block.Starter.Bytes):])
		}
		if ctx.GetMode() != TopBlocks {
			item.Raw = append(item.Raw, md.Run(next))
		}
		return pass(parser, next, trimLeftN(next.Bytes, " ", len(block.Starter.Bytes)))
	})
}