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 }) }
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 } }) }
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 }) }
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 }) }
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 }
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 }) }
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 }) }
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))) }) }
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 }) }
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))) }) }