4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-07-11 21:16:27 +00:00

Update dependencies and remove old matterclient lib (#2067)

This commit is contained in:
Wim
2023-08-05 20:43:19 +02:00
committed by GitHub
parent 9459495484
commit 56e7bd01ca
772 changed files with 139315 additions and 121315 deletions

View File

@ -1,9 +0,0 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
# and commit this file to your remote git repository to share the goodness with others.
tasks:
- init: go get && go build ./... && go test ./...
command: go run

View File

@ -6,7 +6,16 @@ Package `github.com/gomarkdown/markdown` is a Go library for parsing Markdown te
It's very fast and supports common extensions.
Try code examples online: https://replit.com/@kjk1?path=folder/gomarkdown
Tutorial: https://blog.kowalczyk.info/article/cxn3/advanced-markdown-processing-in-go.html
Code examples:
* https://onlinetool.io/goplayground/#txO7hJ-ibeU : basic markdown => HTML
* https://onlinetool.io/goplayground/#yFRIWRiu-KL : customize HTML renderer
* https://onlinetool.io/goplayground/#2yV5-HDKBUV : modify AST
* https://onlinetool.io/goplayground/#9fqKwRbuJ04 : customize parser
* https://onlinetool.io/goplayground/#Bk0zTvrzUDR : syntax highlight
Those examples are also in [examples](./examples) directory.
## API Docs:
@ -15,101 +24,58 @@ Try code examples online: https://replit.com/@kjk1?path=folder/gomarkdown
- https://pkg.go.dev/github.com/gomarkdown/markdown/parser : parser
- https://pkg.go.dev/github.com/gomarkdown/markdown/html : html renderer
## Users
Some tools using this package: https://pkg.go.dev/github.com/gomarkdown/markdown?tab=importedby
## Usage
To convert markdown text to HTML using reasonable defaults:
```go
md := []byte("## markdown document")
output := markdown.ToHTML(md, nil, nil)
```
package main
Try it online: https://replit.com/@kjk1/gomarkdown-basic
## Customizing markdown parser
Markdown format is loosely specified and there are multiple extensions invented after original specification was created.
The parser supports several [extensions](https://pkg.go.dev/github.com/gomarkdown/markdown/parser#Extensions).
Default parser uses most common `parser.CommonExtensions` but you can easily use parser with custom extension:
```go
import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/parser"
"os"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"fmt"
)
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
parser := parser.NewWithExtensions(extensions)
var mds = `# header
md := []byte("markdown text")
html := markdown.ToHTML(md, parser, nil)
```
Sample text.
Try it online: https://replit.com/@kjk1/gomarkdown-customized-html-renderer
[link](http://example.com)
`
## Customizing HTML renderer
func mdToHTML(md []byte) []byte {
// create markdown parser with extensions
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
p := parser.NewWithExtensions(extensions)
doc := p.Parse(md)
Similarly, HTML renderer can be configured with different [options](https://pkg.go.dev/github.com/gomarkdown/markdown/html#RendererOptions)
// create HTML renderer with extensions
htmlFlags := html.CommonFlags | html.HrefTargetBlank
opts := html.RendererOptions{Flags: htmlFlags}
renderer := html.NewRenderer(opts)
Here's how to use a custom renderer:
```go
import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
)
htmlFlags := html.CommonFlags | html.HrefTargetBlank
opts := html.RendererOptions{Flags: htmlFlags}
renderer := html.NewRenderer(opts)
md := []byte("markdown text")
html := markdown.ToHTML(md, nil, renderer)
```
Try it online: https://replit.com/@kjk1/gomarkdown-customized-html-renderer
HTML renderer also supports reusing most of the logic and overriding rendering of only specific nodes.
You can provide [RenderNodeFunc](https://pkg.go.dev/github.com/gomarkdown/markdown/html#RenderNodeFunc) in [RendererOptions](https://pkg.go.dev/github.com/gomarkdown/markdown/html#RendererOptions).
The function is called for each node in AST, you can implement custom rendering logic and tell HTML renderer to skip rendering this node.
Here's the simplest example that drops all code blocks from the output:
````go
import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/html"
)
// return (ast.GoToNext, true) to tell html renderer to skip rendering this node
// (because you've rendered it)
func renderHookDropCodeBlock(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
// skip all nodes that are not CodeBlock nodes
if _, ok := node.(*ast.CodeBlock); !ok {
return ast.GoToNext, false
}
// custom rendering logic for ast.CodeBlock. By doing nothing it won't be
// present in the output
return ast.GoToNext, true
return markdown.Render(doc, renderer)
}
opts := html.RendererOptions{
Flags: html.CommonFlags,
RenderNodeHook: renderHookDropCodeBlock,
func main() {
md := []byte(mds)
html := mdToHTML(md)
fmt.Printf("--- Markdown:\n%s\n\n--- HTML:\n%s\n", md, html)
}
renderer := html.NewRenderer(opts)
md := "test\n```\nthis code block will be dropped from output\n```\ntext"
html := markdown.ToHTML([]byte(md), nil, renderer)
````
```
Try it online: https://onlinetool.io/goplayground/#txO7hJ-ibeU
For more documentation read [this guide](https://blog.kowalczyk.info/article/cxn3/advanced-markdown-processing-in-go.html)
Comparing to other markdown parsers: https://babelmark.github.io/
## Sanitize untrusted content
@ -129,12 +95,6 @@ maybeUnsafeHTML := markdown.ToHTML(md, nil, nil)
html := bluemonday.UGCPolicy().SanitizeBytes(maybeUnsafeHTML)
```
## Windows / Mac newlines
The library only supports Unix newlines. If you have markdown text with possibly
Windows / Mac newlines, normalize newlines before calling this library using
`d = markdown.NormalizeNewlines(d)`
## mdtohtml command-line tool
https://github.com/gomarkdown/mdtohtml is a command-line markdown to html
@ -323,26 +283,15 @@ implements the following extensions:
- **Mmark support**, see <https://mmark.miek.nl/post/syntax/> for all new syntax elements this adds.
## Todo
## Users
- port https://github.com/russross/blackfriday/issues/348
- port [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
renders output as LaTeX.
- port https://github.com/shurcooL/github_flavored_markdown to markdown
- port [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
but for markdown.
- More unit testing
- Improve unicode support. It does not understand all unicode
rules (about what constitutes a letter, a punctuation symbol,
etc.), so it may fail to detect word boundaries correctly in
some instances. It is safe on all utf-8 input.
Some tools using this package: https://pkg.go.dev/github.com/gomarkdown/markdown?tab=importedby
## History
markdown is a fork of v2 of https://github.com/russross/blackfriday that is:
markdown is a fork of v2 of https://github.com/russross/blackfriday.
- actively maintained (sadly in Feb 2018 blackfriday was inactive for 5 months with many bugs and pull requests accumulated)
- refactored API (split into ast/parser/html sub-packages)
I refactored the API (split into ast/parser/html sub-packages).
Blackfriday itself was based on C implementation [sundown](https://github.com/vmg/sundown) which in turn was based on [libsoldout](http://fossil.instinctive.eu/libsoldout/home).

View File

@ -92,6 +92,12 @@ type Container struct {
*Attribute // Block level attribute
}
// return true if can contain children of a given node type
// used by custom nodes to over-ride logic in canNodeContain
type CanContain interface {
CanContain(Node) bool
}
// AsContainer returns itself as *Container
func (c *Container) AsContainer() *Container {
return c

View File

@ -157,6 +157,8 @@ func printRecur(w io.Writer, node Node, prefix string, depth int) {
content += "flags=" + flags + " "
}
printDefault(w, indent, typeName, content)
case *CodeBlock:
printDefault(w, indent, typeName + ":" + string(v.Info), content)
default:
printDefault(w, indent, typeName, content)
}

View File

@ -1,8 +0,0 @@
coverage:
status:
project:
default:
# basic
target: 60%
threshold: 2%
base: auto

View File

@ -88,13 +88,15 @@ type RendererOptions struct {
// FootnoteReturnLinks flag is enabled. If blank, the string
// <sup>[return]</sup> is used.
FootnoteReturnLinkContents string
// CitationFormatString defines how a citation is rendered. If blnck, the string
// CitationFormatString defines how a citation is rendered. If blank, the string
// <sup>[%s]</sup> is used. Where %s will be substituted with the citation target.
CitationFormatString string
// If set, add this text to the front of each Heading ID, to ensure uniqueness.
HeadingIDPrefix string
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
HeadingIDSuffix string
// can over-write <p> for paragraph tag
ParagraphTag string
Title string // Document title (used if CompletePage is set)
CSS string // Optional CSS file URL (used if CompletePage is set)
@ -120,7 +122,7 @@ type RendererOptions struct {
//
// Do not create this directly, instead use the NewRenderer function.
type Renderer struct {
opts RendererOptions
Opts RendererOptions
closeTag string // how to end singleton tags: either " />" or ">"
@ -168,7 +170,7 @@ func EscapeHTML(w io.Writer, d []byte) {
}
}
func escLink(w io.Writer, text []byte) {
func EscLink(w io.Writer, text []byte) {
unesc := html.UnescapeString(string(text))
EscapeHTML(w, []byte(unesc))
}
@ -207,7 +209,7 @@ func NewRenderer(opts RendererOptions) *Renderer {
}
return &Renderer{
opts: opts,
Opts: opts,
closeTag: closeTag,
headingIDs: make(map[string]int),
@ -250,12 +252,12 @@ func isRelativeLink(link []byte) (yes bool) {
return false
}
func (r *Renderer) addAbsPrefix(link []byte) []byte {
if len(link) == 0 {
func AddAbsPrefix(link []byte, prefix string) []byte {
if len(link) == 0 || len(prefix) == 0 {
return link
}
if r.opts.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
newDest := r.opts.AbsolutePrefix
if isRelativeLink(link) && link[0] != '.' {
newDest := prefix
if link[0] != '/' {
newDest += "/"
}
@ -294,7 +296,7 @@ func isMailto(link []byte) bool {
}
func needSkipLink(r *Renderer, dest []byte) bool {
flags := r.opts.Flags
flags := r.Opts.Flags
if flags&SkipLinks != 0 {
return true
}
@ -317,7 +319,7 @@ func appendLanguageAttr(attrs []string, info []byte) []string {
return append(attrs, s)
}
func (r *Renderer) outTag(w io.Writer, name string, attrs []string) {
func (r *Renderer) OutTag(w io.Writer, name string, attrs []string) {
s := name
if len(attrs) > 0 {
s += " " + strings.Join(attrs, " ")
@ -326,22 +328,22 @@ func (r *Renderer) outTag(w io.Writer, name string, attrs []string) {
r.lastOutputLen = 1
}
func footnoteRef(prefix string, node *ast.Link) string {
urlFrag := prefix + string(slugify(node.Destination))
func FootnoteRef(prefix string, node *ast.Link) string {
urlFrag := prefix + string(Slugify(node.Destination))
nStr := strconv.Itoa(node.NoteID)
anchor := `<a href="#fn:` + urlFrag + `">` + nStr + `</a>`
return `<sup class="footnote-ref" id="fnref:` + urlFrag + `">` + anchor + `</sup>`
}
func footnoteItem(prefix string, slug []byte) string {
func FootnoteItem(prefix string, slug []byte) string {
return `<li id="fn:` + prefix + string(slug) + `">`
}
func footnoteReturnLink(prefix, returnLink string, slug []byte) string {
func FootnoteReturnLink(prefix, returnLink string, slug []byte) string {
return ` <a class="footnote-return" href="#fnref:` + prefix + string(slug) + `">` + returnLink + `</a>`
}
func listItemOpenCR(listItem *ast.ListItem) bool {
func ListItemOpenCR(listItem *ast.ListItem) bool {
if ast.GetPrevNode(listItem) == nil {
return false
}
@ -349,13 +351,13 @@ func listItemOpenCR(listItem *ast.ListItem) bool {
return !ld.Tight && ld.ListFlags&ast.ListTypeDefinition == 0
}
func skipParagraphTags(para *ast.Paragraph) bool {
func SkipParagraphTags(para *ast.Paragraph) bool {
parent := para.Parent
grandparent := parent.GetParent()
if grandparent == nil || !isList(grandparent) {
if grandparent == nil || !IsList(grandparent) {
return false
}
isParentTerm := isListItemTerm(parent)
isParentTerm := IsListItemTerm(parent)
grandparentListData := grandparent.(*ast.List)
tightOrTerm := grandparentListData.Tight || isParentTerm
return tightOrTerm
@ -391,35 +393,35 @@ var (
closeHTags = []string{"</h1>", "</h2>", "</h3>", "</h4>", "</h5>"}
)
func headingOpenTagFromLevel(level int) string {
func HeadingOpenTagFromLevel(level int) string {
if level < 1 || level > 5 {
return "<h6"
}
return openHTags[level-1]
}
func headingCloseTagFromLevel(level int) string {
func HeadingCloseTagFromLevel(level int) string {
if level < 1 || level > 5 {
return "</h6>"
}
return closeHTags[level-1]
}
func (r *Renderer) outHRTag(w io.Writer, attrs []string) {
func (r *Renderer) OutHRTag(w io.Writer, attrs []string) {
hr := TagWithAttributes("<hr", attrs)
r.OutOneOf(w, r.opts.Flags&UseXHTML == 0, hr, "<hr />")
r.OutOneOf(w, r.Opts.Flags&UseXHTML == 0, hr, "<hr />")
}
// Text writes ast.Text node
func (r *Renderer) Text(w io.Writer, text *ast.Text) {
if r.opts.Flags&Smartypants != 0 {
if r.Opts.Flags&Smartypants != 0 {
var tmp bytes.Buffer
EscapeHTML(&tmp, text.Literal)
r.sr.Process(w, tmp.Bytes())
} else {
_, parentIsLink := text.Parent.(*ast.Link)
if parentIsLink {
escLink(w, text.Literal)
EscLink(w, text.Literal)
} else {
EscapeHTML(w, text.Literal)
}
@ -428,7 +430,7 @@ func (r *Renderer) Text(w io.Writer, text *ast.Text) {
// HardBreak writes ast.Hardbreak node
func (r *Renderer) HardBreak(w io.Writer, node *ast.Hardbreak) {
r.OutOneOf(w, r.opts.Flags&UseXHTML == 0, "<br>", "<br />")
r.OutOneOf(w, r.Opts.Flags&UseXHTML == 0, "<br>", "<br />")
r.CR(w)
}
@ -459,7 +461,7 @@ func (r *Renderer) OutOneOfCr(w io.Writer, outFirst bool, first string, second s
// HTMLSpan writes ast.HTMLSpan node
func (r *Renderer) HTMLSpan(w io.Writer, span *ast.HTMLSpan) {
if r.opts.Flags&SkipHTML == 0 {
if r.Opts.Flags&SkipHTML == 0 {
r.Out(w, span.Literal)
}
}
@ -467,18 +469,18 @@ func (r *Renderer) HTMLSpan(w io.Writer, span *ast.HTMLSpan) {
func (r *Renderer) linkEnter(w io.Writer, link *ast.Link) {
attrs := link.AdditionalAttributes
dest := link.Destination
dest = r.addAbsPrefix(dest)
dest = AddAbsPrefix(dest, r.Opts.AbsolutePrefix)
var hrefBuf bytes.Buffer
hrefBuf.WriteString("href=\"")
escLink(&hrefBuf, dest)
EscLink(&hrefBuf, dest)
hrefBuf.WriteByte('"')
attrs = append(attrs, hrefBuf.String())
if link.NoteID != 0 {
r.Outs(w, footnoteRef(r.opts.FootnoteAnchorPrefix, link))
r.Outs(w, FootnoteRef(r.Opts.FootnoteAnchorPrefix, link))
return
}
attrs = appendLinkAttrs(attrs, r.opts.Flags, dest)
attrs = appendLinkAttrs(attrs, r.Opts.Flags, dest)
if len(link.Title) > 0 {
var titleBuff bytes.Buffer
titleBuff.WriteString("title=\"")
@ -486,7 +488,7 @@ func (r *Renderer) linkEnter(w io.Writer, link *ast.Link) {
titleBuff.WriteByte('"')
attrs = append(attrs, titleBuff.String())
}
r.outTag(w, "<a", attrs)
r.OutTag(w, "<a", attrs)
}
func (r *Renderer) linkExit(w io.Writer, link *ast.Link) {
@ -511,33 +513,34 @@ func (r *Renderer) Link(w io.Writer, link *ast.Link, entering bool) {
}
func (r *Renderer) imageEnter(w io.Writer, image *ast.Image) {
dest := image.Destination
dest = r.addAbsPrefix(dest)
if r.DisableTags == 0 {
//if options.safe && potentiallyUnsafe(dest) {
//out(w, `<img src="" alt="`)
//} else {
if r.opts.Flags&LazyLoadImages != 0 {
r.Outs(w, `<img loading="lazy" src="`)
} else {
r.Outs(w, `<img src="`)
}
escLink(w, dest)
r.Outs(w, `" alt="`)
//}
}
r.DisableTags++
if r.DisableTags > 1 {
return
}
src := image.Destination
src = AddAbsPrefix(src, r.Opts.AbsolutePrefix)
attrs := BlockAttrs(image)
if r.Opts.Flags&LazyLoadImages != 0 {
attrs = append(attrs, `loading="lazy"`)
}
s := TagWithAttributes("<img", attrs)
s = s[:len(s)-1] // hackish: strip off ">" from end
r.Outs(w, s+` src="`)
EscLink(w, src)
r.Outs(w, `" alt="`)
}
func (r *Renderer) imageExit(w io.Writer, image *ast.Image) {
r.DisableTags--
if r.DisableTags == 0 {
if image.Title != nil {
r.Outs(w, `" title="`)
EscapeHTML(w, image.Title)
}
r.Outs(w, `" />`)
if r.DisableTags > 0 {
return
}
if image.Title != nil {
r.Outs(w, `" title="`)
EscapeHTML(w, image.Title)
}
r.Outs(w, `" />`)
}
// Image writes ast.Image node
@ -571,20 +574,28 @@ func (r *Renderer) paragraphEnter(w io.Writer, para *ast.Paragraph) {
}
}
tag := TagWithAttributes("<p", BlockAttrs(para))
ptag := "<p"
if r.Opts.ParagraphTag != "" {
ptag = "<" + r.Opts.ParagraphTag
}
tag := TagWithAttributes(ptag, BlockAttrs(para))
r.Outs(w, tag)
}
func (r *Renderer) paragraphExit(w io.Writer, para *ast.Paragraph) {
r.Outs(w, "</p>")
if !(isListItem(para.Parent) && ast.GetNextNode(para) == nil) {
ptag := "</p>"
if r.Opts.ParagraphTag != "" {
ptag = "</" + r.Opts.ParagraphTag + ">"
}
r.Outs(w, ptag)
if !(IsListItem(para.Parent) && ast.GetNextNode(para) == nil) {
r.CR(w)
}
}
// Paragraph writes ast.Paragraph node
func (r *Renderer) Paragraph(w io.Writer, para *ast.Paragraph, entering bool) {
if skipParagraphTags(para) {
if SkipParagraphTags(para) {
return
}
if entering {
@ -603,7 +614,7 @@ func (r *Renderer) Code(w io.Writer, node *ast.Code) {
// HTMLBlock write ast.HTMLBlock node
func (r *Renderer) HTMLBlock(w io.Writer, node *ast.HTMLBlock) {
if r.opts.Flags&SkipHTML != 0 {
if r.Opts.Flags&SkipHTML != 0 {
return
}
r.CR(w)
@ -611,6 +622,25 @@ func (r *Renderer) HTMLBlock(w io.Writer, node *ast.HTMLBlock) {
r.CR(w)
}
func (r *Renderer) EnsureUniqueHeadingID(id string) string {
for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
tmp := fmt.Sprintf("%s-%d", id, count+1)
if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
r.headingIDs[id] = count + 1
id = tmp
} else {
id = id + "-1"
}
}
if _, found := r.headingIDs[id]; !found {
r.headingIDs[id] = 0
}
return id
}
func (r *Renderer) headingEnter(w io.Writer, nodeData *ast.Heading) {
var attrs []string
var class string
@ -629,44 +659,25 @@ func (r *Renderer) headingEnter(w io.Writer, nodeData *ast.Heading) {
attrs = []string{`class="` + class + `"`}
}
ensureUniqueHeadingID := func(id string) string {
for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
tmp := fmt.Sprintf("%s-%d", id, count+1)
if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
r.headingIDs[id] = count + 1
id = tmp
} else {
id = id + "-1"
}
}
if _, found := r.headingIDs[id]; !found {
r.headingIDs[id] = 0
}
return id
}
if nodeData.HeadingID != "" {
id := ensureUniqueHeadingID(nodeData.HeadingID)
if r.opts.HeadingIDPrefix != "" {
id = r.opts.HeadingIDPrefix + id
id := r.EnsureUniqueHeadingID(nodeData.HeadingID)
if r.Opts.HeadingIDPrefix != "" {
id = r.Opts.HeadingIDPrefix + id
}
if r.opts.HeadingIDSuffix != "" {
id = id + r.opts.HeadingIDSuffix
if r.Opts.HeadingIDSuffix != "" {
id = id + r.Opts.HeadingIDSuffix
}
attrID := `id="` + id + `"`
attrs = append(attrs, attrID)
}
attrs = append(attrs, BlockAttrs(nodeData)...)
r.CR(w)
r.outTag(w, headingOpenTagFromLevel(nodeData.Level), attrs)
r.OutTag(w, HeadingOpenTagFromLevel(nodeData.Level), attrs)
}
func (r *Renderer) headingExit(w io.Writer, heading *ast.Heading) {
r.Outs(w, headingCloseTagFromLevel(heading.Level))
if !(isListItem(heading.Parent) && ast.GetNextNode(heading) == nil) {
r.Outs(w, HeadingCloseTagFromLevel(heading.Level))
if !(IsListItem(heading.Parent) && ast.GetNextNode(heading) == nil) {
r.CR(w)
}
}
@ -683,7 +694,7 @@ func (r *Renderer) Heading(w io.Writer, node *ast.Heading, entering bool) {
// HorizontalRule writes ast.HorizontalRule node
func (r *Renderer) HorizontalRule(w io.Writer, node *ast.HorizontalRule) {
r.CR(w)
r.outHRTag(w, BlockAttrs(node))
r.OutHRTag(w, BlockAttrs(node))
r.CR(w)
}
@ -693,15 +704,15 @@ func (r *Renderer) listEnter(w io.Writer, nodeData *ast.List) {
if nodeData.IsFootnotesList {
r.Outs(w, "\n<div class=\"footnotes\">\n\n")
if r.opts.Flags&FootnoteNoHRTag == 0 {
r.outHRTag(w, nil)
if r.Opts.Flags&FootnoteNoHRTag == 0 {
r.OutHRTag(w, nil)
r.CR(w)
}
}
r.CR(w)
if isListItem(nodeData.Parent) {
if IsListItem(nodeData.Parent) {
grand := nodeData.Parent.GetParent()
if isListTight(grand) {
if IsListTight(grand) {
r.CR(w)
}
}
@ -717,7 +728,7 @@ func (r *Renderer) listEnter(w io.Writer, nodeData *ast.List) {
openTag = "<dl"
}
attrs = append(attrs, BlockAttrs(nodeData)...)
r.outTag(w, openTag, attrs)
r.OutTag(w, openTag, attrs)
r.CR(w)
}
@ -760,12 +771,12 @@ func (r *Renderer) List(w io.Writer, list *ast.List, entering bool) {
}
func (r *Renderer) listItemEnter(w io.Writer, listItem *ast.ListItem) {
if listItemOpenCR(listItem) {
if ListItemOpenCR(listItem) {
r.CR(w)
}
if listItem.RefLink != nil {
slug := slugify(listItem.RefLink)
r.Outs(w, footnoteItem(r.opts.FootnoteAnchorPrefix, slug))
slug := Slugify(listItem.RefLink)
r.Outs(w, FootnoteItem(r.Opts.FootnoteAnchorPrefix, slug))
return
}
@ -780,11 +791,11 @@ func (r *Renderer) listItemEnter(w io.Writer, listItem *ast.ListItem) {
}
func (r *Renderer) listItemExit(w io.Writer, listItem *ast.ListItem) {
if listItem.RefLink != nil && r.opts.Flags&FootnoteReturnLinks != 0 {
slug := slugify(listItem.RefLink)
prefix := r.opts.FootnoteAnchorPrefix
link := r.opts.FootnoteReturnLinkContents
s := footnoteReturnLink(prefix, link, slug)
if listItem.RefLink != nil && r.Opts.Flags&FootnoteReturnLinks != 0 {
slug := Slugify(listItem.RefLink)
prefix := r.Opts.FootnoteAnchorPrefix
link := r.Opts.FootnoteReturnLinkContents
s := FootnoteReturnLink(prefix, link, slug)
r.Outs(w, s)
}
@ -815,7 +826,7 @@ func (r *Renderer) EscapeHTMLCallouts(w io.Writer, d []byte) {
ld := len(d)
Parse:
for i := 0; i < ld; i++ {
for _, comment := range r.opts.Comments {
for _, comment := range r.Opts.Comments {
if !bytes.HasPrefix(d[i:], comment) {
break
}
@ -853,14 +864,14 @@ func (r *Renderer) CodeBlock(w io.Writer, codeBlock *ast.CodeBlock) {
r.Outs(w, "<pre>")
code := TagWithAttributes("<code", attrs)
r.Outs(w, code)
if r.opts.Comments != nil {
if r.Opts.Comments != nil {
r.EscapeHTMLCallouts(w, codeBlock.Literal)
} else {
EscapeHTML(w, codeBlock.Literal)
}
r.Outs(w, "</code>")
r.Outs(w, "</pre>")
if !isListItem(codeBlock.Parent) {
if !IsListItem(codeBlock.Parent) {
r.CR(w)
}
}
@ -910,7 +921,7 @@ func (r *Renderer) TableCell(w io.Writer, tableCell *ast.TableCell, entering boo
if ast.GetPrevNode(tableCell) == nil {
r.CR(w)
}
r.outTag(w, openTag, attrs)
r.OutTag(w, openTag, attrs)
}
// TableBody writes ast.TableBody node
@ -959,8 +970,8 @@ func (r *Renderer) Citation(w io.Writer, node *ast.Citation) {
case ast.CitationTypeSuppressed:
attr[0] = `class="suppressed"`
}
r.outTag(w, "<cite", attr)
r.Outs(w, fmt.Sprintf(`<a href="#%s">`+r.opts.CitationFormatString+`</a>`, c, c))
r.OutTag(w, "<cite", attr)
r.Outs(w, fmt.Sprintf(`<a href="#%s">`+r.Opts.CitationFormatString+`</a>`, c, c))
r.Outs(w, "</cite>")
}
}
@ -968,7 +979,7 @@ func (r *Renderer) Citation(w io.Writer, node *ast.Citation) {
// Callout writes ast.Callout node
func (r *Renderer) Callout(w io.Writer, node *ast.Callout) {
attr := []string{`class="callout"`}
r.outTag(w, "<span", attr)
r.OutTag(w, "<span", attr)
r.Out(w, node.ID)
r.Outs(w, "</span>")
}
@ -977,14 +988,14 @@ func (r *Renderer) Callout(w io.Writer, node *ast.Callout) {
func (r *Renderer) Index(w io.Writer, node *ast.Index) {
// there is no in-text representation.
attr := []string{`class="index"`, fmt.Sprintf(`id="%s"`, node.ID)}
r.outTag(w, "<span", attr)
r.OutTag(w, "<span", attr)
r.Outs(w, "</span>")
}
// RenderNode renders a markdown node to HTML
func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.WalkStatus {
if r.opts.RenderNodeHook != nil {
status, didHandle := r.opts.RenderNodeHook(w, node, entering)
if r.Opts.RenderNodeHook != nil {
status, didHandle := r.Opts.RenderNodeHook(w, node, entering)
if didHandle {
return status
}
@ -1019,7 +1030,7 @@ func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Wal
case *ast.Citation:
r.Citation(w, node)
case *ast.Image:
if r.opts.Flags&SkipImages != 0 {
if r.Opts.Flags&SkipImages != 0 {
return ast.SkipChildren
}
r.Image(w, node, entering)
@ -1098,7 +1109,7 @@ func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Wal
// RenderHeader writes HTML document preamble and TOC if requested.
func (r *Renderer) RenderHeader(w io.Writer, ast ast.Node) {
r.writeDocumentHeader(w)
if r.opts.Flags&TOC != 0 {
if r.Opts.Flags&TOC != 0 {
r.writeTOC(w, ast)
}
}
@ -1109,18 +1120,18 @@ func (r *Renderer) RenderFooter(w io.Writer, _ ast.Node) {
r.Outs(w, "</section>\n")
}
if r.opts.Flags&CompletePage == 0 {
if r.Opts.Flags&CompletePage == 0 {
return
}
io.WriteString(w, "\n</body>\n</html>\n")
}
func (r *Renderer) writeDocumentHeader(w io.Writer) {
if r.opts.Flags&CompletePage == 0 {
if r.Opts.Flags&CompletePage == 0 {
return
}
ending := ""
if r.opts.Flags&UseXHTML != 0 {
if r.Opts.Flags&UseXHTML != 0 {
io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
@ -1131,35 +1142,35 @@ func (r *Renderer) writeDocumentHeader(w io.Writer) {
}
io.WriteString(w, "<head>\n")
io.WriteString(w, " <title>")
if r.opts.Flags&Smartypants != 0 {
r.sr.Process(w, []byte(r.opts.Title))
if r.Opts.Flags&Smartypants != 0 {
r.sr.Process(w, []byte(r.Opts.Title))
} else {
EscapeHTML(w, []byte(r.opts.Title))
EscapeHTML(w, []byte(r.Opts.Title))
}
io.WriteString(w, "</title>\n")
io.WriteString(w, r.opts.Generator)
io.WriteString(w, r.Opts.Generator)
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
io.WriteString(w, " <meta charset=\"utf-8\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
if r.opts.CSS != "" {
if r.Opts.CSS != "" {
io.WriteString(w, " <link rel=\"stylesheet\" type=\"text/css\" href=\"")
EscapeHTML(w, []byte(r.opts.CSS))
EscapeHTML(w, []byte(r.Opts.CSS))
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
}
if r.opts.Icon != "" {
if r.Opts.Icon != "" {
io.WriteString(w, " <link rel=\"icon\" type=\"image/x-icon\" href=\"")
EscapeHTML(w, []byte(r.opts.Icon))
EscapeHTML(w, []byte(r.Opts.Icon))
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
}
if r.opts.Head != nil {
w.Write(r.opts.Head)
if r.Opts.Head != nil {
w.Write(r.Opts.Head)
}
io.WriteString(w, "</head>\n")
io.WriteString(w, "<body>\n\n")
@ -1221,31 +1232,31 @@ func (r *Renderer) writeTOC(w io.Writer, doc ast.Node) {
r.lastOutputLen = buf.Len()
}
func isList(node ast.Node) bool {
func IsList(node ast.Node) bool {
_, ok := node.(*ast.List)
return ok
}
func isListTight(node ast.Node) bool {
func IsListTight(node ast.Node) bool {
if list, ok := node.(*ast.List); ok {
return list.Tight
}
return false
}
func isListItem(node ast.Node) bool {
func IsListItem(node ast.Node) bool {
_, ok := node.(*ast.ListItem)
return ok
}
func isListItemTerm(node ast.Node) bool {
func IsListItemTerm(node ast.Node) bool {
data, ok := node.(*ast.ListItem)
return ok && data.ListFlags&ast.ListTypeTerm != 0
}
// TODO: move to internal package
// Create a url-safe slug for fragments
func slugify(in []byte) []byte {
func Slugify(in []byte) []byte {
if len(in) == 0 {
return in
}

View File

@ -84,28 +84,7 @@ func ToHTML(markdown []byte, p *parser.Parser, renderer Renderer) []byte {
return Render(doc, renderer)
}
// NormalizeNewlines converts Windows and Mac newlines to Unix newlines
// The parser only supports Unix newlines. If your mardown content
// NormalizeNewlines converts Windows and Mac newlines to Unix newlines.
// The parser only supports Unix newlines. If your markdown content
// might contain Windows or Mac newlines, use this function to convert to Unix newlines
func NormalizeNewlines(d []byte) []byte {
wi := 0
n := len(d)
for i := 0; i < n; i++ {
c := d[i]
// 13 is CR
if c != 13 {
d[wi] = c
wi++
continue
}
// replace CR (mac / win) with LF (unix)
d[wi] = 10
wi++
if i < n-1 && d[i+1] == 10 {
// this was CRLF, so skip the LF
i++
}
}
return d[:wi]
}
var NormalizeNewlines = parser.NormalizeNewlines

View File

@ -25,13 +25,13 @@ func (p *Parser) asidePrefix(data []byte) int {
// aside ends with at least one blank line
// followed by something without a aside prefix
func (p *Parser) terminateAside(data []byte, beg, end int) bool {
if p.isEmpty(data[beg:]) <= 0 {
if IsEmpty(data[beg:]) <= 0 {
return false
}
if end >= len(data) {
return true
}
return p.asidePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0
return p.asidePrefix(data[end:]) == 0 && IsEmpty(data[end:]) == 0
}
// parse a aside fragment
@ -66,8 +66,8 @@ func (p *Parser) aside(data []byte) int {
beg = end
}
block := p.addBlock(&ast.Aside{})
p.block(raw.Bytes())
p.finalize(block)
block := p.AddBlock(&ast.Aside{})
p.Block(raw.Bytes())
p.Finalize(block)
return end
}

View File

@ -103,10 +103,10 @@ func sanitizeHeadingID(text string) string {
return string(anchorName)
}
// Parse block-level data.
// Parse Block-level data.
// Note: this function and many that it calls assume that
// the input buffer ends with a newline.
func (p *Parser) block(data []byte) {
func (p *Parser) Block(data []byte) {
// this is called recursively: enforce a maximum depth
if p.nesting >= p.maxNesting {
return
@ -142,7 +142,7 @@ func (p *Parser) block(data []byte) {
}
}
p.includeStack.Push(path)
p.block(included)
p.Block(included)
p.includeStack.Pop()
data = data[consumed:]
continue
@ -156,10 +156,10 @@ func (p *Parser) block(data []byte) {
data = data[consumed:]
if node != nil {
p.addBlock(node)
p.AddBlock(node)
if blockdata != nil {
p.block(blockdata)
p.finalize(node)
p.Block(blockdata)
p.Finalize(node)
}
}
continue
@ -213,7 +213,7 @@ func (p *Parser) block(data []byte) {
}
// blank lines. note: returns the # of bytes to skip
if i := p.isEmpty(data); i > 0 {
if i := IsEmpty(data); i > 0 {
data = data[i:]
continue
}
@ -255,11 +255,11 @@ func (p *Parser) block(data []byte) {
// ******
// or
// ______
if p.isHRule(data) {
if isHRule(data) {
i := skipUntilChar(data, 0, '\n')
hr := ast.HorizontalRule{}
hr.Literal = bytes.Trim(data[:i], " \n")
p.addBlock(&hr)
p.AddBlock(&hr)
data = data[i:]
continue
}
@ -377,7 +377,7 @@ func (p *Parser) block(data []byte) {
p.nesting--
}
func (p *Parser) addBlock(n ast.Node) ast.Node {
func (p *Parser) AddBlock(n ast.Node) ast.Node {
p.closeUnmatchedBlocks()
if p.attr != nil {
@ -448,7 +448,7 @@ func (p *Parser) prefixHeading(data []byte) int {
p.allHeadingsWithAutoID = append(p.allHeadingsWithAutoID, block)
}
block.Content = data[i:end]
p.addBlock(block)
p.AddBlock(block)
}
return skip
}
@ -521,7 +521,7 @@ func (p *Parser) prefixSpecialHeading(data []byte) int {
}
block.Literal = data[i:end]
block.Content = data[i:end]
p.addBlock(block)
p.AddBlock(block)
}
return skip
}
@ -572,7 +572,7 @@ func (p *Parser) titleBlock(data []byte, doRender bool) int {
IsTitleblock: true,
}
block.Content = data
p.addBlock(block)
p.AddBlock(block)
return consumed
}
@ -617,14 +617,14 @@ func (p *Parser) html(data []byte, doRender bool) int {
}
// see if it is the only thing on the line
if skip := p.isEmpty(data[j:]); skip > 0 {
if skip := IsEmpty(data[j:]); skip > 0 {
// see if it is followed by a blank line/eof
j += skip
if j >= len(data) {
found = true
i = j
} else {
if skip := p.isEmpty(data[j:]); skip > 0 {
if skip := IsEmpty(data[j:]); skip > 0 {
j += skip
found = true
i = j
@ -667,7 +667,7 @@ func (p *Parser) html(data []byte, doRender bool) int {
// trim newlines
end := backChar(data, i, '\n')
htmlBLock := &ast.HTMLBlock{Leaf: ast.Leaf{Content: data[:end]}}
p.addBlock(htmlBLock)
p.AddBlock(htmlBLock)
finalizeHTMLBlock(htmlBLock)
}
@ -683,13 +683,13 @@ func finalizeHTMLBlock(block *ast.HTMLBlock) {
func (p *Parser) htmlComment(data []byte, doRender bool) int {
i := p.inlineHTMLComment(data)
// needs to end with a blank line
if j := p.isEmpty(data[i:]); j > 0 {
if j := IsEmpty(data[i:]); j > 0 {
size := i + j
if doRender {
// trim trailing newlines
end := backChar(data, size, '\n')
htmlBLock := &ast.HTMLBlock{Leaf: ast.Leaf{Content: data[:end]}}
p.addBlock(htmlBLock)
p.AddBlock(htmlBLock)
finalizeHTMLBlock(htmlBLock)
}
return size
@ -715,13 +715,13 @@ func (p *Parser) htmlHr(data []byte, doRender bool) int {
}
if i < len(data) && data[i] == '>' {
i++
if j := p.isEmpty(data[i:]); j > 0 {
if j := IsEmpty(data[i:]); j > 0 {
size := i + j
if doRender {
// trim newlines
end := backChar(data, size, '\n')
htmlBlock := &ast.HTMLBlock{Leaf: ast.Leaf{Content: data[:end]}}
p.addBlock(htmlBlock)
p.AddBlock(htmlBlock)
finalizeHTMLBlock(htmlBlock)
}
return size
@ -753,7 +753,7 @@ func (p *Parser) htmlFindEnd(tag string, data []byte) int {
// check that the rest of the line is blank
skip := 0
if skip = p.isEmpty(data[i:]); skip == 0 {
if skip = IsEmpty(data[i:]); skip == 0 {
return 0
}
i += skip
@ -766,7 +766,7 @@ func (p *Parser) htmlFindEnd(tag string, data []byte) int {
if p.extensions&LaxHTMLBlocks != 0 {
return i
}
if skip = p.isEmpty(data[i:]); skip == 0 {
if skip = IsEmpty(data[i:]); skip == 0 {
// following line must be blank
return 0
}
@ -774,7 +774,7 @@ func (p *Parser) htmlFindEnd(tag string, data []byte) int {
return i + skip
}
func (*Parser) isEmpty(data []byte) int {
func IsEmpty(data []byte) int {
// it is okay to call isEmpty on an empty buffer
if len(data) == 0 {
return 0
@ -790,7 +790,7 @@ func (*Parser) isEmpty(data []byte) int {
return i
}
func (*Parser) isHRule(data []byte) bool {
func isHRule(data []byte) bool {
i := 0
// skip up to three spaces
@ -976,7 +976,7 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
codeBlock.Content = work.Bytes() // TODO: get rid of temp buffer
if p.extensions&Mmark == 0 {
p.addBlock(codeBlock)
p.AddBlock(codeBlock)
finalizeCodeBlock(codeBlock)
return beg
}
@ -988,12 +988,12 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
figure.HeadingID = id
p.Inline(caption, captionContent)
p.addBlock(figure)
p.AddBlock(figure)
codeBlock.AsLeaf().Attribute = figure.AsContainer().Attribute
p.addChild(codeBlock)
finalizeCodeBlock(codeBlock)
p.addChild(caption)
p.finalize(figure)
p.Finalize(figure)
beg += consumed
@ -1001,7 +1001,7 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
}
// Still here, normal block
p.addBlock(codeBlock)
p.AddBlock(codeBlock)
finalizeCodeBlock(codeBlock)
}
@ -1055,13 +1055,13 @@ func (p *Parser) quotePrefix(data []byte) int {
// blockquote ends with at least one blank line
// followed by something without a blockquote prefix
func (p *Parser) terminateBlockquote(data []byte, beg, end int) bool {
if p.isEmpty(data[beg:]) <= 0 {
if IsEmpty(data[beg:]) <= 0 {
return false
}
if end >= len(data) {
return true
}
return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0
return p.quotePrefix(data[end:]) == 0 && IsEmpty(data[end:]) == 0
}
// parse a blockquote fragment
@ -1096,9 +1096,9 @@ func (p *Parser) quote(data []byte) int {
}
if p.extensions&Mmark == 0 {
block := p.addBlock(&ast.BlockQuote{})
p.block(raw.Bytes())
p.finalize(block)
block := p.AddBlock(&ast.BlockQuote{})
p.Block(raw.Bytes())
p.Finalize(block)
return end
}
@ -1108,24 +1108,24 @@ func (p *Parser) quote(data []byte) int {
figure.HeadingID = id
p.Inline(caption, captionContent)
p.addBlock(figure) // this discard any attributes
p.AddBlock(figure) // this discard any attributes
block := &ast.BlockQuote{}
block.AsContainer().Attribute = figure.AsContainer().Attribute
p.addChild(block)
p.block(raw.Bytes())
p.finalize(block)
p.Block(raw.Bytes())
p.Finalize(block)
p.addChild(caption)
p.finalize(figure)
p.Finalize(figure)
end += consumed
return end
}
block := p.addBlock(&ast.BlockQuote{})
p.block(raw.Bytes())
p.finalize(block)
block := p.AddBlock(&ast.BlockQuote{})
p.Block(raw.Bytes())
p.Finalize(block)
return end
}
@ -1152,7 +1152,7 @@ func (p *Parser) code(data []byte) int {
i = skipUntilChar(data, i, '\n')
i = skipCharN(data, i, '\n', 1)
blankline := p.isEmpty(data[beg:i]) > 0
blankline := IsEmpty(data[beg:i]) > 0
if pre := p.codePrefix(data[beg:i]); pre > 0 {
beg += pre
} else if !blankline {
@ -1185,7 +1185,7 @@ func (p *Parser) code(data []byte) int {
}
// TODO: get rid of temp buffer
codeBlock.Content = work.Bytes()
p.addBlock(codeBlock)
p.AddBlock(codeBlock)
finalizeCodeBlock(codeBlock)
return i
@ -1237,10 +1237,29 @@ func (p *Parser) dliPrefix(data []byte) int {
if data[0] != ':' || !(data[1] == ' ' || data[1] == '\t') {
return 0
}
// TODO: this is a no-op (data[0] is ':' so not ' ').
// Maybe the intent was to eat spaces before ':' ?
// either way, no change in tests
i := skipChar(data, 0, ' ')
return i + 2
}
// TODO: maybe it was meant to be like below
// either way, no change in tests
/*
func (p *Parser) dliPrefix(data []byte) int {
i := skipChar(data, 0, ' ')
if i+len(data) < 2 {
return 0
}
// need a ':' followed by a space or a tab
if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') {
return 0
}
return i + 2
}
*/
// parse ordered or unordered list block
func (p *Parser) list(data []byte, flags ast.ListType, start int, delim byte) int {
i := 0
@ -1251,7 +1270,7 @@ func (p *Parser) list(data []byte, flags ast.ListType, start int, delim byte) in
Start: start,
Delimiter: delim,
}
block := p.addBlock(list)
block := p.AddBlock(list)
for i < len(data) {
skip := p.listItem(data[i:], &flags)
@ -1398,7 +1417,7 @@ gatherlines:
// if it is an empty line, guess that it is part of this item
// and move on to the next line
if p.isEmpty(data[line:i]) > 0 {
if IsEmpty(data[line:i]) > 0 {
containsBlankLine = true
line = i
continue
@ -1432,7 +1451,7 @@ gatherlines:
// evaluate how this line fits in
switch {
// is this a nested list item?
case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) || p.oliPrefix(chunk) > 0 || p.dliPrefix(chunk) > 0:
case (p.uliPrefix(chunk) > 0 && !isHRule(chunk)) || p.oliPrefix(chunk) > 0 || p.dliPrefix(chunk) > 0:
// if indent is 4 or more spaces on unordered or ordered lists
// we need to add leadingWhiteSpaces + 1 spaces in the beginning of the chunk
@ -1484,10 +1503,7 @@ gatherlines:
case containsBlankLine && indent < 4:
if *flags&ast.ListTypeDefinition != 0 && i < len(data)-1 {
// is the next item still a part of this list?
next := i
for next < len(data) && data[next] != '\n' {
next++
}
next := skipUntilChar(data, i, '\n')
for next < len(data)-1 && data[next] == '\n' {
next++
}
@ -1526,16 +1542,16 @@ gatherlines:
BulletChar: bulletChar,
Delimiter: delimiter,
}
p.addBlock(listItem)
p.AddBlock(listItem)
// render the contents of the list item
if *flags&ast.ListItemContainsBlock != 0 && *flags&ast.ListTypeTerm == 0 {
// intermediate render of block item, except for definition term
if sublist > 0 {
p.block(rawBytes[:sublist])
p.block(rawBytes[sublist:])
p.Block(rawBytes[:sublist])
p.Block(rawBytes[sublist:])
} else {
p.block(rawBytes)
p.Block(rawBytes)
}
} else {
// intermediate render of inline item
@ -1547,7 +1563,7 @@ gatherlines:
}
p.addChild(para)
if sublist > 0 {
p.block(rawBytes[sublist:])
p.Block(rawBytes[sublist:])
}
}
return line
@ -1574,7 +1590,7 @@ func (p *Parser) renderParagraph(data []byte) {
}
para := &ast.Paragraph{}
para.Content = data[beg:end]
p.addBlock(para)
p.AddBlock(para)
}
// blockMath handle block surround with $$
@ -1596,7 +1612,7 @@ func (p *Parser) blockMath(data []byte) int {
// render the display math
mathBlock := &ast.MathBlock{}
mathBlock.Literal = data[2:end]
p.addBlock(mathBlock)
p.AddBlock(mathBlock)
return end + 2
}
@ -1626,7 +1642,7 @@ func (p *Parser) paragraph(data []byte) int {
}
// did we find a blank line marking the end of the paragraph?
if n := p.isEmpty(current); n > 0 {
if n := IsEmpty(current); n > 0 {
// did this blank line followed by a definition list item?
if p.extensions&DefinitionLists != 0 {
if i < len(data)-1 && data[i+1] == ':' {
@ -1663,7 +1679,7 @@ func (p *Parser) paragraph(data []byte) int {
}
block.Content = data[prev:eol]
p.addBlock(block)
p.AddBlock(block)
// find the end of the underline
return skipUntilChar(data, i, '\n')
@ -1680,7 +1696,7 @@ func (p *Parser) paragraph(data []byte) int {
}
// if there's a prefixed heading or a horizontal rule after this, paragraph is over
if p.isPrefixHeading(current) || p.isPrefixSpecialHeading(current) || p.isHRule(current) {
if p.isPrefixHeading(current) || p.isPrefixSpecialHeading(current) || isHRule(current) {
p.renderParagraph(data[:i])
return i
}

View File

@ -12,7 +12,7 @@ func isBackslashEscaped(data []byte, i int) bool {
}
func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool) {
p.addBlock(&ast.TableRow{})
p.AddBlock(&ast.TableRow{})
col := 0
i := skipChar(data, 0, '|')
@ -61,7 +61,7 @@ func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool
// an empty cell that we should ignore, it exists because of colspan
colspans--
} else {
p.addBlock(block)
p.AddBlock(block)
}
if colspan > 0 {
@ -75,7 +75,7 @@ func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool
IsHeader: header,
Align: columns[col],
}
p.addBlock(block)
p.AddBlock(block)
}
// silently ignore rows with too many cells
@ -109,7 +109,7 @@ func (p *Parser) tableFooter(data []byte) bool {
return false
}
p.addBlock(&ast.TableFooter{})
p.AddBlock(&ast.TableFooter{})
return true
}
@ -217,7 +217,7 @@ func (p *Parser) tableHeader(data []byte, doRender bool) (size int, columns []as
}
// end of column test is messy
switch {
case dashes < 3:
case dashes < 1:
// not a valid column
return
@ -253,9 +253,9 @@ func (p *Parser) tableHeader(data []byte, doRender bool) (size int, columns []as
if doRender {
table = &ast.Table{}
p.addBlock(table)
p.AddBlock(table)
if header != nil {
p.addBlock(&ast.TableHeader{})
p.AddBlock(&ast.TableHeader{})
p.tableRow(header, columns, true)
}
}
@ -277,7 +277,7 @@ func (p *Parser) table(data []byte) int {
return 0
}
p.addBlock(&ast.TableBody{})
p.AddBlock(&ast.TableBody{})
for i < len(data) {
pipes, rowStart := 0, i
@ -319,7 +319,7 @@ func (p *Parser) table(data []byte) int {
ast.AppendChild(figure, caption)
p.addChild(figure)
p.finalize(figure)
p.Finalize(figure)
i += consumed
}

View File

@ -11,7 +11,7 @@ func (p *Parser) caption(data, caption []byte) ([]byte, string, int) {
}
j := len(caption)
data = data[j:]
end := p.linesUntilEmpty(data)
end := LinesUntilEmpty(data)
data = data[:end]
@ -23,8 +23,8 @@ func (p *Parser) caption(data, caption []byte) ([]byte, string, int) {
return data, "", end + j
}
// linesUntilEmpty scans lines up to the first empty line.
func (p *Parser) linesUntilEmpty(data []byte) int {
// LinesUntilEmpty scans lines up to the first empty line.
func LinesUntilEmpty(data []byte) int {
line, i := 0, 0
for line < len(data) {
@ -35,7 +35,7 @@ func (p *Parser) linesUntilEmpty(data []byte) int {
i++
}
if p.isEmpty(data[line:i]) == 0 {
if IsEmpty(data[line:i]) == 0 {
line = i
continue
}

View File

@ -98,10 +98,10 @@ func (p *Parser) figureBlock(data []byte, doRender bool) int {
}
figure := &ast.CaptionFigure{}
p.addBlock(figure)
p.block(raw.Bytes())
p.AddBlock(figure)
p.Block(raw.Bytes())
defer p.finalize(figure)
defer p.Finalize(figure)
if captionContent, id, consumed := p.caption(data[beg:], []byte("Figure: ")); consumed > 0 {
caption := &ast.Caption{}
@ -113,7 +113,5 @@ func (p *Parser) figureBlock(data []byte, doRender bool) int {
beg += consumed
}
p.finalize(figure)
return beg
}

View File

@ -29,8 +29,8 @@ func (p *Parser) documentMatter(data []byte) int {
return 0
}
node := &ast.DocumentMatter{Matter: matter}
p.addBlock(node)
p.finalize(node)
p.AddBlock(node)
p.Finalize(node)
return consumed
}

View File

@ -42,7 +42,7 @@ const (
SuperSubscript // Super- and subscript support: 2^10^, H~2~O.
EmptyLinesBreakList // 2 empty lines break out of list
Includes // Support including other files.
Mmark // Support Mmark syntax, see https://mmark.nl/syntax
Mmark // Support Mmark syntax, see https://mmark.miek.nl/post/syntax/
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
@ -206,13 +206,13 @@ func (p *Parser) isFootnote(ref *reference) bool {
return ok
}
func (p *Parser) finalize(block ast.Node) {
func (p *Parser) Finalize(block ast.Node) {
p.tip = block.GetParent()
}
func (p *Parser) addChild(node ast.Node) ast.Node {
for !canNodeContain(p.tip, node) {
p.finalize(p.tip)
p.Finalize(p.tip)
}
ast.AppendChild(p.tip, node)
p.tip = node
@ -239,6 +239,18 @@ func canNodeContain(n ast.Node, v ast.Node) bool {
_, ok := v.(*ast.TableCell)
return ok
}
// for nodes implemented outside of ast package, allow them
// to implement this logic via CanContain interface
if o, ok := n.(ast.CanContain); ok {
return o.CanContain(v)
}
// for container nodes outside of ast package default to true
// because false is a bad default
typ := fmt.Sprintf("%T", n)
customNode := !strings.HasPrefix(typ, "*ast.")
if customNode {
return n.AsLeaf() == nil
}
return false
}
@ -248,7 +260,7 @@ func (p *Parser) closeUnmatchedBlocks() {
}
for p.oldTip != p.lastMatchedContainer {
parent := p.oldTip.GetParent()
p.finalize(p.oldTip)
p.Finalize(p.oldTip)
p.oldTip = parent
}
p.allClosed = true
@ -273,10 +285,14 @@ type Reference struct {
// You can then convert AST to html using html.Renderer, to some other format
// using a custom renderer or transform the tree.
func (p *Parser) Parse(input []byte) ast.Node {
p.block(input)
// the code only works with Unix CR newlines so to make life easy for
// callers normalize newlines
input = NormalizeNewlines(input)
p.Block(input)
// Walk the tree and finish up some of unfinished blocks
for p.tip != nil {
p.finalize(p.tip)
p.Finalize(p.tip)
}
// Walk the tree again and process inline markdown in each block
ast.WalkFunc(p.Doc, func(node ast.Node, entering bool) ast.WalkStatus {
@ -322,8 +338,8 @@ func (p *Parser) parseRefsToAST() {
IsFootnotesList: true,
ListFlags: ast.ListTypeOrdered,
}
p.addBlock(&ast.Footnotes{})
block := p.addBlock(list)
p.AddBlock(&ast.Footnotes{})
block := p.AddBlock(list)
flags := ast.ListItemBeginningOfList
// Note: this loop is intentionally explicit, not range-form. This is
// because the body of the loop will append nested footnotes to p.notes and
@ -338,7 +354,7 @@ func (p *Parser) parseRefsToAST() {
listItem.RefLink = ref.link
if ref.hasBlock {
flags |= ast.ListItemContainsBlock
p.block(ref.title)
p.Block(ref.title)
} else {
p.Inline(block, ref.title)
}
@ -660,7 +676,7 @@ gatherLines:
// if it is an empty line, guess that it is part of this item
// and move on to the next line
if p.isEmpty(data[blockEnd:i]) > 0 {
if IsEmpty(data[blockEnd:i]) > 0 {
containsBlankLine = true
blockEnd = i
continue
@ -883,3 +899,26 @@ func isListItem(d ast.Node) bool {
_, ok := d.(*ast.ListItem)
return ok
}
func NormalizeNewlines(d []byte) []byte {
wi := 0
n := len(d)
for i := 0; i < n; i++ {
c := d[i]
// 13 is CR
if c != 13 {
d[wi] = c
wi++
continue
}
// replace CR (mac / win) with LF (unix)
d[wi] = 10
wi++
if i < n-1 && d[i+1] == 10 {
// this was CRLF, so skip the LF
i++
}
}
return d[:wi]
}

View File

@ -1,7 +0,0 @@
# Things to do
[ ] docs: add examples like https://godoc.org/github.com/dgrijalva/jwt-go (put in foo_example_test.go). Or see https://github.com/garyburd/redigo/blob/master/redis/zpop_example_test.go#L5 / https://godoc.org/github.com/garyburd/redigo/redis or https://godoc.org/github.com/go-redis/redis
[ ] figure out expandTabs and parser.TabSizeEight. Are those used?
[ ] SoftbreakData is not used