diff --git a/go.mod b/go.mod
index 139c13582..daf27b575 100644
--- a/go.mod
+++ b/go.mod
@@ -35,7 +35,6 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/oklog/ulid v1.3.1
github.com/robfig/cron/v3 v3.0.1
- github.com/russross/blackfriday/v2 v2.1.0
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.14.0
github.com/stretchr/testify v1.8.1
@@ -48,6 +47,7 @@ require (
github.com/uptrace/bun/dialect/pgdialect v1.1.9
github.com/uptrace/bun/dialect/sqlitedialect v1.1.9
github.com/wagslane/go-password-validator v0.3.0
+ github.com/yuin/goldmark v1.5.3
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
golang.org/x/image v0.2.0
diff --git a/go.sum b/go.sum
index cbcbafdb1..420e15b57 100644
--- a/go.sum
+++ b/go.sum
@@ -521,7 +521,6 @@ github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
-github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
@@ -645,6 +644,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M=
+github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.1/go.mod h1:TSQ0KjMH+pht+bRyvVooJ1rBpvvngSGaPISafq9MxJk=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
diff --git a/internal/api/client/status/statuscreate_test.go b/internal/api/client/status/statuscreate_test.go
index 9b570ba18..c143489f3 100644
--- a/internal/api/client/status/statuscreate_test.go
+++ b/internal/api/client/status/statuscreate_test.go
@@ -44,7 +44,7 @@ type StatusCreateTestSuite struct {
const (
statusWithLinksAndTags = "#test alright, should be able to post #links with fragments in them now, let's see........\n\nhttps://docs.gotosocial.org/en/latest/user_guide/posts/#links\n\n#gotosocial\n\n(tobi remember to pull the docker image challenge)"
statusMarkdown = "# Title\n\n## Smaller title\n\nThis is a post written in [markdown](https://www.markdownguide.org/)\n\n"
- statusMarkdownExpected = "
Title
Smaller title
This is a post written in markdown
"
+ statusMarkdownExpected = "Title
Smaller title
This is a post written in markdown
"
)
// Post a new status with some custom visibility settings
diff --git a/internal/text/markdown.go b/internal/text/markdown.go
index 837f213e1..56edf2d01 100644
--- a/internal/text/markdown.go
+++ b/internal/text/markdown.go
@@ -21,62 +21,23 @@
import (
"bytes"
"context"
- "io"
"strings"
- "github.com/russross/blackfriday/v2"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/tdewolff/minify/v2"
- "github.com/tdewolff/minify/v2/html"
+ minifyHtml "github.com/tdewolff/minify/v2/html"
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/extension"
+ "github.com/yuin/goldmark/renderer/html"
)
var (
- bfExtensions = blackfriday.NoIntraEmphasis | blackfriday.FencedCode | blackfriday.Autolink | blackfriday.Strikethrough | blackfriday.SpaceHeadings | blackfriday.HardLineBreak
- m *minify.M
+ m *minify.M
)
-type renderer struct {
- f *formatter
- ctx context.Context
- mentions []*gtsmodel.Mention
- tags []*gtsmodel.Tag
- blackfriday.HTMLRenderer
-}
-
-func (r *renderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
- if node.Type == blackfriday.Text {
- // call RenderNode to do the html escaping
- var buff bytes.Buffer
- status := r.HTMLRenderer.RenderNode(&buff, node, entering)
-
- html := buff.String()
- html = r.f.ReplaceTags(r.ctx, html, r.tags)
- html = r.f.ReplaceMentions(r.ctx, html, r.mentions)
-
- // we don't have much recourse if this fails
- if _, err := io.WriteString(w, html); err != nil {
- log.Errorf("error outputting markdown text: %s", err)
- }
- return status
- }
- return r.HTMLRenderer.RenderNode(w, node, entering)
-}
-
func (f *formatter) FromMarkdown(ctx context.Context, markdownText string, mentions []*gtsmodel.Mention, tags []*gtsmodel.Tag, emojis []*gtsmodel.Emoji) string {
- renderer := &renderer{
- f: f,
- ctx: ctx,
- mentions: mentions,
- tags: tags,
- HTMLRenderer: *blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
- // same as blackfriday.CommonHTMLFlags, but with Smartypants disabled
- // ref: https://github.com/superseriousbusiness/gotosocial/issues/1028
- Flags: blackfriday.UseXHTML,
- }),
- }
-
// Temporarily replace all found emoji shortcodes in the markdown text with
// their ID so that they're not parsed as anything by the markdown parser -
// this fixes cases where emojis with some underscores in them are parsed as
@@ -89,8 +50,25 @@ func (f *formatter) FromMarkdown(ctx context.Context, markdownText string, menti
}
// parse markdown text into html, using custom renderer to add hashtag/mention links
- htmlContentBytes := blackfriday.Run([]byte(markdownText), blackfriday.WithExtensions(bfExtensions), blackfriday.WithRenderer(renderer))
- htmlContent := string(htmlContentBytes)
+ md := goldmark.New(
+ goldmark.WithRendererOptions(
+ html.WithXHTML(),
+ html.WithHardWraps(),
+ html.WithUnsafe(), // allows raw HTML
+ ),
+ goldmark.WithExtensions(
+ &customRenderer{f, ctx, mentions, tags},
+ extension.Linkify, // turns URLs into links
+ extension.Strikethrough,
+ ),
+ )
+
+ var htmlContentBytes bytes.Buffer
+ err := md.Convert([]byte(markdownText), &htmlContentBytes)
+ if err != nil {
+ log.Errorf("error rendering markdown to HTML: %s", err)
+ }
+ htmlContent := htmlContentBytes.String()
// Replace emoji IDs in the parsed html content with their shortcodes again
for _, e := range emojis {
@@ -102,7 +80,7 @@ func (f *formatter) FromMarkdown(ctx context.Context, markdownText string, menti
if m == nil {
m = minify.New()
- m.Add("text/html", &html.Minifier{
+ m.Add("text/html", &minifyHtml.Minifier{
KeepEndTags: true,
KeepQuotes: true,
})
diff --git a/internal/text/markdown_test.go b/internal/text/markdown_test.go
index 6b7c1032f..3a67218d4 100644
--- a/internal/text/markdown_test.go
+++ b/internal/text/markdown_test.go
@@ -71,6 +71,12 @@
mdMentionAndCodeBlockExpected = "@the_mighty_zork
@the_mighty_zork\n
"
mdWithSmartypants = "\"you have to quargle the bleepflorp\" they said with 1/2 of nominal speed and 1/3 of the usual glumping"
mdWithSmartypantsExpected = "\"you have to quargle the bleepflorp\" they said with 1/2 of nominal speed and 1/3 of the usual glumping
"
+ mdWithAsciiHeart = "hello <3 old friend <3 i loved u 3 :(( you stole my heart"
+ mdWithAsciiHeartExpected = "hello <3 old friend <3 i loved u </3 :(( you stole my heart
"
+ mdWithStrikethrough = "I have ~~mdae~~ made an error"
+ mdWithStrikethroughExpected = "I have mdae made an error
"
+ mdWithLink = "Check out this code, i heard it was written by a sloth https://github.com/superseriousbusiness/gotosocial"
+ mdWithLinkExpected = "Check out this code, i heard it was written by a sloth https://github.com/superseriousbusiness/gotosocial
"
)
type MarkdownTestSuite struct {
@@ -160,6 +166,21 @@ func (suite *MarkdownTestSuite) TestParseSmartypants() {
suite.Equal(mdWithSmartypantsExpected, s)
}
+func (suite *MarkdownTestSuite) TestParseAsciiHeart() {
+ s := suite.formatter.FromMarkdown(context.Background(), mdWithAsciiHeart, nil, nil, nil)
+ suite.Equal(mdWithAsciiHeartExpected, s)
+}
+
+func (suite *MarkdownTestSuite) TestParseStrikethrough() {
+ s := suite.formatter.FromMarkdown(context.Background(), mdWithStrikethrough, nil, nil, nil)
+ suite.Equal(mdWithStrikethroughExpected, s)
+}
+
+func (suite *MarkdownTestSuite) TestParseLink() {
+ s := suite.formatter.FromMarkdown(context.Background(), mdWithLink, nil, nil, nil)
+ suite.Equal(mdWithLinkExpected, s)
+}
+
func TestMarkdownTestSuite(t *testing.T) {
suite.Run(t, new(MarkdownTestSuite))
}
diff --git a/internal/text/markdownextension.go b/internal/text/markdownextension.go
new file mode 100644
index 000000000..1be1e99d6
--- /dev/null
+++ b/internal/text/markdownextension.go
@@ -0,0 +1,215 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package text
+
+import (
+ "context"
+ "unicode"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/regexes"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/text"
+ mdutil "github.com/yuin/goldmark/util"
+)
+
+// A goldmark extension that parses potential mentions and hashtags separately from regular
+// text, so that they stay as one contiguous text fragment in the AST, and then renders
+// them separately too, to avoid scanning normal text for mentions and tags.
+
+// mention and hashtag fulfil the goldmark ast.Node interface.
+type mention struct {
+ ast.BaseInline
+ Segment text.Segment
+}
+
+type hashtag struct {
+ ast.BaseInline
+ Segment text.Segment
+}
+
+var kindMention = ast.NewNodeKind("Mention")
+var kindHashtag = ast.NewNodeKind("Hashtag")
+
+func (n *mention) Kind() ast.NodeKind {
+ return kindMention
+}
+
+func (n *hashtag) Kind() ast.NodeKind {
+ return kindHashtag
+}
+
+// Dump is used by goldmark for debugging. It is implemented only minimally because
+// it is not used in our code.
+func (n *mention) Dump(source []byte, level int) {
+ ast.DumpHelper(n, source, level, nil, nil)
+}
+
+func (n *hashtag) Dump(source []byte, level int) {
+ ast.DumpHelper(n, source, level, nil, nil)
+}
+
+// newMention and newHashtag create a goldmark ast.Node from a goldmark text.Segment.
+// The contained segment is used in rendering.
+func newMention(s text.Segment) *mention {
+ return &mention{
+ BaseInline: ast.BaseInline{},
+ Segment: s,
+ }
+}
+
+func newHashtag(s text.Segment) *hashtag {
+ return &hashtag{
+ BaseInline: ast.BaseInline{},
+ Segment: s,
+ }
+}
+
+// mentionParser and hashtagParser fulfil the goldmark parser.InlineParser interface.
+type mentionParser struct {
+}
+
+type hashtagParser struct {
+}
+
+func (p *mentionParser) Trigger() []byte {
+ return []byte{'@'}
+}
+
+func (p *hashtagParser) Trigger() []byte {
+ return []byte{'#'}
+}
+
+func (p *mentionParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
+ before := block.PrecendingCharacter()
+ line, segment := block.PeekLine()
+
+ if !unicode.IsSpace(before) {
+ return nil
+ }
+
+ // unideal for performance but makes use of existing regex
+ loc := regexes.MentionFinder.FindIndex(line)
+ switch {
+ case loc == nil:
+ fallthrough
+ case loc[0] != 0: // fail if not found at start
+ return nil
+ default:
+ block.Advance(loc[1])
+ return newMention(segment.WithStop(segment.Start + loc[1]))
+ }
+}
+
+func (p *hashtagParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
+ before := block.PrecendingCharacter()
+ line, segment := block.PeekLine()
+ s := string(line)
+
+ if !util.IsHashtagBoundary(before) {
+ return nil
+ }
+
+ for i, r := range s {
+ switch {
+ case r == '#' && i == 0:
+ continue
+ case !util.IsPermittedInHashtag(r) && !util.IsHashtagBoundary(r):
+ // Fake hashtag, don't trust it
+ return nil
+ case util.IsHashtagBoundary(r):
+ // End of hashtag
+ block.Advance(i)
+ return newHashtag(segment.WithStop(segment.Start + i))
+ }
+ }
+ // If we don't find invalid characters before the end of the line then it's good
+ block.Advance(len(s))
+ return newHashtag(segment)
+}
+
+// customRenderer fulfils both the renderer.NodeRenderer and goldmark.Extender interfaces.
+// It is created in FromMarkdown to be used a goldmark extension, and the fields are used
+// when rendering mentions and tags.
+type customRenderer struct {
+ f *formatter
+ ctx context.Context
+ mentions []*gtsmodel.Mention
+ tags []*gtsmodel.Tag
+}
+
+func (r *customRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(kindMention, r.renderMention)
+ reg.Register(kindHashtag, r.renderHashtag)
+}
+
+func (r *customRenderer) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(parser.WithInlineParsers(
+ // 500 is pretty arbitrary here, it was copied from example goldmark extension code.
+ // https://github.com/yuin/goldmark/blob/75d8cce5b78c7e1d5d9c4ca32c1164f0a1e57b53/extension/strikethrough.go#L111
+ mdutil.Prioritized(&mentionParser{}, 500),
+ mdutil.Prioritized(&hashtagParser{}, 500),
+ ))
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ mdutil.Prioritized(r, 500),
+ ))
+}
+
+// renderMention and renderHashtag take a mention or a hashtag ast.Node and render it as HTML.
+func (r *customRenderer) renderMention(w mdutil.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ n, ok := node.(*mention) // this function is only registered for kindMention
+ if !ok {
+ log.Errorf("type assertion failed")
+ }
+ text := string(n.Segment.Value(source))
+
+ html := r.f.ReplaceMentions(r.ctx, text, r.mentions)
+
+ // we don't have much recourse if this fails
+ if _, err := w.WriteString(html); err != nil {
+ log.Errorf("error outputting markdown text: %s", err)
+ }
+ return ast.WalkContinue, nil
+}
+
+func (r *customRenderer) renderHashtag(w mdutil.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ n, ok := node.(*hashtag) // this function is only registered for kindHashtag
+ if !ok {
+ log.Errorf("type assertion failed")
+ }
+ text := string(n.Segment.Value(source))
+
+ html := r.f.ReplaceTags(r.ctx, text, r.tags)
+
+ // we don't have much recourse if this fails
+ if _, err := w.WriteString(html); err != nil {
+ log.Errorf("error outputting markdown text: %s", err)
+ }
+ return ast.WalkContinue, nil
+}
diff --git a/internal/util/statustools.go b/internal/util/statustools.go
index b1fd7968b..7479fbff5 100644
--- a/internal/util/statustools.go
+++ b/internal/util/statustools.go
@@ -78,14 +78,14 @@ func FindHashtagSpansInText(text string) []Span {
inTag := false
for i, r := range text {
- if r == '#' && isHashtagBoundary(prev) {
+ if r == '#' && IsHashtagBoundary(prev) {
// Start of hashtag.
inTag = true
start = i
- } else if inTag && !isPermittedInHashtag(r) && !isHashtagBoundary(r) {
+ } else if inTag && !IsPermittedInHashtag(r) && !IsHashtagBoundary(r) {
// Inside the hashtag, but it was a phoney, gottem.
inTag = false
- } else if inTag && isHashtagBoundary(r) {
+ } else if inTag && IsHashtagBoundary(r) {
// End of hashtag.
inTag = false
appendTag(&tags, text, start, i)
@@ -119,12 +119,12 @@ func DeriveEmojisFromText(text string) []string {
return UniqueStrings(emojis)
}
-func isPermittedInHashtag(r rune) bool {
+func IsPermittedInHashtag(r rune) bool {
return unicode.IsLetter(r) || unicode.IsNumber(r)
}
// Decides where to break before or after a hashtag.
-func isHashtagBoundary(r rune) bool {
+func IsHashtagBoundary(r rune) bool {
return r == '#' || // `###lol` should work
unicode.IsSpace(r) || // All kinds of Unicode whitespace.
unicode.IsControl(r) || // All kinds of control characters, like tab.
diff --git a/vendor/github.com/russross/blackfriday/v2/.gitignore b/vendor/github.com/russross/blackfriday/v2/.gitignore
deleted file mode 100644
index 75623dccc..000000000
--- a/vendor/github.com/russross/blackfriday/v2/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*.out
-*.swp
-*.8
-*.6
-_obj
-_test*
-markdown
-tags
diff --git a/vendor/github.com/russross/blackfriday/v2/.travis.yml b/vendor/github.com/russross/blackfriday/v2/.travis.yml
deleted file mode 100644
index b0b525a5a..000000000
--- a/vendor/github.com/russross/blackfriday/v2/.travis.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-sudo: false
-language: go
-go:
- - "1.10.x"
- - "1.11.x"
- - tip
-matrix:
- fast_finish: true
- allow_failures:
- - go: tip
-install:
- - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
-script:
- - go get -t -v ./...
- - diff -u <(echo -n) <(gofmt -d -s .)
- - go tool vet .
- - go test -v ./...
diff --git a/vendor/github.com/russross/blackfriday/v2/LICENSE.txt b/vendor/github.com/russross/blackfriday/v2/LICENSE.txt
deleted file mode 100644
index 2885af360..000000000
--- a/vendor/github.com/russross/blackfriday/v2/LICENSE.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-Blackfriday is distributed under the Simplified BSD License:
-
-> Copyright © 2011 Russ Ross
-> All rights reserved.
->
-> Redistribution and use in source and binary forms, with or without
-> modification, are permitted provided that the following conditions
-> are met:
->
-> 1. Redistributions of source code must retain the above copyright
-> notice, this list of conditions and the following disclaimer.
->
-> 2. Redistributions in binary form must reproduce the above
-> copyright notice, this list of conditions and the following
-> disclaimer in the documentation and/or other materials provided with
-> the distribution.
->
-> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-> POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/russross/blackfriday/v2/README.md b/vendor/github.com/russross/blackfriday/v2/README.md
deleted file mode 100644
index d9c08a22f..000000000
--- a/vendor/github.com/russross/blackfriday/v2/README.md
+++ /dev/null
@@ -1,335 +0,0 @@
-Blackfriday
-[![Build Status][BuildV2SVG]][BuildV2URL]
-[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL]
-===========
-
-Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
-is paranoid about its input (so you can safely feed it user-supplied
-data), it is fast, it supports common extensions (tables, smart
-punctuation substitutions, etc.), and it is safe for all utf-8
-(unicode) input.
-
-HTML output is currently supported, along with Smartypants
-extensions.
-
-It started as a translation from C of [Sundown][3].
-
-
-Installation
-------------
-
-Blackfriday is compatible with modern Go releases in module mode.
-With Go installed:
-
- go get github.com/russross/blackfriday/v2
-
-will resolve and add the package to the current development module,
-then build and install it. Alternatively, you can achieve the same
-if you import it in a package:
-
- import "github.com/russross/blackfriday/v2"
-
-and `go get` without parameters.
-
-Legacy GOPATH mode is unsupported.
-
-
-Versions
---------
-
-Currently maintained and recommended version of Blackfriday is `v2`. It's being
-developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
-documentation is available at
-https://pkg.go.dev/github.com/russross/blackfriday/v2.
-
-It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`.
-
-Version 2 offers a number of improvements over v1:
-
-* Cleaned up API
-* A separate call to [`Parse`][4], which produces an abstract syntax tree for
- the document
-* Latest bug fixes
-* Flexibility to easily add your own rendering extensions
-
-Potential drawbacks:
-
-* Our benchmarks show v2 to be slightly slower than v1. Currently in the
- ballpark of around 15%.
-* API breakage. If you can't afford modifying your code to adhere to the new API
- and don't care too much about the new features, v2 is probably not for you.
-* Several bug fixes are trailing behind and still need to be forward-ported to
- v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
- tracking.
-
-If you are still interested in the legacy `v1`, you can import it from
-`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found
-here: https://pkg.go.dev/github.com/russross/blackfriday.
-
-
-Usage
------
-
-For the most sensible markdown processing, it is as simple as getting your input
-into a byte slice and calling:
-
-```go
-output := blackfriday.Run(input)
-```
-
-Your input will be parsed and the output rendered with a set of most popular
-extensions enabled. If you want the most basic feature set, corresponding with
-the bare Markdown specification, use:
-
-```go
-output := blackfriday.Run(input, blackfriday.WithNoExtensions())
-```
-
-### Sanitize untrusted content
-
-Blackfriday itself does nothing to protect against malicious content. If you are
-dealing with user-supplied markdown, we recommend running Blackfriday's output
-through HTML sanitizer such as [Bluemonday][5].
-
-Here's an example of simple usage of Blackfriday together with Bluemonday:
-
-```go
-import (
- "github.com/microcosm-cc/bluemonday"
- "github.com/russross/blackfriday/v2"
-)
-
-// ...
-unsafe := blackfriday.Run(input)
-html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
-```
-
-### Custom options
-
-If you want to customize the set of options, use `blackfriday.WithExtensions`,
-`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
-
-### `blackfriday-tool`
-
-You can also check out `blackfriday-tool` for a more complete example
-of how to use it. Download and install it using:
-
- go get github.com/russross/blackfriday-tool
-
-This is a simple command-line tool that allows you to process a
-markdown file using a standalone program. You can also browse the
-source directly on github if you are just looking for some example
-code:
-
-*
-
-Note that if you have not already done so, installing
-`blackfriday-tool` will be sufficient to download and install
-blackfriday in addition to the tool itself. The tool binary will be
-installed in `$GOPATH/bin`. This is a statically-linked binary that
-can be copied to wherever you need it without worrying about
-dependencies and library versions.
-
-### Sanitized anchor names
-
-Blackfriday includes an algorithm for creating sanitized anchor names
-corresponding to a given input text. This algorithm is used to create
-anchors for headings when `AutoHeadingIDs` extension is enabled. The
-algorithm has a specification, so that other packages can create
-compatible anchor names and links to those anchors.
-
-The specification is located at https://pkg.go.dev/github.com/russross/blackfriday/v2#hdr-Sanitized_Anchor_Names.
-
-[`SanitizedAnchorName`](https://pkg.go.dev/github.com/russross/blackfriday/v2#SanitizedAnchorName) exposes this functionality, and can be used to
-create compatible links to the anchor names generated by blackfriday.
-This algorithm is also implemented in a small standalone package at
-[`github.com/shurcooL/sanitized_anchor_name`](https://pkg.go.dev/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
-that want a small package and don't need full functionality of blackfriday.
-
-
-Features
---------
-
-All features of Sundown are supported, including:
-
-* **Compatibility**. The Markdown v1.0.3 test suite passes with
- the `--tidy` option. Without `--tidy`, the differences are
- mostly in whitespace and entity escaping, where blackfriday is
- more consistent and cleaner.
-
-* **Common extensions**, including table support, fenced code
- blocks, autolinks, strikethroughs, non-strict emphasis, etc.
-
-* **Safety**. Blackfriday is paranoid when parsing, making it safe
- to feed untrusted user input without fear of bad things
- happening. The test suite stress tests this and there are no
- known inputs that make it crash. If you find one, please let me
- know and send me the input that does it.
-
- NOTE: "safety" in this context means *runtime safety only*. In order to
- protect yourself against JavaScript injection in untrusted content, see
- [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
-
-* **Fast processing**. It is fast enough to render on-demand in
- most web applications without having to cache the output.
-
-* **Thread safety**. You can run multiple parsers in different
- goroutines without ill effect. There is no dependence on global
- shared state.
-
-* **Minimal dependencies**. Blackfriday only depends on standard
- library packages in Go. The source code is pretty
- self-contained, so it is easy to add to any project, including
- Google App Engine projects.
-
-* **Standards compliant**. Output successfully validates using the
- W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
-
-
-Extensions
-----------
-
-In addition to the standard markdown syntax, this package
-implements the following extensions:
-
-* **Intra-word emphasis supression**. The `_` character is
- commonly used inside words when discussing code, so having
- markdown interpret it as an emphasis command is usually the
- wrong thing. Blackfriday lets you treat all emphasis markers as
- normal characters when they occur inside a word.
-
-* **Tables**. Tables can be created by drawing them in the input
- using a simple syntax:
-
- ```
- Name | Age
- --------|------
- Bob | 27
- Alice | 23
- ```
-
-* **Fenced code blocks**. In addition to the normal 4-space
- indentation to mark code blocks, you can explicitly mark them
- and supply a language (to make syntax highlighting simple). Just
- mark it like this:
-
- ```go
- func getTrue() bool {
- return true
- }
- ```
-
- You can use 3 or more backticks to mark the beginning of the
- block, and the same number to mark the end of the block.
-
- To preserve classes of fenced code blocks while using the bluemonday
- HTML sanitizer, use the following policy:
-
- ```go
- p := bluemonday.UGCPolicy()
- p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
- html := p.SanitizeBytes(unsafe)
- ```
-
-* **Definition lists**. A simple definition list is made of a single-line
- term followed by a colon and the definition for that term.
-
- Cat
- : Fluffy animal everyone likes
-
- Internet
- : Vector of transmission for pictures of cats
-
- Terms must be separated from the previous definition by a blank line.
-
-* **Footnotes**. A marker in the text that will become a superscript number;
- a footnote definition that will be placed in a list of footnotes at the
- end of the document. A footnote looks like this:
-
- This is a footnote.[^1]
-
- [^1]: the footnote text.
-
-* **Autolinking**. Blackfriday can find URLs that have not been
- explicitly marked as links and turn them into links.
-
-* **Strikethrough**. Use two tildes (`~~`) to mark text that
- should be crossed out.
-
-* **Hard line breaks**. With this extension enabled newlines in the input
- translate into line breaks in the output. This extension is off by default.
-
-* **Smart quotes**. Smartypants-style punctuation substitution is
- supported, turning normal double- and single-quote marks into
- curly quotes, etc.
-
-* **LaTeX-style dash parsing** is an additional option, where `--`
- is translated into `–`, and `---` is translated into
- `—`. This differs from most smartypants processors, which
- turn a single hyphen into an ndash and a double hyphen into an
- mdash.
-
-* **Smart fractions**, where anything that looks like a fraction
- is translated into suitable HTML (instead of just a few special
- cases like most smartypant processors). For example, `4/5`
- becomes `4⁄5`, which renders as
- 4⁄5.
-
-
-Other renderers
----------------
-
-Blackfriday is structured to allow alternative rendering engines. Here
-are a few of note:
-
-* [github_flavored_markdown](https://pkg.go.dev/github.com/shurcooL/github_flavored_markdown):
- provides a GitHub Flavored Markdown renderer with fenced code block
- highlighting, clickable heading anchor links.
-
- It's not customizable, and its goal is to produce HTML output
- equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
- except the rendering is performed locally.
-
-* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
- but for markdown.
-
-* [LaTeX output](https://gitlab.com/ambrevar/blackfriday-latex):
- renders output as LaTeX.
-
-* [bfchroma](https://github.com/Depado/bfchroma/): provides convenience
- integration with the [Chroma](https://github.com/alecthomas/chroma) code
- highlighting library. bfchroma is only compatible with v2 of Blackfriday and
- provides a drop-in renderer ready to use with Blackfriday, as well as
- options and means for further customization.
-
-* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
-
-* [Blackfriday-Slack](https://github.com/karriereat/blackfriday-slack): converts markdown to slack message style
-
-
-TODO
-----
-
-* 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.
-
-
-License
--------
-
-[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
-
-
- [1]: https://daringfireball.net/projects/markdown/ "Markdown"
- [2]: https://golang.org/ "Go Language"
- [3]: https://github.com/vmg/sundown "Sundown"
- [4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func"
- [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
-
- [BuildV2SVG]: https://travis-ci.org/russross/blackfriday.svg?branch=v2
- [BuildV2URL]: https://travis-ci.org/russross/blackfriday
- [PkgGoDevV2SVG]: https://pkg.go.dev/badge/github.com/russross/blackfriday/v2
- [PkgGoDevV2URL]: https://pkg.go.dev/github.com/russross/blackfriday/v2
diff --git a/vendor/github.com/russross/blackfriday/v2/block.go b/vendor/github.com/russross/blackfriday/v2/block.go
deleted file mode 100644
index dcd61e6e3..000000000
--- a/vendor/github.com/russross/blackfriday/v2/block.go
+++ /dev/null
@@ -1,1612 +0,0 @@
-//
-// Blackfriday Markdown Processor
-// Available at http://github.com/russross/blackfriday
-//
-// Copyright © 2011 Russ Ross .
-// Distributed under the Simplified BSD License.
-// See README.md for details.
-//
-
-//
-// Functions to parse block-level elements.
-//
-
-package blackfriday
-
-import (
- "bytes"
- "html"
- "regexp"
- "strings"
- "unicode"
-)
-
-const (
- charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"
- escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]"
-)
-
-var (
- reBackslashOrAmp = regexp.MustCompile("[\\&]")
- reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
-)
-
-// Parse block-level data.
-// Note: this function and many that it calls assume that
-// the input buffer ends with a newline.
-func (p *Markdown) block(data []byte) {
- // this is called recursively: enforce a maximum depth
- if p.nesting >= p.maxNesting {
- return
- }
- p.nesting++
-
- // parse out one block-level construct at a time
- for len(data) > 0 {
- // prefixed heading:
- //
- // # Heading 1
- // ## Heading 2
- // ...
- // ###### Heading 6
- if p.isPrefixHeading(data) {
- data = data[p.prefixHeading(data):]
- continue
- }
-
- // block of preformatted HTML:
- //
- //
- // ...
- //
- if data[0] == '<' {
- if i := p.html(data, true); i > 0 {
- data = data[i:]
- continue
- }
- }
-
- // title block
- //
- // % stuff
- // % more stuff
- // % even more stuff
- if p.extensions&Titleblock != 0 {
- if data[0] == '%' {
- if i := p.titleBlock(data, true); i > 0 {
- data = data[i:]
- continue
- }
- }
- }
-
- // blank lines. note: returns the # of bytes to skip
- if i := p.isEmpty(data); i > 0 {
- data = data[i:]
- continue
- }
-
- // indented code block:
- //
- // func max(a, b int) int {
- // if a > b {
- // return a
- // }
- // return b
- // }
- if p.codePrefix(data) > 0 {
- data = data[p.code(data):]
- continue
- }
-
- // fenced code block:
- //
- // ``` go
- // func fact(n int) int {
- // if n <= 1 {
- // return n
- // }
- // return n * fact(n-1)
- // }
- // ```
- if p.extensions&FencedCode != 0 {
- if i := p.fencedCodeBlock(data, true); i > 0 {
- data = data[i:]
- continue
- }
- }
-
- // horizontal rule:
- //
- // ------
- // or
- // ******
- // or
- // ______
- if p.isHRule(data) {
- p.addBlock(HorizontalRule, nil)
- var i int
- for i = 0; i < len(data) && data[i] != '\n'; i++ {
- }
- data = data[i:]
- continue
- }
-
- // block quote:
- //
- // > A big quote I found somewhere
- // > on the web
- if p.quotePrefix(data) > 0 {
- data = data[p.quote(data):]
- continue
- }
-
- // table:
- //
- // Name | Age | Phone
- // ------|-----|---------
- // Bob | 31 | 555-1234
- // Alice | 27 | 555-4321
- if p.extensions&Tables != 0 {
- if i := p.table(data); i > 0 {
- data = data[i:]
- continue
- }
- }
-
- // an itemized/unordered list:
- //
- // * Item 1
- // * Item 2
- //
- // also works with + or -
- if p.uliPrefix(data) > 0 {
- data = data[p.list(data, 0):]
- continue
- }
-
- // a numbered/ordered list:
- //
- // 1. Item 1
- // 2. Item 2
- if p.oliPrefix(data) > 0 {
- data = data[p.list(data, ListTypeOrdered):]
- continue
- }
-
- // definition lists:
- //
- // Term 1
- // : Definition a
- // : Definition b
- //
- // Term 2
- // : Definition c
- if p.extensions&DefinitionLists != 0 {
- if p.dliPrefix(data) > 0 {
- data = data[p.list(data, ListTypeDefinition):]
- continue
- }
- }
-
- // anything else must look like a normal paragraph
- // note: this finds underlined headings, too
- data = data[p.paragraph(data):]
- }
-
- p.nesting--
-}
-
-func (p *Markdown) addBlock(typ NodeType, content []byte) *Node {
- p.closeUnmatchedBlocks()
- container := p.addChild(typ, 0)
- container.content = content
- return container
-}
-
-func (p *Markdown) isPrefixHeading(data []byte) bool {
- if data[0] != '#' {
- return false
- }
-
- if p.extensions&SpaceHeadings != 0 {
- level := 0
- for level < 6 && level < len(data) && data[level] == '#' {
- level++
- }
- if level == len(data) || data[level] != ' ' {
- return false
- }
- }
- return true
-}
-
-func (p *Markdown) prefixHeading(data []byte) int {
- level := 0
- for level < 6 && level < len(data) && data[level] == '#' {
- level++
- }
- i := skipChar(data, level, ' ')
- end := skipUntilChar(data, i, '\n')
- skip := end
- id := ""
- if p.extensions&HeadingIDs != 0 {
- j, k := 0, 0
- // find start/end of heading id
- for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
- }
- for k = j + 1; k < end && data[k] != '}'; k++ {
- }
- // extract heading id iff found
- if j < end && k < end {
- id = string(data[j+2 : k])
- end = j
- skip = k + 1
- for end > 0 && data[end-1] == ' ' {
- end--
- }
- }
- }
- for end > 0 && data[end-1] == '#' {
- if isBackslashEscaped(data, end-1) {
- break
- }
- end--
- }
- for end > 0 && data[end-1] == ' ' {
- end--
- }
- if end > i {
- if id == "" && p.extensions&AutoHeadingIDs != 0 {
- id = SanitizedAnchorName(string(data[i:end]))
- }
- block := p.addBlock(Heading, data[i:end])
- block.HeadingID = id
- block.Level = level
- }
- return skip
-}
-
-func (p *Markdown) isUnderlinedHeading(data []byte) int {
- // test of level 1 heading
- if data[0] == '=' {
- i := skipChar(data, 1, '=')
- i = skipChar(data, i, ' ')
- if i < len(data) && data[i] == '\n' {
- return 1
- }
- return 0
- }
-
- // test of level 2 heading
- if data[0] == '-' {
- i := skipChar(data, 1, '-')
- i = skipChar(data, i, ' ')
- if i < len(data) && data[i] == '\n' {
- return 2
- }
- return 0
- }
-
- return 0
-}
-
-func (p *Markdown) titleBlock(data []byte, doRender bool) int {
- if data[0] != '%' {
- return 0
- }
- splitData := bytes.Split(data, []byte("\n"))
- var i int
- for idx, b := range splitData {
- if !bytes.HasPrefix(b, []byte("%")) {
- i = idx // - 1
- break
- }
- }
-
- data = bytes.Join(splitData[0:i], []byte("\n"))
- consumed := len(data)
- data = bytes.TrimPrefix(data, []byte("% "))
- data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
- block := p.addBlock(Heading, data)
- block.Level = 1
- block.IsTitleblock = true
-
- return consumed
-}
-
-func (p *Markdown) html(data []byte, doRender bool) int {
- var i, j int
-
- // identify the opening tag
- if data[0] != '<' {
- return 0
- }
- curtag, tagfound := p.htmlFindTag(data[1:])
-
- // handle special cases
- if !tagfound {
- // check for an HTML comment
- if size := p.htmlComment(data, doRender); size > 0 {
- return size
- }
-
- // check for an
tag
- if size := p.htmlHr(data, doRender); size > 0 {
- return size
- }
-
- // no special case recognized
- return 0
- }
-
- // look for an unindented matching closing tag
- // followed by a blank line
- found := false
- /*
- closetag := []byte("\n" + curtag + ">")
- j = len(curtag) + 1
- for !found {
- // scan for a closing tag at the beginning of a line
- if skip := bytes.Index(data[j:], closetag); skip >= 0 {
- j += skip + len(closetag)
- } else {
- break
- }
-
- // see if it is the only thing on the line
- if skip := p.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 {
- j += skip
- found = true
- i = j
- }
- }
- }
- }
- */
-
- // if not found, try a second pass looking for indented match
- // but not if tag is "ins" or "del" (following original Markdown.pl)
- if !found && curtag != "ins" && curtag != "del" {
- i = 1
- for i < len(data) {
- i++
- for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
- i++
- }
-
- if i+2+len(curtag) >= len(data) {
- break
- }
-
- j = p.htmlFindEnd(curtag, data[i-1:])
-
- if j > 0 {
- i += j - 1
- found = true
- break
- }
- }
- }
-
- if !found {
- return 0
- }
-
- // the end of the block has been found
- if doRender {
- // trim newlines
- end := i
- for end > 0 && data[end-1] == '\n' {
- end--
- }
- finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
- }
-
- return i
-}
-
-func finalizeHTMLBlock(block *Node) {
- block.Literal = block.content
- block.content = nil
-}
-
-// HTML comment, lax form
-func (p *Markdown) 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 {
- size := i + j
- if doRender {
- // trim trailing newlines
- end := size
- for end > 0 && data[end-1] == '\n' {
- end--
- }
- block := p.addBlock(HTMLBlock, data[:end])
- finalizeHTMLBlock(block)
- }
- return size
- }
- return 0
-}
-
-// HR, which is the only self-closing block tag considered
-func (p *Markdown) htmlHr(data []byte, doRender bool) int {
- if len(data) < 4 {
- return 0
- }
- if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
- return 0
- }
- if data[3] != ' ' && data[3] != '/' && data[3] != '>' {
- // not an
tag after all; at least not a valid one
- return 0
- }
- i := 3
- for i < len(data) && data[i] != '>' && data[i] != '\n' {
- i++
- }
- if i < len(data) && data[i] == '>' {
- i++
- if j := p.isEmpty(data[i:]); j > 0 {
- size := i + j
- if doRender {
- // trim newlines
- end := size
- for end > 0 && data[end-1] == '\n' {
- end--
- }
- finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
- }
- return size
- }
- }
- return 0
-}
-
-func (p *Markdown) htmlFindTag(data []byte) (string, bool) {
- i := 0
- for i < len(data) && isalnum(data[i]) {
- i++
- }
- key := string(data[:i])
- if _, ok := blockTags[key]; ok {
- return key, true
- }
- return "", false
-}
-
-func (p *Markdown) htmlFindEnd(tag string, data []byte) int {
- // assume data[0] == '<' && data[1] == '/' already tested
- if tag == "hr" {
- return 2
- }
- // check if tag is a match
- closetag := []byte("" + tag + ">")
- if !bytes.HasPrefix(data, closetag) {
- return 0
- }
- i := len(closetag)
-
- // check that the rest of the line is blank
- skip := 0
- if skip = p.isEmpty(data[i:]); skip == 0 {
- return 0
- }
- i += skip
- skip = 0
-
- if i >= len(data) {
- return i
- }
-
- if p.extensions&LaxHTMLBlocks != 0 {
- return i
- }
- if skip = p.isEmpty(data[i:]); skip == 0 {
- // following line must be blank
- return 0
- }
-
- return i + skip
-}
-
-func (*Markdown) isEmpty(data []byte) int {
- // it is okay to call isEmpty on an empty buffer
- if len(data) == 0 {
- return 0
- }
-
- var i int
- for i = 0; i < len(data) && data[i] != '\n'; i++ {
- if data[i] != ' ' && data[i] != '\t' {
- return 0
- }
- }
- if i < len(data) && data[i] == '\n' {
- i++
- }
- return i
-}
-
-func (*Markdown) isHRule(data []byte) bool {
- i := 0
-
- // skip up to three spaces
- for i < 3 && data[i] == ' ' {
- i++
- }
-
- // look at the hrule char
- if data[i] != '*' && data[i] != '-' && data[i] != '_' {
- return false
- }
- c := data[i]
-
- // the whole line must be the char or whitespace
- n := 0
- for i < len(data) && data[i] != '\n' {
- switch {
- case data[i] == c:
- n++
- case data[i] != ' ':
- return false
- }
- i++
- }
-
- return n >= 3
-}
-
-// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
-// and returns the end index if so, or 0 otherwise. It also returns the marker found.
-// If info is not nil, it gets set to the syntax specified in the fence line.
-func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
- i, size := 0, 0
-
- // skip up to three spaces
- for i < len(data) && i < 3 && data[i] == ' ' {
- i++
- }
-
- // check for the marker characters: ~ or `
- if i >= len(data) {
- return 0, ""
- }
- if data[i] != '~' && data[i] != '`' {
- return 0, ""
- }
-
- c := data[i]
-
- // the whole line must be the same char or whitespace
- for i < len(data) && data[i] == c {
- size++
- i++
- }
-
- // the marker char must occur at least 3 times
- if size < 3 {
- return 0, ""
- }
- marker = string(data[i-size : i])
-
- // if this is the end marker, it must match the beginning marker
- if oldmarker != "" && marker != oldmarker {
- return 0, ""
- }
-
- // TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
- // into one, always get the info string, and discard it if the caller doesn't care.
- if info != nil {
- infoLength := 0
- i = skipChar(data, i, ' ')
-
- if i >= len(data) {
- if i == len(data) {
- return i, marker
- }
- return 0, ""
- }
-
- infoStart := i
-
- if data[i] == '{' {
- i++
- infoStart++
-
- for i < len(data) && data[i] != '}' && data[i] != '\n' {
- infoLength++
- i++
- }
-
- if i >= len(data) || data[i] != '}' {
- return 0, ""
- }
-
- // strip all whitespace at the beginning and the end
- // of the {} block
- for infoLength > 0 && isspace(data[infoStart]) {
- infoStart++
- infoLength--
- }
-
- for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
- infoLength--
- }
- i++
- i = skipChar(data, i, ' ')
- } else {
- for i < len(data) && !isverticalspace(data[i]) {
- infoLength++
- i++
- }
- }
-
- *info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
- }
-
- if i == len(data) {
- return i, marker
- }
- if i > len(data) || data[i] != '\n' {
- return 0, ""
- }
- return i + 1, marker // Take newline into account.
-}
-
-// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning,
-// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
-// If doRender is true, a final newline is mandatory to recognize the fenced code block.
-func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
- var info string
- beg, marker := isFenceLine(data, &info, "")
- if beg == 0 || beg >= len(data) {
- return 0
- }
- fenceLength := beg - 1
-
- var work bytes.Buffer
- work.Write([]byte(info))
- work.WriteByte('\n')
-
- for {
- // safe to assume beg < len(data)
-
- // check for the end of the code block
- fenceEnd, _ := isFenceLine(data[beg:], nil, marker)
- if fenceEnd != 0 {
- beg += fenceEnd
- break
- }
-
- // copy the current line
- end := skipUntilChar(data, beg, '\n') + 1
-
- // did we reach the end of the buffer without a closing marker?
- if end >= len(data) {
- return 0
- }
-
- // verbatim copy to the working buffer
- if doRender {
- work.Write(data[beg:end])
- }
- beg = end
- }
-
- if doRender {
- block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
- block.IsFenced = true
- block.FenceLength = fenceLength
- finalizeCodeBlock(block)
- }
-
- return beg
-}
-
-func unescapeChar(str []byte) []byte {
- if str[0] == '\\' {
- return []byte{str[1]}
- }
- return []byte(html.UnescapeString(string(str)))
-}
-
-func unescapeString(str []byte) []byte {
- if reBackslashOrAmp.Match(str) {
- return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar)
- }
- return str
-}
-
-func finalizeCodeBlock(block *Node) {
- if block.IsFenced {
- newlinePos := bytes.IndexByte(block.content, '\n')
- firstLine := block.content[:newlinePos]
- rest := block.content[newlinePos+1:]
- block.Info = unescapeString(bytes.Trim(firstLine, "\n"))
- block.Literal = rest
- } else {
- block.Literal = block.content
- }
- block.content = nil
-}
-
-func (p *Markdown) table(data []byte) int {
- table := p.addBlock(Table, nil)
- i, columns := p.tableHeader(data)
- if i == 0 {
- p.tip = table.Parent
- table.Unlink()
- return 0
- }
-
- p.addBlock(TableBody, nil)
-
- for i < len(data) {
- pipes, rowStart := 0, i
- for ; i < len(data) && data[i] != '\n'; i++ {
- if data[i] == '|' {
- pipes++
- }
- }
-
- if pipes == 0 {
- i = rowStart
- break
- }
-
- // include the newline in data sent to tableRow
- if i < len(data) && data[i] == '\n' {
- i++
- }
- p.tableRow(data[rowStart:i], columns, false)
- }
-
- return i
-}
-
-// check if the specified position is preceded by an odd number of backslashes
-func isBackslashEscaped(data []byte, i int) bool {
- backslashes := 0
- for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
- backslashes++
- }
- return backslashes&1 == 1
-}
-
-func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) {
- i := 0
- colCount := 1
- for i = 0; i < len(data) && data[i] != '\n'; i++ {
- if data[i] == '|' && !isBackslashEscaped(data, i) {
- colCount++
- }
- }
-
- // doesn't look like a table header
- if colCount == 1 {
- return
- }
-
- // include the newline in the data sent to tableRow
- j := i
- if j < len(data) && data[j] == '\n' {
- j++
- }
- header := data[:j]
-
- // column count ignores pipes at beginning or end of line
- if data[0] == '|' {
- colCount--
- }
- if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) {
- colCount--
- }
-
- columns = make([]CellAlignFlags, colCount)
-
- // move on to the header underline
- i++
- if i >= len(data) {
- return
- }
-
- if data[i] == '|' && !isBackslashEscaped(data, i) {
- i++
- }
- i = skipChar(data, i, ' ')
-
- // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
- // and trailing | optional on last column
- col := 0
- for i < len(data) && data[i] != '\n' {
- dashes := 0
-
- if data[i] == ':' {
- i++
- columns[col] |= TableAlignmentLeft
- dashes++
- }
- for i < len(data) && data[i] == '-' {
- i++
- dashes++
- }
- if i < len(data) && data[i] == ':' {
- i++
- columns[col] |= TableAlignmentRight
- dashes++
- }
- for i < len(data) && data[i] == ' ' {
- i++
- }
- if i == len(data) {
- return
- }
- // end of column test is messy
- switch {
- case dashes < 3:
- // not a valid column
- return
-
- case data[i] == '|' && !isBackslashEscaped(data, i):
- // marker found, now skip past trailing whitespace
- col++
- i++
- for i < len(data) && data[i] == ' ' {
- i++
- }
-
- // trailing junk found after last column
- if col >= colCount && i < len(data) && data[i] != '\n' {
- return
- }
-
- case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
- // something else found where marker was required
- return
-
- case data[i] == '\n':
- // marker is optional for the last column
- col++
-
- default:
- // trailing junk found after last column
- return
- }
- }
- if col != colCount {
- return
- }
-
- p.addBlock(TableHead, nil)
- p.tableRow(header, columns, true)
- size = i
- if size < len(data) && data[size] == '\n' {
- size++
- }
- return
-}
-
-func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) {
- p.addBlock(TableRow, nil)
- i, col := 0, 0
-
- if data[i] == '|' && !isBackslashEscaped(data, i) {
- i++
- }
-
- for col = 0; col < len(columns) && i < len(data); col++ {
- for i < len(data) && data[i] == ' ' {
- i++
- }
-
- cellStart := i
-
- for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
- i++
- }
-
- cellEnd := i
-
- // skip the end-of-cell marker, possibly taking us past end of buffer
- i++
-
- for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' {
- cellEnd--
- }
-
- cell := p.addBlock(TableCell, data[cellStart:cellEnd])
- cell.IsHeader = header
- cell.Align = columns[col]
- }
-
- // pad it out with empty columns to get the right number
- for ; col < len(columns); col++ {
- cell := p.addBlock(TableCell, nil)
- cell.IsHeader = header
- cell.Align = columns[col]
- }
-
- // silently ignore rows with too many cells
-}
-
-// returns blockquote prefix length
-func (p *Markdown) quotePrefix(data []byte) int {
- i := 0
- for i < 3 && i < len(data) && data[i] == ' ' {
- i++
- }
- if i < len(data) && data[i] == '>' {
- if i+1 < len(data) && data[i+1] == ' ' {
- return i + 2
- }
- return i + 1
- }
- return 0
-}
-
-// blockquote ends with at least one blank line
-// followed by something without a blockquote prefix
-func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool {
- if p.isEmpty(data[beg:]) <= 0 {
- return false
- }
- if end >= len(data) {
- return true
- }
- return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0
-}
-
-// parse a blockquote fragment
-func (p *Markdown) quote(data []byte) int {
- block := p.addBlock(BlockQuote, nil)
- var raw bytes.Buffer
- beg, end := 0, 0
- for beg < len(data) {
- end = beg
- // Step over whole lines, collecting them. While doing that, check for
- // fenced code and if one's found, incorporate it altogether,
- // irregardless of any contents inside it
- for end < len(data) && data[end] != '\n' {
- if p.extensions&FencedCode != 0 {
- if i := p.fencedCodeBlock(data[end:], false); i > 0 {
- // -1 to compensate for the extra end++ after the loop:
- end += i - 1
- break
- }
- }
- end++
- }
- if end < len(data) && data[end] == '\n' {
- end++
- }
- if pre := p.quotePrefix(data[beg:]); pre > 0 {
- // skip the prefix
- beg += pre
- } else if p.terminateBlockquote(data, beg, end) {
- break
- }
- // this line is part of the blockquote
- raw.Write(data[beg:end])
- beg = end
- }
- p.block(raw.Bytes())
- p.finalize(block)
- return end
-}
-
-// returns prefix length for block code
-func (p *Markdown) codePrefix(data []byte) int {
- if len(data) >= 1 && data[0] == '\t' {
- return 1
- }
- if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
- return 4
- }
- return 0
-}
-
-func (p *Markdown) code(data []byte) int {
- var work bytes.Buffer
-
- i := 0
- for i < len(data) {
- beg := i
- for i < len(data) && data[i] != '\n' {
- i++
- }
- if i < len(data) && data[i] == '\n' {
- i++
- }
-
- blankline := p.isEmpty(data[beg:i]) > 0
- if pre := p.codePrefix(data[beg:i]); pre > 0 {
- beg += pre
- } else if !blankline {
- // non-empty, non-prefixed line breaks the pre
- i = beg
- break
- }
-
- // verbatim copy to the working buffer
- if blankline {
- work.WriteByte('\n')
- } else {
- work.Write(data[beg:i])
- }
- }
-
- // trim all the \n off the end of work
- workbytes := work.Bytes()
- eol := len(workbytes)
- for eol > 0 && workbytes[eol-1] == '\n' {
- eol--
- }
- if eol != len(workbytes) {
- work.Truncate(eol)
- }
-
- work.WriteByte('\n')
-
- block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
- block.IsFenced = false
- finalizeCodeBlock(block)
-
- return i
-}
-
-// returns unordered list item prefix
-func (p *Markdown) uliPrefix(data []byte) int {
- i := 0
- // start with up to 3 spaces
- for i < len(data) && i < 3 && data[i] == ' ' {
- i++
- }
- if i >= len(data)-1 {
- return 0
- }
- // need one of {'*', '+', '-'} followed by a space or a tab
- if (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
- (data[i+1] != ' ' && data[i+1] != '\t') {
- return 0
- }
- return i + 2
-}
-
-// returns ordered list item prefix
-func (p *Markdown) oliPrefix(data []byte) int {
- i := 0
-
- // start with up to 3 spaces
- for i < 3 && i < len(data) && data[i] == ' ' {
- i++
- }
-
- // count the digits
- start := i
- for i < len(data) && data[i] >= '0' && data[i] <= '9' {
- i++
- }
- if start == i || i >= len(data)-1 {
- return 0
- }
-
- // we need >= 1 digits followed by a dot and a space or a tab
- if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') {
- return 0
- }
- return i + 2
-}
-
-// returns definition list item prefix
-func (p *Markdown) dliPrefix(data []byte) int {
- if len(data) < 2 {
- return 0
- }
- i := 0
- // need a ':' followed by a space or a tab
- if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') {
- return 0
- }
- for i < len(data) && data[i] == ' ' {
- i++
- }
- return i + 2
-}
-
-// parse ordered or unordered list block
-func (p *Markdown) list(data []byte, flags ListType) int {
- i := 0
- flags |= ListItemBeginningOfList
- block := p.addBlock(List, nil)
- block.ListFlags = flags
- block.Tight = true
-
- for i < len(data) {
- skip := p.listItem(data[i:], &flags)
- if flags&ListItemContainsBlock != 0 {
- block.ListData.Tight = false
- }
- i += skip
- if skip == 0 || flags&ListItemEndOfList != 0 {
- break
- }
- flags &= ^ListItemBeginningOfList
- }
-
- above := block.Parent
- finalizeList(block)
- p.tip = above
- return i
-}
-
-// Returns true if the list item is not the same type as its parent list
-func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool {
- if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 {
- return true
- } else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 {
- return true
- } else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) {
- return true
- }
- return false
-}
-
-// Returns true if block ends with a blank line, descending if needed
-// into lists and sublists.
-func endsWithBlankLine(block *Node) bool {
- // TODO: figure this out. Always false now.
- for block != nil {
- //if block.lastLineBlank {
- //return true
- //}
- t := block.Type
- if t == List || t == Item {
- block = block.LastChild
- } else {
- break
- }
- }
- return false
-}
-
-func finalizeList(block *Node) {
- block.open = false
- item := block.FirstChild
- for item != nil {
- // check for non-final list item ending with blank line:
- if endsWithBlankLine(item) && item.Next != nil {
- block.ListData.Tight = false
- break
- }
- // recurse into children of list item, to see if there are spaces
- // between any of them:
- subItem := item.FirstChild
- for subItem != nil {
- if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) {
- block.ListData.Tight = false
- break
- }
- subItem = subItem.Next
- }
- item = item.Next
- }
-}
-
-// Parse a single list item.
-// Assumes initial prefix is already removed if this is a sublist.
-func (p *Markdown) listItem(data []byte, flags *ListType) int {
- // keep track of the indentation of the first line
- itemIndent := 0
- if data[0] == '\t' {
- itemIndent += 4
- } else {
- for itemIndent < 3 && data[itemIndent] == ' ' {
- itemIndent++
- }
- }
-
- var bulletChar byte = '*'
- i := p.uliPrefix(data)
- if i == 0 {
- i = p.oliPrefix(data)
- } else {
- bulletChar = data[i-2]
- }
- if i == 0 {
- i = p.dliPrefix(data)
- // reset definition term flag
- if i > 0 {
- *flags &= ^ListTypeTerm
- }
- }
- if i == 0 {
- // if in definition list, set term flag and continue
- if *flags&ListTypeDefinition != 0 {
- *flags |= ListTypeTerm
- } else {
- return 0
- }
- }
-
- // skip leading whitespace on first line
- for i < len(data) && data[i] == ' ' {
- i++
- }
-
- // find the end of the line
- line := i
- for i > 0 && i < len(data) && data[i-1] != '\n' {
- i++
- }
-
- // get working buffer
- var raw bytes.Buffer
-
- // put the first line into the working buffer
- raw.Write(data[line:i])
- line = i
-
- // process the following lines
- containsBlankLine := false
- sublist := 0
- codeBlockMarker := ""
-
-gatherlines:
- for line < len(data) {
- i++
-
- // find the end of this line
- for i < len(data) && data[i-1] != '\n' {
- i++
- }
-
- // 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 {
- containsBlankLine = true
- line = i
- continue
- }
-
- // calculate the indentation
- indent := 0
- indentIndex := 0
- if data[line] == '\t' {
- indentIndex++
- indent += 4
- } else {
- for indent < 4 && line+indent < i && data[line+indent] == ' ' {
- indent++
- indentIndex++
- }
- }
-
- chunk := data[line+indentIndex : i]
-
- if p.extensions&FencedCode != 0 {
- // determine if in or out of codeblock
- // if in codeblock, ignore normal list processing
- _, marker := isFenceLine(chunk, nil, codeBlockMarker)
- if marker != "" {
- if codeBlockMarker == "" {
- // start of codeblock
- codeBlockMarker = marker
- } else {
- // end of codeblock.
- codeBlockMarker = ""
- }
- }
- // we are in a codeblock, write line, and continue
- if codeBlockMarker != "" || marker != "" {
- raw.Write(data[line+indentIndex : i])
- line = i
- continue 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:
-
- // to be a nested list, it must be indented more
- // if not, it is either a different kind of list
- // or the next item in the same list
- if indent <= itemIndent {
- if p.listTypeChanged(chunk, flags) {
- *flags |= ListItemEndOfList
- } else if containsBlankLine {
- *flags |= ListItemContainsBlock
- }
-
- break gatherlines
- }
-
- if containsBlankLine {
- *flags |= ListItemContainsBlock
- }
-
- // is this the first item in the nested list?
- if sublist == 0 {
- sublist = raw.Len()
- }
-
- // is this a nested prefix heading?
- case p.isPrefixHeading(chunk):
- // if the heading is not indented, it is not nested in the list
- // and thus ends the list
- if containsBlankLine && indent < 4 {
- *flags |= ListItemEndOfList
- break gatherlines
- }
- *flags |= ListItemContainsBlock
-
- // anything following an empty line is only part
- // of this item if it is indented 4 spaces
- // (regardless of the indentation of the beginning of the item)
- case containsBlankLine && indent < 4:
- if *flags&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++
- }
- for next < len(data)-1 && data[next] == '\n' {
- next++
- }
- if i < len(data)-1 && data[i] != ':' && data[next] != ':' {
- *flags |= ListItemEndOfList
- }
- } else {
- *flags |= ListItemEndOfList
- }
- break gatherlines
-
- // a blank line means this should be parsed as a block
- case containsBlankLine:
- raw.WriteByte('\n')
- *flags |= ListItemContainsBlock
- }
-
- // if this line was preceded by one or more blanks,
- // re-introduce the blank into the buffer
- if containsBlankLine {
- containsBlankLine = false
- raw.WriteByte('\n')
- }
-
- // add the line into the working buffer without prefix
- raw.Write(data[line+indentIndex : i])
-
- line = i
- }
-
- rawBytes := raw.Bytes()
-
- block := p.addBlock(Item, nil)
- block.ListFlags = *flags
- block.Tight = false
- block.BulletChar = bulletChar
- block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark
-
- // render the contents of the list item
- if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 {
- // intermediate render of block item, except for definition term
- if sublist > 0 {
- p.block(rawBytes[:sublist])
- p.block(rawBytes[sublist:])
- } else {
- p.block(rawBytes)
- }
- } else {
- // intermediate render of inline item
- if sublist > 0 {
- child := p.addChild(Paragraph, 0)
- child.content = rawBytes[:sublist]
- p.block(rawBytes[sublist:])
- } else {
- child := p.addChild(Paragraph, 0)
- child.content = rawBytes
- }
- }
- return line
-}
-
-// render a single paragraph that has already been parsed out
-func (p *Markdown) renderParagraph(data []byte) {
- if len(data) == 0 {
- return
- }
-
- // trim leading spaces
- beg := 0
- for data[beg] == ' ' {
- beg++
- }
-
- end := len(data)
- // trim trailing newline
- if data[len(data)-1] == '\n' {
- end--
- }
-
- // trim trailing spaces
- for end > beg && data[end-1] == ' ' {
- end--
- }
-
- p.addBlock(Paragraph, data[beg:end])
-}
-
-func (p *Markdown) paragraph(data []byte) int {
- // prev: index of 1st char of previous line
- // line: index of 1st char of current line
- // i: index of cursor/end of current line
- var prev, line, i int
- tabSize := TabSizeDefault
- if p.extensions&TabSizeEight != 0 {
- tabSize = TabSizeDouble
- }
- // keep going until we find something to mark the end of the paragraph
- for i < len(data) {
- // mark the beginning of the current line
- prev = line
- current := data[i:]
- line = i
-
- // did we find a reference or a footnote? If so, end a paragraph
- // preceding it and report that we have consumed up to the end of that
- // reference:
- if refEnd := isReference(p, current, tabSize); refEnd > 0 {
- p.renderParagraph(data[:i])
- return i + refEnd
- }
-
- // did we find a blank line marking the end of the paragraph?
- if n := p.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] == ':' {
- return p.list(data[prev:], ListTypeDefinition)
- }
- }
-
- p.renderParagraph(data[:i])
- return i + n
- }
-
- // an underline under some text marks a heading, so our paragraph ended on prev line
- if i > 0 {
- if level := p.isUnderlinedHeading(current); level > 0 {
- // render the paragraph
- p.renderParagraph(data[:prev])
-
- // ignore leading and trailing whitespace
- eol := i - 1
- for prev < eol && data[prev] == ' ' {
- prev++
- }
- for eol > prev && data[eol-1] == ' ' {
- eol--
- }
-
- id := ""
- if p.extensions&AutoHeadingIDs != 0 {
- id = SanitizedAnchorName(string(data[prev:eol]))
- }
-
- block := p.addBlock(Heading, data[prev:eol])
- block.Level = level
- block.HeadingID = id
-
- // find the end of the underline
- for i < len(data) && data[i] != '\n' {
- i++
- }
- return i
- }
- }
-
- // if the next line starts a block of HTML, then the paragraph ends here
- if p.extensions&LaxHTMLBlocks != 0 {
- if data[i] == '<' && p.html(current, false) > 0 {
- // rewind to before the HTML block
- p.renderParagraph(data[:i])
- return i
- }
- }
-
- // if there's a prefixed heading or a horizontal rule after this, paragraph is over
- if p.isPrefixHeading(current) || p.isHRule(current) {
- p.renderParagraph(data[:i])
- return i
- }
-
- // if there's a fenced code block, paragraph is over
- if p.extensions&FencedCode != 0 {
- if p.fencedCodeBlock(current, false) > 0 {
- p.renderParagraph(data[:i])
- return i
- }
- }
-
- // if there's a definition list item, prev line is a definition term
- if p.extensions&DefinitionLists != 0 {
- if p.dliPrefix(current) != 0 {
- ret := p.list(data[prev:], ListTypeDefinition)
- return ret
- }
- }
-
- // if there's a list after this, paragraph is over
- if p.extensions&NoEmptyLineBeforeBlock != 0 {
- if p.uliPrefix(current) != 0 ||
- p.oliPrefix(current) != 0 ||
- p.quotePrefix(current) != 0 ||
- p.codePrefix(current) != 0 {
- p.renderParagraph(data[:i])
- return i
- }
- }
-
- // otherwise, scan to the beginning of the next line
- nl := bytes.IndexByte(data[i:], '\n')
- if nl >= 0 {
- i += nl + 1
- } else {
- i += len(data[i:])
- }
- }
-
- p.renderParagraph(data[:i])
- return i
-}
-
-func skipChar(data []byte, start int, char byte) int {
- i := start
- for i < len(data) && data[i] == char {
- i++
- }
- return i
-}
-
-func skipUntilChar(text []byte, start int, char byte) int {
- i := start
- for i < len(text) && text[i] != char {
- i++
- }
- return i
-}
-
-// SanitizedAnchorName returns a sanitized anchor name for the given text.
-//
-// It implements the algorithm specified in the package comment.
-func SanitizedAnchorName(text string) string {
- var anchorName []rune
- futureDash := false
- for _, r := range text {
- switch {
- case unicode.IsLetter(r) || unicode.IsNumber(r):
- if futureDash && len(anchorName) > 0 {
- anchorName = append(anchorName, '-')
- }
- futureDash = false
- anchorName = append(anchorName, unicode.ToLower(r))
- default:
- futureDash = true
- }
- }
- return string(anchorName)
-}
diff --git a/vendor/github.com/russross/blackfriday/v2/doc.go b/vendor/github.com/russross/blackfriday/v2/doc.go
deleted file mode 100644
index 57ff152a0..000000000
--- a/vendor/github.com/russross/blackfriday/v2/doc.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Package blackfriday is a markdown processor.
-//
-// It translates plain text with simple formatting rules into an AST, which can
-// then be further processed to HTML (provided by Blackfriday itself) or other
-// formats (provided by the community).
-//
-// The simplest way to invoke Blackfriday is to call the Run function. It will
-// take a text input and produce a text output in HTML (or other format).
-//
-// A slightly more sophisticated way to use Blackfriday is to create a Markdown
-// processor and to call Parse, which returns a syntax tree for the input
-// document. You can leverage Blackfriday's parsing for content extraction from
-// markdown documents. You can assign a custom renderer and set various options
-// to the Markdown processor.
-//
-// If you're interested in calling Blackfriday from command line, see
-// https://github.com/russross/blackfriday-tool.
-//
-// Sanitized Anchor Names
-//
-// Blackfriday includes an algorithm for creating sanitized anchor names
-// corresponding to a given input text. This algorithm is used to create
-// anchors for headings when AutoHeadingIDs extension is enabled. The
-// algorithm is specified below, so that other packages can create
-// compatible anchor names and links to those anchors.
-//
-// The algorithm iterates over the input text, interpreted as UTF-8,
-// one Unicode code point (rune) at a time. All runes that are letters (category L)
-// or numbers (category N) are considered valid characters. They are mapped to
-// lower case, and included in the output. All other runes are considered
-// invalid characters. Invalid characters that precede the first valid character,
-// as well as invalid character that follow the last valid character
-// are dropped completely. All other sequences of invalid characters
-// between two valid characters are replaced with a single dash character '-'.
-//
-// SanitizedAnchorName exposes this functionality, and can be used to
-// create compatible links to the anchor names generated by blackfriday.
-// This algorithm is also implemented in a small standalone package at
-// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
-// that want a small package and don't need full functionality of blackfriday.
-package blackfriday
-
-// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
-// github.com/shurcooL/sanitized_anchor_name.
-// Otherwise, users of sanitized_anchor_name will get anchor names
-// that are incompatible with those generated by blackfriday.
diff --git a/vendor/github.com/russross/blackfriday/v2/entities.go b/vendor/github.com/russross/blackfriday/v2/entities.go
deleted file mode 100644
index a2c3edb69..000000000
--- a/vendor/github.com/russross/blackfriday/v2/entities.go
+++ /dev/null
@@ -1,2236 +0,0 @@
-package blackfriday
-
-// Extracted from https://html.spec.whatwg.org/multipage/entities.json
-var entities = map[string]bool{
- "Æ": true,
- "Æ": true,
- "&": true,
- "&": true,
- "Á": true,
- "Á": true,
- "Ă": true,
- "Â": true,
- "Â": true,
- "А": true,
- "𝔄": true,
- "À": true,
- "À": true,
- "Α": true,
- "Ā": true,
- "⩓": true,
- "Ą": true,
- "𝔸": true,
- "⁡": true,
- "Å": true,
- "Å": true,
- "𝒜": true,
- "≔": true,
- "Ã": true,
- "Ã": true,
- "Ä": true,
- "Ä": true,
- "∖": true,
- "⫧": true,
- "⌆": true,
- "Б": true,
- "∵": true,
- "ℬ": true,
- "Β": true,
- "𝔅": true,
- "𝔹": true,
- "˘": true,
- "ℬ": true,
- "≎": true,
- "Ч": true,
- "©": true,
- "©": true,
- "Ć": true,
- "⋒": true,
- "ⅅ": true,
- "ℭ": true,
- "Č": true,
- "Ç": true,
- "Ç": true,
- "Ĉ": true,
- "∰": true,
- "Ċ": true,
- "¸": true,
- "·": true,
- "ℭ": true,
- "Χ": true,
- "⊙": true,
- "⊖": true,
- "⊕": true,
- "⊗": true,
- "∲": true,
- "”": true,
- "’": true,
- "∷": true,
- "⩴": true,
- "≡": true,
- "∯": true,
- "∮": true,
- "ℂ": true,
- "∐": true,
- "∳": true,
- "⨯": true,
- "𝒞": true,
- "⋓": true,
- "≍": true,
- "ⅅ": true,
- "⤑": true,
- "Ђ": true,
- "Ѕ": true,
- "Џ": true,
- "‡": true,
- "↡": true,
- "⫤": true,
- "Ď": true,
- "Д": true,
- "∇": true,
- "Δ": true,
- "𝔇": true,
- "´": true,
- "˙": true,
- "˝": true,
- "`": true,
- "˜": true,
- "⋄": true,
- "ⅆ": true,
- "𝔻": true,
- "¨": true,
- "⃜": true,
- "≐": true,
- "∯": true,
- "¨": true,
- "⇓": true,
- "⇐": true,
- "⇔": true,
- "⫤": true,
- "⟸": true,
- "⟺": true,
- "⟹": true,
- "⇒": true,
- "⊨": true,
- "⇑": true,
- "⇕": true,
- "∥": true,
- "↓": true,
- "⤓": true,
- "⇵": true,
- "̑": true,
- "⥐": true,
- "⥞": true,
- "↽": true,
- "⥖": true,
- "⥟": true,
- "⇁": true,
- "⥗": true,
- "⊤": true,
- "↧": true,
- "⇓": true,
- "𝒟": true,
- "Đ": true,
- "Ŋ": true,
- "Ð": true,
- "Ð": true,
- "É": true,
- "É": true,
- "Ě": true,
- "Ê": true,
- "Ê": true,
- "Э": true,
- "Ė": true,
- "𝔈": true,
- "È": true,
- "È": true,
- "∈": true,
- "Ē": true,
- "◻": true,
- "▫": true,
- "Ę": true,
- "𝔼": true,
- "Ε": true,
- "⩵": true,
- "≂": true,
- "⇌": true,
- "ℰ": true,
- "⩳": true,
- "Η": true,
- "Ë": true,
- "Ë": true,
- "∃": true,
- "ⅇ": true,
- "Ф": true,
- "𝔉": true,
- "◼": true,
- "▪": true,
- "𝔽": true,
- "∀": true,
- "ℱ": true,
- "ℱ": true,
- "Ѓ": true,
- ">": true,
- ">": true,
- "Γ": true,
- "Ϝ": true,
- "Ğ": true,
- "Ģ": true,
- "Ĝ": true,
- "Г": true,
- "Ġ": true,
- "𝔊": true,
- "⋙": true,
- "𝔾": true,
- "≥": true,
- "⋛": true,
- "≧": true,
- "⪢": true,
- "≷": true,
- "⩾": true,
- "≳": true,
- "𝒢": true,
- "≫": true,
- "Ъ": true,
- "ˇ": true,
- "^": true,
- "Ĥ": true,
- "ℌ": true,
- "ℋ": true,
- "ℍ": true,
- "─": true,
- "ℋ": true,
- "Ħ": true,
- "≎": true,
- "≏": true,
- "Е": true,
- "IJ": true,
- "Ё": true,
- "Í": true,
- "Í": true,
- "Î": true,
- "Î": true,
- "И": true,
- "İ": true,
- "ℑ": true,
- "Ì": true,
- "Ì": true,
- "ℑ": true,
- "Ī": true,
- "ⅈ": true,
- "⇒": true,
- "∬": true,
- "∫": true,
- "⋂": true,
- "⁣": true,
- "⁢": true,
- "Į": true,
- "𝕀": true,
- "Ι": true,
- "ℐ": true,
- "Ĩ": true,
- "І": true,
- "Ï": true,
- "Ï": true,
- "Ĵ": true,
- "Й": true,
- "𝔍": true,
- "𝕁": true,
- "𝒥": true,
- "Ј": true,
- "Є": true,
- "Х": true,
- "Ќ": true,
- "Κ": true,
- "Ķ": true,
- "К": true,
- "𝔎": true,
- "𝕂": true,
- "𝒦": true,
- "Љ": true,
- "<": true,
- "<": true,
- "Ĺ": true,
- "Λ": true,
- "⟪": true,
- "ℒ": true,
- "↞": true,
- "Ľ": true,
- "Ļ": true,
- "Л": true,
- "⟨": true,
- "←": true,
- "⇤": true,
- "⇆": true,
- "⌈": true,
- "⟦": true,
- "⥡": true,
- "⇃": true,
- "⥙": true,
- "⌊": true,
- "↔": true,
- "⥎": true,
- "⊣": true,
- "↤": true,
- "⥚": true,
- "⊲": true,
- "⧏": true,
- "⊴": true,
- "⥑": true,
- "⥠": true,
- "↿": true,
- "⥘": true,
- "↼": true,
- "⥒": true,
- "⇐": true,
- "⇔": true,
- "⋚": true,
- "≦": true,
- "≶": true,
- "⪡": true,
- "⩽": true,
- "≲": true,
- "𝔏": true,
- "⋘": true,
- "⇚": true,
- "Ŀ": true,
- "⟵": true,
- "⟷": true,
- "⟶": true,
- "⟸": true,
- "⟺": true,
- "⟹": true,
- "𝕃": true,
- "↙": true,
- "↘": true,
- "ℒ": true,
- "↰": true,
- "Ł": true,
- "≪": true,
- "⤅": true,
- "М": true,
- " ": true,
- "ℳ": true,
- "𝔐": true,
- "∓": true,
- "𝕄": true,
- "ℳ": true,
- "Μ": true,
- "Њ": true,
- "Ń": true,
- "Ň": true,
- "Ņ": true,
- "Н": true,
- "​": true,
- "​": true,
- "​": true,
- "​": true,
- "≫": true,
- "≪": true,
- "
": true,
- "𝔑": true,
- "⁠": true,
- " ": true,
- "ℕ": true,
- "⫬": true,
- "≢": true,
- "≭": true,
- "∦": true,
- "∉": true,
- "≠": true,
- "≂̸": true,
- "∄": true,
- "≯": true,
- "≱": true,
- "≧̸": true,
- "≫̸": true,
- "≹": true,
- "⩾̸": true,
- "≵": true,
- "≎̸": true,
- "≏̸": true,
- "⋪": true,
- "⧏̸": true,
- "⋬": true,
- "≮": true,
- "≰": true,
- "≸": true,
- "≪̸": true,
- "⩽̸": true,
- "≴": true,
- "⪢̸": true,
- "⪡̸": true,
- "⊀": true,
- "⪯̸": true,
- "⋠": true,
- "∌": true,
- "⋫": true,
- "⧐̸": true,
- "⋭": true,
- "⊏̸": true,
- "⋢": true,
- "⊐̸": true,
- "⋣": true,
- "⊂⃒": true,
- "⊈": true,
- "⊁": true,
- "⪰̸": true,
- "⋡": true,
- "≿̸": true,
- "⊃⃒": true,
- "⊉": true,
- "≁": true,
- "≄": true,
- "≇": true,
- "≉": true,
- "∤": true,
- "𝒩": true,
- "Ñ": true,
- "Ñ": true,
- "Ν": true,
- "Œ": true,
- "Ó": true,
- "Ó": true,
- "Ô": true,
- "Ô": true,
- "О": true,
- "Ő": true,
- "𝔒": true,
- "Ò": true,
- "Ò": true,
- "Ō": true,
- "Ω": true,
- "Ο": true,
- "𝕆": true,
- "“": true,
- "‘": true,
- "⩔": true,
- "𝒪": true,
- "Ø": true,
- "Ø": true,
- "Õ": true,
- "Õ": true,
- "⨷": true,
- "Ö": true,
- "Ö": true,
- "‾": true,
- "⏞": true,
- "⎴": true,
- "⏜": true,
- "∂": true,
- "П": true,
- "𝔓": true,
- "Φ": true,
- "Π": true,
- "±": true,
- "ℌ": true,
- "ℙ": true,
- "⪻": true,
- "≺": true,
- "⪯": true,
- "≼": true,
- "≾": true,
- "″": true,
- "∏": true,
- "∷": true,
- "∝": true,
- "𝒫": true,
- "Ψ": true,
- """: true,
- """: true,
- "𝔔": true,
- "ℚ": true,
- "𝒬": true,
- "⤐": true,
- "®": true,
- "®": true,
- "Ŕ": true,
- "⟫": true,
- "↠": true,
- "⤖": true,
- "Ř": true,
- "Ŗ": true,
- "Р": true,
- "ℜ": true,
- "∋": true,
- "⇋": true,
- "⥯": true,
- "ℜ": true,
- "Ρ": true,
- "⟩": true,
- "→": true,
- "⇥": true,
- "⇄": true,
- "⌉": true,
- "⟧": true,
- "⥝": true,
- "⇂": true,
- "⥕": true,
- "⌋": true,
- "⊢": true,
- "↦": true,
- "⥛": true,
- "⊳": true,
- "⧐": true,
- "⊵": true,
- "⥏": true,
- "⥜": true,
- "↾": true,
- "⥔": true,
- "⇀": true,
- "⥓": true,
- "⇒": true,
- "ℝ": true,
- "⥰": true,
- "⇛": true,
- "ℛ": true,
- "↱": true,
- "⧴": true,
- "Щ": true,
- "Ш": true,
- "Ь": true,
- "Ś": true,
- "⪼": true,
- "Š": true,
- "Ş": true,
- "Ŝ": true,
- "С": true,
- "𝔖": true,
- "↓": true,
- "←": true,
- "→": true,
- "↑": true,
- "Σ": true,
- "∘": true,
- "𝕊": true,
- "√": true,
- "□": true,
- "⊓": true,
- "⊏": true,
- "⊑": true,
- "⊐": true,
- "⊒": true,
- "⊔": true,
- "𝒮": true,
- "⋆": true,
- "⋐": true,
- "⋐": true,
- "⊆": true,
- "≻": true,
- "⪰": true,
- "≽": true,
- "≿": true,
- "∋": true,
- "∑": true,
- "⋑": true,
- "⊃": true,
- "⊇": true,
- "⋑": true,
- "Þ": true,
- "Þ": true,
- "™": true,
- "Ћ": true,
- "Ц": true,
- "	": true,
- "Τ": true,
- "Ť": true,
- "Ţ": true,
- "Т": true,
- "𝔗": true,
- "∴": true,
- "Θ": true,
- "  ": true,
- " ": true,
- "∼": true,
- "≃": true,
- "≅": true,
- "≈": true,
- "𝕋": true,
- "⃛": true,
- "𝒯": true,
- "Ŧ": true,
- "Ú": true,
- "Ú": true,
- "↟": true,
- "⥉": true,
- "Ў": true,
- "Ŭ": true,
- "Û": true,
- "Û": true,
- "У": true,
- "Ű": true,
- "𝔘": true,
- "Ù": true,
- "Ù": true,
- "Ū": true,
- "_": true,
- "⏟": true,
- "⎵": true,
- "⏝": true,
- "⋃": true,
- "⊎": true,
- "Ų": true,
- "𝕌": true,
- "↑": true,
- "⤒": true,
- "⇅": true,
- "↕": true,
- "⥮": true,
- "⊥": true,
- "↥": true,
- "⇑": true,
- "⇕": true,
- "↖": true,
- "↗": true,
- "ϒ": true,
- "Υ": true,
- "Ů": true,
- "𝒰": true,
- "Ũ": true,
- "Ü": true,
- "Ü": true,
- "⊫": true,
- "⫫": true,
- "В": true,
- "⊩": true,
- "⫦": true,
- "⋁": true,
- "‖": true,
- "‖": true,
- "∣": true,
- "|": true,
- "❘": true,
- "≀": true,
- " ": true,
- "𝔙": true,
- "𝕍": true,
- "𝒱": true,
- "⊪": true,
- "Ŵ": true,
- "⋀": true,
- "𝔚": true,
- "𝕎": true,
- "𝒲": true,
- "𝔛": true,
- "Ξ": true,
- "𝕏": true,
- "𝒳": true,
- "Я": true,
- "Ї": true,
- "Ю": true,
- "Ý": true,
- "Ý": true,
- "Ŷ": true,
- "Ы": true,
- "𝔜": true,
- "𝕐": true,
- "𝒴": true,
- "Ÿ": true,
- "Ж": true,
- "Ź": true,
- "Ž": true,
- "З": true,
- "Ż": true,
- "​": true,
- "Ζ": true,
- "ℨ": true,
- "ℤ": true,
- "𝒵": true,
- "á": true,
- "á": true,
- "ă": true,
- "∾": true,
- "∾̳": true,
- "∿": true,
- "â": true,
- "â": true,
- "´": true,
- "´": true,
- "а": true,
- "æ": true,
- "æ": true,
- "⁡": true,
- "𝔞": true,
- "à": true,
- "à": true,
- "ℵ": true,
- "ℵ": true,
- "α": true,
- "ā": true,
- "⨿": true,
- "&": true,
- "&": true,
- "∧": true,
- "⩕": true,
- "⩜": true,
- "⩘": true,
- "⩚": true,
- "∠": true,
- "⦤": true,
- "∠": true,
- "∡": true,
- "⦨": true,
- "⦩": true,
- "⦪": true,
- "⦫": true,
- "⦬": true,
- "⦭": true,
- "⦮": true,
- "⦯": true,
- "∟": true,
- "⊾": true,
- "⦝": true,
- "∢": true,
- "Å": true,
- "⍼": true,
- "ą": true,
- "𝕒": true,
- "≈": true,
- "⩰": true,
- "⩯": true,
- "≊": true,
- "≋": true,
- "'": true,
- "≈": true,
- "≊": true,
- "å": true,
- "å": true,
- "𝒶": true,
- "*": true,
- "≈": true,
- "≍": true,
- "ã": true,
- "ã": true,
- "ä": true,
- "ä": true,
- "∳": true,
- "⨑": true,
- "⫭": true,
- "≌": true,
- "϶": true,
- "‵": true,
- "∽": true,
- "⋍": true,
- "⊽": true,
- "⌅": true,
- "⌅": true,
- "⎵": true,
- "⎶": true,
- "≌": true,
- "б": true,
- "„": true,
- "∵": true,
- "∵": true,
- "⦰": true,
- "϶": true,
- "ℬ": true,
- "β": true,
- "ℶ": true,
- "≬": true,
- "𝔟": true,
- "⋂": true,
- "◯": true,
- "⋃": true,
- "⨀": true,
- "⨁": true,
- "⨂": true,
- "⨆": true,
- "★": true,
- "▽": true,
- "△": true,
- "⨄": true,
- "⋁": true,
- "⋀": true,
- "⤍": true,
- "⧫": true,
- "▪": true,
- "▴": true,
- "▾": true,
- "◂": true,
- "▸": true,
- "␣": true,
- "▒": true,
- "░": true,
- "▓": true,
- "█": true,
- "=⃥": true,
- "≡⃥": true,
- "⌐": true,
- "𝕓": true,
- "⊥": true,
- "⊥": true,
- "⋈": true,
- "╗": true,
- "╔": true,
- "╖": true,
- "╓": true,
- "═": true,
- "╦": true,
- "╩": true,
- "╤": true,
- "╧": true,
- "╝": true,
- "╚": true,
- "╜": true,
- "╙": true,
- "║": true,
- "╬": true,
- "╣": true,
- "╠": true,
- "╫": true,
- "╢": true,
- "╟": true,
- "⧉": true,
- "╕": true,
- "╒": true,
- "┐": true,
- "┌": true,
- "─": true,
- "╥": true,
- "╨": true,
- "┬": true,
- "┴": true,
- "⊟": true,
- "⊞": true,
- "⊠": true,
- "╛": true,
- "╘": true,
- "┘": true,
- "└": true,
- "│": true,
- "╪": true,
- "╡": true,
- "╞": true,
- "┼": true,
- "┤": true,
- "├": true,
- "‵": true,
- "˘": true,
- "¦": true,
- "¦": true,
- "𝒷": true,
- "⁏": true,
- "∽": true,
- "⋍": true,
- "\": true,
- "⧅": true,
- "⟈": true,
- "•": true,
- "•": true,
- "≎": true,
- "⪮": true,
- "≏": true,
- "≏": true,
- "ć": true,
- "∩": true,
- "⩄": true,
- "⩉": true,
- "⩋": true,
- "⩇": true,
- "⩀": true,
- "∩︀": true,
- "⁁": true,
- "ˇ": true,
- "⩍": true,
- "č": true,
- "ç": true,
- "ç": true,
- "ĉ": true,
- "⩌": true,
- "⩐": true,
- "ċ": true,
- "¸": true,
- "¸": true,
- "⦲": true,
- "¢": true,
- "¢": true,
- "·": true,
- "𝔠": true,
- "ч": true,
- "✓": true,
- "✓": true,
- "χ": true,
- "○": true,
- "⧃": true,
- "ˆ": true,
- "≗": true,
- "↺": true,
- "↻": true,
- "®": true,
- "Ⓢ": true,
- "⊛": true,
- "⊚": true,
- "⊝": true,
- "≗": true,
- "⨐": true,
- "⫯": true,
- "⧂": true,
- "♣": true,
- "♣": true,
- ":": true,
- "≔": true,
- "≔": true,
- ",": true,
- "@": true,
- "∁": true,
- "∘": true,
- "∁": true,
- "ℂ": true,
- "≅": true,
- "⩭": true,
- "∮": true,
- "𝕔": true,
- "∐": true,
- "©": true,
- "©": true,
- "℗": true,
- "↵": true,
- "✗": true,
- "𝒸": true,
- "⫏": true,
- "⫑": true,
- "⫐": true,
- "⫒": true,
- "⋯": true,
- "⤸": true,
- "⤵": true,
- "⋞": true,
- "⋟": true,
- "↶": true,
- "⤽": true,
- "∪": true,
- "⩈": true,
- "⩆": true,
- "⩊": true,
- "⊍": true,
- "⩅": true,
- "∪︀": true,
- "↷": true,
- "⤼": true,
- "⋞": true,
- "⋟": true,
- "⋎": true,
- "⋏": true,
- "¤": true,
- "¤": true,
- "↶": true,
- "↷": true,
- "⋎": true,
- "⋏": true,
- "∲": true,
- "∱": true,
- "⌭": true,
- "⇓": true,
- "⥥": true,
- "†": true,
- "ℸ": true,
- "↓": true,
- "‐": true,
- "⊣": true,
- "⤏": true,
- "˝": true,
- "ď": true,
- "д": true,
- "ⅆ": true,
- "‡": true,
- "⇊": true,
- "⩷": true,
- "°": true,
- "°": true,
- "δ": true,
- "⦱": true,
- "⥿": true,
- "𝔡": true,
- "⇃": true,
- "⇂": true,
- "⋄": true,
- "⋄": true,
- "♦": true,
- "♦": true,
- "¨": true,
- "ϝ": true,
- "⋲": true,
- "÷": true,
- "÷": true,
- "÷": true,
- "⋇": true,
- "⋇": true,
- "ђ": true,
- "⌞": true,
- "⌍": true,
- "$": true,
- "𝕕": true,
- "˙": true,
- "≐": true,
- "≑": true,
- "∸": true,
- "∔": true,
- "⊡": true,
- "⌆": true,
- "↓": true,
- "⇊": true,
- "⇃": true,
- "⇂": true,
- "⤐": true,
- "⌟": true,
- "⌌": true,
- "𝒹": true,
- "ѕ": true,
- "⧶": true,
- "đ": true,
- "⋱": true,
- "▿": true,
- "▾": true,
- "⇵": true,
- "⥯": true,
- "⦦": true,
- "џ": true,
- "⟿": true,
- "⩷": true,
- "≑": true,
- "é": true,
- "é": true,
- "⩮": true,
- "ě": true,
- "≖": true,
- "ê": true,
- "ê": true,
- "≕": true,
- "э": true,
- "ė": true,
- "ⅇ": true,
- "≒": true,
- "𝔢": true,
- "⪚": true,
- "è": true,
- "è": true,
- "⪖": true,
- "⪘": true,
- "⪙": true,
- "⏧": true,
- "ℓ": true,
- "⪕": true,
- "⪗": true,
- "ē": true,
- "∅": true,
- "∅": true,
- "∅": true,
- " ": true,
- " ": true,
- " ": true,
- "ŋ": true,
- " ": true,
- "ę": true,
- "𝕖": true,
- "⋕": true,
- "⧣": true,
- "⩱": true,
- "ε": true,
- "ε": true,
- "ϵ": true,
- "≖": true,
- "≕": true,
- "≂": true,
- "⪖": true,
- "⪕": true,
- "=": true,
- "≟": true,
- "≡": true,
- "⩸": true,
- "⧥": true,
- "≓": true,
- "⥱": true,
- "ℯ": true,
- "≐": true,
- "≂": true,
- "η": true,
- "ð": true,
- "ð": true,
- "ë": true,
- "ë": true,
- "€": true,
- "!": true,
- "∃": true,
- "ℰ": true,
- "ⅇ": true,
- "≒": true,
- "ф": true,
- "♀": true,
- "ffi": true,
- "ff": true,
- "ffl": true,
- "𝔣": true,
- "fi": true,
- "fj": true,
- "♭": true,
- "fl": true,
- "▱": true,
- "ƒ": true,
- "𝕗": true,
- "∀": true,
- "⋔": true,
- "⫙": true,
- "⨍": true,
- "½": true,
- "½": true,
- "⅓": true,
- "¼": true,
- "¼": true,
- "⅕": true,
- "⅙": true,
- "⅛": true,
- "⅔": true,
- "⅖": true,
- "¾": true,
- "¾": true,
- "⅗": true,
- "⅜": true,
- "⅘": true,
- "⅚": true,
- "⅝": true,
- "⅞": true,
- "⁄": true,
- "⌢": true,
- "𝒻": true,
- "≧": true,
- "⪌": true,
- "ǵ": true,
- "γ": true,
- "ϝ": true,
- "⪆": true,
- "ğ": true,
- "ĝ": true,
- "г": true,
- "ġ": true,
- "≥": true,
- "⋛": true,
- "≥": true,
- "≧": true,
- "⩾": true,
- "⩾": true,
- "⪩": true,
- "⪀": true,
- "⪂": true,
- "⪄": true,
- "⋛︀": true,
- "⪔": true,
- "𝔤": true,
- "≫": true,
- "⋙": true,
- "ℷ": true,
- "ѓ": true,
- "≷": true,
- "⪒": true,
- "⪥": true,
- "⪤": true,
- "≩": true,
- "⪊": true,
- "⪊": true,
- "⪈": true,
- "⪈": true,
- "≩": true,
- "⋧": true,
- "𝕘": true,
- "`": true,
- "ℊ": true,
- "≳": true,
- "⪎": true,
- "⪐": true,
- ">": true,
- ">": true,
- "⪧": true,
- "⩺": true,
- "⋗": true,
- "⦕": true,
- "⩼": true,
- "⪆": true,
- "⥸": true,
- "⋗": true,
- "⋛": true,
- "⪌": true,
- "≷": true,
- "≳": true,
- "≩︀": true,
- "≩︀": true,
- "⇔": true,
- " ": true,
- "½": true,
- "ℋ": true,
- "ъ": true,
- "↔": true,
- "⥈": true,
- "↭": true,
- "ℏ": true,
- "ĥ": true,
- "♥": true,
- "♥": true,
- "…": true,
- "⊹": true,
- "𝔥": true,
- "⤥": true,
- "⤦": true,
- "⇿": true,
- "∻": true,
- "↩": true,
- "↪": true,
- "𝕙": true,
- "―": true,
- "𝒽": true,
- "ℏ": true,
- "ħ": true,
- "⁃": true,
- "‐": true,
- "í": true,
- "í": true,
- "⁣": true,
- "î": true,
- "î": true,
- "и": true,
- "е": true,
- "¡": true,
- "¡": true,
- "⇔": true,
- "𝔦": true,
- "ì": true,
- "ì": true,
- "ⅈ": true,
- "⨌": true,
- "∭": true,
- "⧜": true,
- "℩": true,
- "ij": true,
- "ī": true,
- "ℑ": true,
- "ℐ": true,
- "ℑ": true,
- "ı": true,
- "⊷": true,
- "Ƶ": true,
- "∈": true,
- "℅": true,
- "∞": true,
- "⧝": true,
- "ı": true,
- "∫": true,
- "⊺": true,
- "ℤ": true,
- "⊺": true,
- "⨗": true,
- "⨼": true,
- "ё": true,
- "į": true,
- "𝕚": true,
- "ι": true,
- "⨼": true,
- "¿": true,
- "¿": true,
- "𝒾": true,
- "∈": true,
- "⋹": true,
- "⋵": true,
- "⋴": true,
- "⋳": true,
- "∈": true,
- "⁢": true,
- "ĩ": true,
- "і": true,
- "ï": true,
- "ï": true,
- "ĵ": true,
- "й": true,
- "𝔧": true,
- "ȷ": true,
- "𝕛": true,
- "𝒿": true,
- "ј": true,
- "є": true,
- "κ": true,
- "ϰ": true,
- "ķ": true,
- "к": true,
- "𝔨": true,
- "ĸ": true,
- "х": true,
- "ќ": true,
- "𝕜": true,
- "𝓀": true,
- "⇚": true,
- "⇐": true,
- "⤛": true,
- "⤎": true,
- "≦": true,
- "⪋": true,
- "⥢": true,
- "ĺ": true,
- "⦴": true,
- "ℒ": true,
- "λ": true,
- "〈": true,
- "⦑": true,
- "⟨": true,
- "⪅": true,
- "«": true,
- "«": true,
- "←": true,
- "⇤": true,
- "⤟": true,
- "⤝": true,
- "↩": true,
- "↫": true,
- "⤹": true,
- "⥳": true,
- "↢": true,
- "⪫": true,
- "⤙": true,
- "⪭": true,
- "⪭︀": true,
- "⤌": true,
- "❲": true,
- "{": true,
- "[": true,
- "⦋": true,
- "⦏": true,
- "⦍": true,
- "ľ": true,
- "ļ": true,
- "⌈": true,
- "{": true,
- "л": true,
- "⤶": true,
- "“": true,
- "„": true,
- "⥧": true,
- "⥋": true,
- "↲": true,
- "≤": true,
- "←": true,
- "↢": true,
- "↽": true,
- "↼": true,
- "⇇": true,
- "↔": true,
- "⇆": true,
- "⇋": true,
- "↭": true,
- "⋋": true,
- "⋚": true,
- "≤": true,
- "≦": true,
- "⩽": true,
- "⩽": true,
- "⪨": true,
- "⩿": true,
- "⪁": true,
- "⪃": true,
- "⋚︀": true,
- "⪓": true,
- "⪅": true,
- "⋖": true,
- "⋚": true,
- "⪋": true,
- "≶": true,
- "≲": true,
- "⥼": true,
- "⌊": true,
- "𝔩": true,
- "≶": true,
- "⪑": true,
- "↽": true,
- "↼": true,
- "⥪": true,
- "▄": true,
- "љ": true,
- "≪": true,
- "⇇": true,
- "⌞": true,
- "⥫": true,
- "◺": true,
- "ŀ": true,
- "⎰": true,
- "⎰": true,
- "≨": true,
- "⪉": true,
- "⪉": true,
- "⪇": true,
- "⪇": true,
- "≨": true,
- "⋦": true,
- "⟬": true,
- "⇽": true,
- "⟦": true,
- "⟵": true,
- "⟷": true,
- "⟼": true,
- "⟶": true,
- "↫": true,
- "↬": true,
- "⦅": true,
- "𝕝": true,
- "⨭": true,
- "⨴": true,
- "∗": true,
- "_": true,
- "◊": true,
- "◊": true,
- "⧫": true,
- "(": true,
- "⦓": true,
- "⇆": true,
- "⌟": true,
- "⇋": true,
- "⥭": true,
- "": true,
- "⊿": true,
- "‹": true,
- "𝓁": true,
- "↰": true,
- "≲": true,
- "⪍": true,
- "⪏": true,
- "[": true,
- "‘": true,
- "‚": true,
- "ł": true,
- "<": true,
- "<": true,
- "⪦": true,
- "⩹": true,
- "⋖": true,
- "⋋": true,
- "⋉": true,
- "⥶": true,
- "⩻": true,
- "⦖": true,
- "◃": true,
- "⊴": true,
- "◂": true,
- "⥊": true,
- "⥦": true,
- "≨︀": true,
- "≨︀": true,
- "∺": true,
- "¯": true,
- "¯": true,
- "♂": true,
- "✠": true,
- "✠": true,
- "↦": true,
- "↦": true,
- "↧": true,
- "↤": true,
- "↥": true,
- "▮": true,
- "⨩": true,
- "м": true,
- "—": true,
- "∡": true,
- "𝔪": true,
- "℧": true,
- "µ": true,
- "µ": true,
- "∣": true,
- "*": true,
- "⫰": true,
- "·": true,
- "·": true,
- "−": true,
- "⊟": true,
- "∸": true,
- "⨪": true,
- "⫛": true,
- "…": true,
- "∓": true,
- "⊧": true,
- "𝕞": true,
- "∓": true,
- "𝓂": true,
- "∾": true,
- "μ": true,
- "⊸": true,
- "⊸": true,
- "⋙̸": true,
- "≫⃒": true,
- "≫̸": true,
- "⇍": true,
- "⇎": true,
- "⋘̸": true,
- "≪⃒": true,
- "≪̸": true,
- "⇏": true,
- "⊯": true,
- "⊮": true,
- "∇": true,
- "ń": true,
- "∠⃒": true,
- "≉": true,
- "⩰̸": true,
- "≋̸": true,
- "ʼn": true,
- "≉": true,
- "♮": true,
- "♮": true,
- "ℕ": true,
- " ": true,
- " ": true,
- "≎̸": true,
- "≏̸": true,
- "⩃": true,
- "ň": true,
- "ņ": true,
- "≇": true,
- "⩭̸": true,
- "⩂": true,
- "н": true,
- "–": true,
- "≠": true,
- "⇗": true,
- "⤤": true,
- "↗": true,
- "↗": true,
- "≐̸": true,
- "≢": true,
- "⤨": true,
- "≂̸": true,
- "∄": true,
- "∄": true,
- "𝔫": true,
- "≧̸": true,
- "≱": true,
- "≱": true,
- "≧̸": true,
- "⩾̸": true,
- "⩾̸": true,
- "≵": true,
- "≯": true,
- "≯": true,
- "⇎": true,
- "↮": true,
- "⫲": true,
- "∋": true,
- "⋼": true,
- "⋺": true,
- "∋": true,
- "њ": true,
- "⇍": true,
- "≦̸": true,
- "↚": true,
- "‥": true,
- "≰": true,
- "↚": true,
- "↮": true,
- "≰": true,
- "≦̸": true,
- "⩽̸": true,
- "⩽̸": true,
- "≮": true,
- "≴": true,
- "≮": true,
- "⋪": true,
- "⋬": true,
- "∤": true,
- "𝕟": true,
- "¬": true,
- "¬": true,
- "∉": true,
- "⋹̸": true,
- "⋵̸": true,
- "∉": true,
- "⋷": true,
- "⋶": true,
- "∌": true,
- "∌": true,
- "⋾": true,
- "⋽": true,
- "∦": true,
- "∦": true,
- "⫽⃥": true,
- "∂̸": true,
- "⨔": true,
- "⊀": true,
- "⋠": true,
- "⪯̸": true,
- "⊀": true,
- "⪯̸": true,
- "⇏": true,
- "↛": true,
- "⤳̸": true,
- "↝̸": true,
- "↛": true,
- "⋫": true,
- "⋭": true,
- "⊁": true,
- "⋡": true,
- "⪰̸": true,
- "𝓃": true,
- "∤": true,
- "∦": true,
- "≁": true,
- "≄": true,
- "≄": true,
- "∤": true,
- "∦": true,
- "⋢": true,
- "⋣": true,
- "⊄": true,
- "⫅̸": true,
- "⊈": true,
- "⊂⃒": true,
- "⊈": true,
- "⫅̸": true,
- "⊁": true,
- "⪰̸": true,
- "⊅": true,
- "⫆̸": true,
- "⊉": true,
- "⊃⃒": true,
- "⊉": true,
- "⫆̸": true,
- "≹": true,
- "ñ": true,
- "ñ": true,
- "≸": true,
- "⋪": true,
- "⋬": true,
- "⋫": true,
- "⋭": true,
- "ν": true,
- "#": true,
- "№": true,
- " ": true,
- "⊭": true,
- "⤄": true,
- "≍⃒": true,
- "⊬": true,
- "≥⃒": true,
- ">⃒": true,
- "⧞": true,
- "⤂": true,
- "≤⃒": true,
- "<⃒": true,
- "⊴⃒": true,
- "⤃": true,
- "⊵⃒": true,
- "∼⃒": true,
- "⇖": true,
- "⤣": true,
- "↖": true,
- "↖": true,
- "⤧": true,
- "Ⓢ": true,
- "ó": true,
- "ó": true,
- "⊛": true,
- "⊚": true,
- "ô": true,
- "ô": true,
- "о": true,
- "⊝": true,
- "ő": true,
- "⨸": true,
- "⊙": true,
- "⦼": true,
- "œ": true,
- "⦿": true,
- "𝔬": true,
- "˛": true,
- "ò": true,
- "ò": true,
- "⧁": true,
- "⦵": true,
- "Ω": true,
- "∮": true,
- "↺": true,
- "⦾": true,
- "⦻": true,
- "‾": true,
- "⧀": true,
- "ō": true,
- "ω": true,
- "ο": true,
- "⦶": true,
- "⊖": true,
- "𝕠": true,
- "⦷": true,
- "⦹": true,
- "⊕": true,
- "∨": true,
- "↻": true,
- "⩝": true,
- "ℴ": true,
- "ℴ": true,
- "ª": true,
- "ª": true,
- "º": true,
- "º": true,
- "⊶": true,
- "⩖": true,
- "⩗": true,
- "⩛": true,
- "ℴ": true,
- "ø": true,
- "ø": true,
- "⊘": true,
- "õ": true,
- "õ": true,
- "⊗": true,
- "⨶": true,
- "ö": true,
- "ö": true,
- "⌽": true,
- "∥": true,
- "¶": true,
- "¶": true,
- "∥": true,
- "⫳": true,
- "⫽": true,
- "∂": true,
- "п": true,
- "%": true,
- ".": true,
- "‰": true,
- "⊥": true,
- "‱": true,
- "𝔭": true,
- "φ": true,
- "ϕ": true,
- "ℳ": true,
- "☎": true,
- "π": true,
- "⋔": true,
- "ϖ": true,
- "ℏ": true,
- "ℎ": true,
- "ℏ": true,
- "+": true,
- "⨣": true,
- "⊞": true,
- "⨢": true,
- "∔": true,
- "⨥": true,
- "⩲": true,
- "±": true,
- "±": true,
- "⨦": true,
- "⨧": true,
- "±": true,
- "⨕": true,
- "𝕡": true,
- "£": true,
- "£": true,
- "≺": true,
- "⪳": true,
- "⪷": true,
- "≼": true,
- "⪯": true,
- "≺": true,
- "⪷": true,
- "≼": true,
- "⪯": true,
- "⪹": true,
- "⪵": true,
- "⋨": true,
- "≾": true,
- "′": true,
- "ℙ": true,
- "⪵": true,
- "⪹": true,
- "⋨": true,
- "∏": true,
- "⌮": true,
- "⌒": true,
- "⌓": true,
- "∝": true,
- "∝": true,
- "≾": true,
- "⊰": true,
- "𝓅": true,
- "ψ": true,
- " ": true,
- "𝔮": true,
- "⨌": true,
- "𝕢": true,
- "⁗": true,
- "𝓆": true,
- "ℍ": true,
- "⨖": true,
- "?": true,
- "≟": true,
- """: true,
- """: true,
- "⇛": true,
- "⇒": true,
- "⤜": true,
- "⤏": true,
- "⥤": true,
- "∽̱": true,
- "ŕ": true,
- "√": true,
- "⦳": true,
- "〉": true,
- "⦒": true,
- "⦥": true,
- "⟩": true,
- "»": true,
- "»": true,
- "→": true,
- "⥵": true,
- "⇥": true,
- "⤠": true,
- "⤳": true,
- "⤞": true,
- "↪": true,
- "↬": true,
- "⥅": true,
- "⥴": true,
- "↣": true,
- "↝": true,
- "⤚": true,
- "∶": true,
- "ℚ": true,
- "⤍": true,
- "❳": true,
- "}": true,
- "]": true,
- "⦌": true,
- "⦎": true,
- "⦐": true,
- "ř": true,
- "ŗ": true,
- "⌉": true,
- "}": true,
- "р": true,
- "⤷": true,
- "⥩": true,
- "”": true,
- "”": true,
- "↳": true,
- "ℜ": true,
- "ℛ": true,
- "ℜ": true,
- "ℝ": true,
- "▭": true,
- "®": true,
- "®": true,
- "⥽": true,
- "⌋": true,
- "𝔯": true,
- "⇁": true,
- "⇀": true,
- "⥬": true,
- "ρ": true,
- "ϱ": true,
- "→": true,
- "↣": true,
- "⇁": true,
- "⇀": true,
- "⇄": true,
- "⇌": true,
- "⇉": true,
- "↝": true,
- "⋌": true,
- "˚": true,
- "≓": true,
- "⇄": true,
- "⇌": true,
- "": true,
- "⎱": true,
- "⎱": true,
- "⫮": true,
- "⟭": true,
- "⇾": true,
- "⟧": true,
- "⦆": true,
- "𝕣": true,
- "⨮": true,
- "⨵": true,
- ")": true,
- "⦔": true,
- "⨒": true,
- "⇉": true,
- "›": true,
- "𝓇": true,
- "↱": true,
- "]": true,
- "’": true,
- "’": true,
- "⋌": true,
- "⋊": true,
- "▹": true,
- "⊵": true,
- "▸": true,
- "⧎": true,
- "⥨": true,
- "℞": true,
- "ś": true,
- "‚": true,
- "≻": true,
- "⪴": true,
- "⪸": true,
- "š": true,
- "≽": true,
- "⪰": true,
- "ş": true,
- "ŝ": true,
- "⪶": true,
- "⪺": true,
- "⋩": true,
- "⨓": true,
- "≿": true,
- "с": true,
- "⋅": true,
- "⊡": true,
- "⩦": true,
- "⇘": true,
- "⤥": true,
- "↘": true,
- "↘": true,
- "§": true,
- "§": true,
- ";": true,
- "⤩": true,
- "∖": true,
- "∖": true,
- "✶": true,
- "𝔰": true,
- "⌢": true,
- "♯": true,
- "щ": true,
- "ш": true,
- "∣": true,
- "∥": true,
- "­": true,
- "": true,
- "σ": true,
- "ς": true,
- "ς": true,
- "∼": true,
- "⩪": true,
- "≃": true,
- "≃": true,
- "⪞": true,
- "⪠": true,
- "⪝": true,
- "⪟": true,
- "≆": true,
- "⨤": true,
- "⥲": true,
- "←": true,
- "∖": true,
- "⨳": true,
- "⧤": true,
- "∣": true,
- "⌣": true,
- "⪪": true,
- "⪬": true,
- "⪬︀": true,
- "ь": true,
- "/": true,
- "⧄": true,
- "⌿": true,
- "𝕤": true,
- "♠": true,
- "♠": true,
- "∥": true,
- "⊓": true,
- "⊓︀": true,
- "⊔": true,
- "⊔︀": true,
- "⊏": true,
- "⊑": true,
- "⊏": true,
- "⊑": true,
- "⊐": true,
- "⊒": true,
- "⊐": true,
- "⊒": true,
- "□": true,
- "□": true,
- "▪": true,
- "▪": true,
- "→": true,
- "𝓈": true,
- "∖": true,
- "⌣": true,
- "⋆": true,
- "☆": true,
- "★": true,
- "ϵ": true,
- "ϕ": true,
- "¯": true,
- "⊂": true,
- "⫅": true,
- "⪽": true,
- "⊆": true,
- "⫃": true,
- "⫁": true,
- "⫋": true,
- "⊊": true,
- "⪿": true,
- "⥹": true,
- "⊂": true,
- "⊆": true,
- "⫅": true,
- "⊊": true,
- "⫋": true,
- "⫇": true,
- "⫕": true,
- "⫓": true,
- "≻": true,
- "⪸": true,
- "≽": true,
- "⪰": true,
- "⪺": true,
- "⪶": true,
- "⋩": true,
- "≿": true,
- "∑": true,
- "♪": true,
- "¹": true,
- "¹": true,
- "²": true,
- "²": true,
- "³": true,
- "³": true,
- "⊃": true,
- "⫆": true,
- "⪾": true,
- "⫘": true,
- "⊇": true,
- "⫄": true,
- "⟉": true,
- "⫗": true,
- "⥻": true,
- "⫂": true,
- "⫌": true,
- "⊋": true,
- "⫀": true,
- "⊃": true,
- "⊇": true,
- "⫆": true,
- "⊋": true,
- "⫌": true,
- "⫈": true,
- "⫔": true,
- "⫖": true,
- "⇙": true,
- "⤦": true,
- "↙": true,
- "↙": true,
- "⤪": true,
- "ß": true,
- "ß": true,
- "⌖": true,
- "τ": true,
- "⎴": true,
- "ť": true,
- "ţ": true,
- "т": true,
- "⃛": true,
- "⌕": true,
- "𝔱": true,
- "∴": true,
- "∴": true,
- "θ": true,
- "ϑ": true,
- "ϑ": true,
- "≈": true,
- "∼": true,
- " ": true,
- "≈": true,
- "∼": true,
- "þ": true,
- "þ": true,
- "˜": true,
- "×": true,
- "×": true,
- "⊠": true,
- "⨱": true,
- "⨰": true,
- "∭": true,
- "⤨": true,
- "⊤": true,
- "⌶": true,
- "⫱": true,
- "𝕥": true,
- "⫚": true,
- "⤩": true,
- "‴": true,
- "™": true,
- "▵": true,
- "▿": true,
- "◃": true,
- "⊴": true,
- "≜": true,
- "▹": true,
- "⊵": true,
- "◬": true,
- "≜": true,
- "⨺": true,
- "⨹": true,
- "⧍": true,
- "⨻": true,
- "⏢": true,
- "𝓉": true,
- "ц": true,
- "ћ": true,
- "ŧ": true,
- "≬": true,
- "↞": true,
- "↠": true,
- "⇑": true,
- "⥣": true,
- "ú": true,
- "ú": true,
- "↑": true,
- "ў": true,
- "ŭ": true,
- "û": true,
- "û": true,
- "у": true,
- "⇅": true,
- "ű": true,
- "⥮": true,
- "⥾": true,
- "𝔲": true,
- "ù": true,
- "ù": true,
- "↿": true,
- "↾": true,
- "▀": true,
- "⌜": true,
- "⌜": true,
- "⌏": true,
- "◸": true,
- "ū": true,
- "¨": true,
- "¨": true,
- "ų": true,
- "𝕦": true,
- "↑": true,
- "↕": true,
- "↿": true,
- "↾": true,
- "⊎": true,
- "υ": true,
- "ϒ": true,
- "υ": true,
- "⇈": true,
- "⌝": true,
- "⌝": true,
- "⌎": true,
- "ů": true,
- "◹": true,
- "𝓊": true,
- "⋰": true,
- "ũ": true,
- "▵": true,
- "▴": true,
- "⇈": true,
- "ü": true,
- "ü": true,
- "⦧": true,
- "⇕": true,
- "⫨": true,
- "⫩": true,
- "⊨": true,
- "⦜": true,
- "ϵ": true,
- "ϰ": true,
- "∅": true,
- "ϕ": true,
- "ϖ": true,
- "∝": true,
- "↕": true,
- "ϱ": true,
- "ς": true,
- "⊊︀": true,
- "⫋︀": true,
- "⊋︀": true,
- "⫌︀": true,
- "ϑ": true,
- "⊲": true,
- "⊳": true,
- "в": true,
- "⊢": true,
- "∨": true,
- "⊻": true,
- "≚": true,
- "⋮": true,
- "|": true,
- "|": true,
- "𝔳": true,
- "⊲": true,
- "⊂⃒": true,
- "⊃⃒": true,
- "𝕧": true,
- "∝": true,
- "⊳": true,
- "𝓋": true,
- "⫋︀": true,
- "⊊︀": true,
- "⫌︀": true,
- "⊋︀": true,
- "⦚": true,
- "ŵ": true,
- "⩟": true,
- "∧": true,
- "≙": true,
- "℘": true,
- "𝔴": true,
- "𝕨": true,
- "℘": true,
- "≀": true,
- "≀": true,
- "𝓌": true,
- "⋂": true,
- "◯": true,
- "⋃": true,
- "▽": true,
- "𝔵": true,
- "⟺": true,
- "⟷": true,
- "ξ": true,
- "⟸": true,
- "⟵": true,
- "⟼": true,
- "⋻": true,
- "⨀": true,
- "𝕩": true,
- "⨁": true,
- "⨂": true,
- "⟹": true,
- "⟶": true,
- "𝓍": true,
- "⨆": true,
- "⨄": true,
- "△": true,
- "⋁": true,
- "⋀": true,
- "ý": true,
- "ý": true,
- "я": true,
- "ŷ": true,
- "ы": true,
- "¥": true,
- "¥": true,
- "𝔶": true,
- "ї": true,
- "𝕪": true,
- "𝓎": true,
- "ю": true,
- "ÿ": true,
- "ÿ": true,
- "ź": true,
- "ž": true,
- "з": true,
- "ż": true,
- "ℨ": true,
- "ζ": true,
- "𝔷": true,
- "ж": true,
- "⇝": true,
- "𝕫": true,
- "𝓏": true,
- "": true,
- "": true,
-}
diff --git a/vendor/github.com/russross/blackfriday/v2/esc.go b/vendor/github.com/russross/blackfriday/v2/esc.go
deleted file mode 100644
index 6ab60102c..000000000
--- a/vendor/github.com/russross/blackfriday/v2/esc.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package blackfriday
-
-import (
- "html"
- "io"
-)
-
-var htmlEscaper = [256][]byte{
- '&': []byte("&"),
- '<': []byte("<"),
- '>': []byte(">"),
- '"': []byte("""),
-}
-
-func escapeHTML(w io.Writer, s []byte) {
- escapeEntities(w, s, false)
-}
-
-func escapeAllHTML(w io.Writer, s []byte) {
- escapeEntities(w, s, true)
-}
-
-func escapeEntities(w io.Writer, s []byte, escapeValidEntities bool) {
- var start, end int
- for end < len(s) {
- escSeq := htmlEscaper[s[end]]
- if escSeq != nil {
- isEntity, entityEnd := nodeIsEntity(s, end)
- if isEntity && !escapeValidEntities {
- w.Write(s[start : entityEnd+1])
- start = entityEnd + 1
- } else {
- w.Write(s[start:end])
- w.Write(escSeq)
- start = end + 1
- }
- }
- end++
- }
- if start < len(s) && end <= len(s) {
- w.Write(s[start:end])
- }
-}
-
-func nodeIsEntity(s []byte, end int) (isEntity bool, endEntityPos int) {
- isEntity = false
- endEntityPos = end + 1
-
- if s[end] == '&' {
- for endEntityPos < len(s) {
- if s[endEntityPos] == ';' {
- if entities[string(s[end:endEntityPos+1])] {
- isEntity = true
- break
- }
- }
- if !isalnum(s[endEntityPos]) && s[endEntityPos] != '&' && s[endEntityPos] != '#' {
- break
- }
- endEntityPos++
- }
- }
-
- return isEntity, endEntityPos
-}
-
-func escLink(w io.Writer, text []byte) {
- unesc := html.UnescapeString(string(text))
- escapeHTML(w, []byte(unesc))
-}
diff --git a/vendor/github.com/russross/blackfriday/v2/html.go b/vendor/github.com/russross/blackfriday/v2/html.go
deleted file mode 100644
index cb4f26e30..000000000
--- a/vendor/github.com/russross/blackfriday/v2/html.go
+++ /dev/null
@@ -1,952 +0,0 @@
-//
-// Blackfriday Markdown Processor
-// Available at http://github.com/russross/blackfriday
-//
-// Copyright © 2011 Russ Ross .
-// Distributed under the Simplified BSD License.
-// See README.md for details.
-//
-
-//
-//
-// HTML rendering backend
-//
-//
-
-package blackfriday
-
-import (
- "bytes"
- "fmt"
- "io"
- "regexp"
- "strings"
-)
-
-// HTMLFlags control optional behavior of HTML renderer.
-type HTMLFlags int
-
-// HTML renderer configuration options.
-const (
- HTMLFlagsNone HTMLFlags = 0
- SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks
- SkipImages // Skip embedded images
- SkipLinks // Skip all links
- Safelink // Only link to trusted protocols
- NofollowLinks // Only link with rel="nofollow"
- NoreferrerLinks // Only link with rel="noreferrer"
- NoopenerLinks // Only link with rel="noopener"
- HrefTargetBlank // Add a blank target
- CompletePage // Generate a complete HTML page
- UseXHTML // Generate XHTML output instead of HTML
- FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source
- Smartypants // Enable smart punctuation substitutions
- SmartypantsFractions // Enable smart fractions (with Smartypants)
- SmartypantsDashes // Enable smart dashes (with Smartypants)
- SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants)
- SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering
- SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants)
- TOC // Generate a table of contents
-)
-
-var (
- htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag)
-)
-
-const (
- htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" +
- processingInstruction + "|" + declaration + "|" + cdata + ")"
- closeTag = "" + tagName + "\\s*[>]"
- openTag = "<" + tagName + attribute + "*" + "\\s*/?>"
- attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)"
- attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")"
- attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")"
- attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
- cdata = ""
- declaration = "]*>"
- doubleQuotedValue = "\"[^\"]*\""
- htmlComment = "|"
- processingInstruction = "[<][?].*?[?][>]"
- singleQuotedValue = "'[^']*'"
- tagName = "[A-Za-z][A-Za-z0-9-]*"
- unquotedValue = "[^\"'=<>`\\x00-\\x20]+"
-)
-
-// HTMLRendererParameters is a collection of supplementary parameters tweaking
-// the behavior of various parts of HTML renderer.
-type HTMLRendererParameters struct {
- // Prepend this text to each relative URL.
- AbsolutePrefix string
- // Add this text to each footnote anchor, to ensure uniqueness.
- FootnoteAnchorPrefix string
- // Show this text inside the tag for a footnote return link, if the
- // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
- // [return] is used.
- FootnoteReturnLinkContents 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
- // Increase heading levels: if the offset is 1, becomes etc.
- // Negative offset is also valid.
- // Resulting levels are clipped between 1 and 6.
- HeadingLevelOffset int
-
- Title string // Document title (used if CompletePage is set)
- CSS string // Optional CSS file URL (used if CompletePage is set)
- Icon string // Optional icon file URL (used if CompletePage is set)
-
- Flags HTMLFlags // Flags allow customizing this renderer's behavior
-}
-
-// HTMLRenderer is a type that implements the Renderer interface for HTML output.
-//
-// Do not create this directly, instead use the NewHTMLRenderer function.
-type HTMLRenderer struct {
- HTMLRendererParameters
-
- closeTag string // how to end singleton tags: either " />" or ">"
-
- // Track heading IDs to prevent ID collision in a single generation.
- headingIDs map[string]int
-
- lastOutputLen int
- disableTags int
-
- sr *SPRenderer
-}
-
-const (
- xhtmlClose = " />"
- htmlClose = ">"
-)
-
-// NewHTMLRenderer creates and configures an HTMLRenderer object, which
-// satisfies the Renderer interface.
-func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
- // configure the rendering engine
- closeTag := htmlClose
- if params.Flags&UseXHTML != 0 {
- closeTag = xhtmlClose
- }
-
- if params.FootnoteReturnLinkContents == "" {
- // U+FE0E is VARIATION SELECTOR-15.
- // It suppresses automatic emoji presentation of the preceding
- // U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS.
- params.FootnoteReturnLinkContents = "↩\ufe0e"
- }
-
- return &HTMLRenderer{
- HTMLRendererParameters: params,
-
- closeTag: closeTag,
- headingIDs: make(map[string]int),
-
- sr: NewSmartypantsRenderer(params.Flags),
- }
-}
-
-func isHTMLTag(tag []byte, tagname string) bool {
- found, _ := findHTMLTagPos(tag, tagname)
- return found
-}
-
-// Look for a character, but ignore it when it's in any kind of quotes, it
-// might be JavaScript
-func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
- inSingleQuote := false
- inDoubleQuote := false
- inGraveQuote := false
- i := start
- for i < len(html) {
- switch {
- case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
- return i
- case html[i] == '\'':
- inSingleQuote = !inSingleQuote
- case html[i] == '"':
- inDoubleQuote = !inDoubleQuote
- case html[i] == '`':
- inGraveQuote = !inGraveQuote
- }
- i++
- }
- return start
-}
-
-func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
- i := 0
- if i < len(tag) && tag[0] != '<' {
- return false, -1
- }
- i++
- i = skipSpace(tag, i)
-
- if i < len(tag) && tag[i] == '/' {
- i++
- }
-
- i = skipSpace(tag, i)
- j := 0
- for ; i < len(tag); i, j = i+1, j+1 {
- if j >= len(tagname) {
- break
- }
-
- if strings.ToLower(string(tag[i]))[0] != tagname[j] {
- return false, -1
- }
- }
-
- if i == len(tag) {
- return false, -1
- }
-
- rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
- if rightAngle >= i {
- return true, rightAngle
- }
-
- return false, -1
-}
-
-func skipSpace(tag []byte, i int) int {
- for i < len(tag) && isspace(tag[i]) {
- i++
- }
- return i
-}
-
-func isRelativeLink(link []byte) (yes bool) {
- // a tag begin with '#'
- if link[0] == '#' {
- return true
- }
-
- // link begin with '/' but not '//', the second maybe a protocol relative link
- if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
- return true
- }
-
- // only the root '/'
- if len(link) == 1 && link[0] == '/' {
- return true
- }
-
- // current directory : begin with "./"
- if bytes.HasPrefix(link, []byte("./")) {
- return true
- }
-
- // parent directory : begin with "../"
- if bytes.HasPrefix(link, []byte("../")) {
- return true
- }
-
- return false
-}
-
-func (r *HTMLRenderer) 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 *HTMLRenderer) addAbsPrefix(link []byte) []byte {
- if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
- newDest := r.AbsolutePrefix
- if link[0] != '/' {
- newDest += "/"
- }
- newDest += string(link)
- return []byte(newDest)
- }
- return link
-}
-
-func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
- if isRelativeLink(link) {
- return attrs
- }
- val := []string{}
- if flags&NofollowLinks != 0 {
- val = append(val, "nofollow")
- }
- if flags&NoreferrerLinks != 0 {
- val = append(val, "noreferrer")
- }
- if flags&NoopenerLinks != 0 {
- val = append(val, "noopener")
- }
- if flags&HrefTargetBlank != 0 {
- attrs = append(attrs, "target=\"_blank\"")
- }
- if len(val) == 0 {
- return attrs
- }
- attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
- return append(attrs, attr)
-}
-
-func isMailto(link []byte) bool {
- return bytes.HasPrefix(link, []byte("mailto:"))
-}
-
-func needSkipLink(flags HTMLFlags, dest []byte) bool {
- if flags&SkipLinks != 0 {
- return true
- }
- return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
-}
-
-func isSmartypantable(node *Node) bool {
- pt := node.Parent.Type
- return pt != Link && pt != CodeBlock && pt != Code
-}
-
-func appendLanguageAttr(attrs []string, info []byte) []string {
- if len(info) == 0 {
- return attrs
- }
- endOfLang := bytes.IndexAny(info, "\t ")
- if endOfLang < 0 {
- endOfLang = len(info)
- }
- return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
-}
-
-func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
- w.Write(name)
- if len(attrs) > 0 {
- w.Write(spaceBytes)
- w.Write([]byte(strings.Join(attrs, " ")))
- }
- w.Write(gtBytes)
- r.lastOutputLen = 1
-}
-
-func footnoteRef(prefix string, node *Node) []byte {
- urlFrag := prefix + string(slugify(node.Destination))
- anchor := fmt.Sprintf(`%d`, urlFrag, node.NoteID)
- return []byte(fmt.Sprintf(``, urlFrag, anchor))
-}
-
-func footnoteItem(prefix string, slug []byte) []byte {
- return []byte(fmt.Sprintf(`
`, prefix, slug))
-}
-
-func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
- const format = ` `
- return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
-}
-
-func itemOpenCR(node *Node) bool {
- if node.Prev == nil {
- return false
- }
- ld := node.Parent.ListData
- return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
-}
-
-func skipParagraphTags(node *Node) bool {
- grandparent := node.Parent.Parent
- if grandparent == nil || grandparent.Type != List {
- return false
- }
- tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
- return grandparent.Type == List && tightOrTerm
-}
-
-func cellAlignment(align CellAlignFlags) string {
- switch align {
- case TableAlignmentLeft:
- return "left"
- case TableAlignmentRight:
- return "right"
- case TableAlignmentCenter:
- return "center"
- default:
- return ""
- }
-}
-
-func (r *HTMLRenderer) out(w io.Writer, text []byte) {
- if r.disableTags > 0 {
- w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
- } else {
- w.Write(text)
- }
- r.lastOutputLen = len(text)
-}
-
-func (r *HTMLRenderer) cr(w io.Writer) {
- if r.lastOutputLen > 0 {
- r.out(w, nlBytes)
- }
-}
-
-var (
- nlBytes = []byte{'\n'}
- gtBytes = []byte{'>'}
- spaceBytes = []byte{' '}
-)
-
-var (
- brTag = []byte("
")
- brXHTMLTag = []byte("
")
- emTag = []byte("")
- emCloseTag = []byte("")
- strongTag = []byte("")
- strongCloseTag = []byte("")
- delTag = []byte("")
- delCloseTag = []byte("")
- ttTag = []byte("")
- ttCloseTag = []byte("")
- aTag = []byte("")
- preTag = []byte("")
- preCloseTag = []byte("
")
- codeTag = []byte("")
- codeCloseTag = []byte("
")
- pTag = []byte("")
- pCloseTag = []byte("
")
- blockquoteTag = []byte("")
- blockquoteCloseTag = []byte("
")
- hrTag = []byte("
")
- hrXHTMLTag = []byte("
")
- ulTag = []byte("")
- ulCloseTag = []byte("
")
- olTag = []byte("")
- olCloseTag = []byte("
")
- dlTag = []byte("")
- dlCloseTag = []byte("
")
- liTag = []byte("")
- liCloseTag = []byte("")
- ddTag = []byte("")
- ddCloseTag = []byte("")
- dtTag = []byte("")
- dtCloseTag = []byte("")
- tableTag = []byte("")
- tableCloseTag = []byte("
")
- tdTag = []byte("")
- thTag = []byte(" | ")
- theadTag = []byte("")
- theadCloseTag = []byte("")
- tbodyTag = []byte(" | ")
- tbodyCloseTag = []byte("")
- trTag = []byte("")
- trCloseTag = []byte("
")
- h1Tag = []byte("")
- h2Tag = []byte("")
- h3Tag = []byte("")
- h4Tag = []byte("")
- h5Tag = []byte("")
- h6Tag = []byte("")
-
- footnotesDivBytes = []byte("\n\n")
-)
-
-func headingTagsFromLevel(level int) ([]byte, []byte) {
- if level <= 1 {
- return h1Tag, h1CloseTag
- }
- switch level {
- case 2:
- return h2Tag, h2CloseTag
- case 3:
- return h3Tag, h3CloseTag
- case 4:
- return h4Tag, h4CloseTag
- case 5:
- return h5Tag, h5CloseTag
- }
- return h6Tag, h6CloseTag
-}
-
-func (r *HTMLRenderer) outHRTag(w io.Writer) {
- if r.Flags&UseXHTML == 0 {
- r.out(w, hrTag)
- } else {
- r.out(w, hrXHTMLTag)
- }
-}
-
-// RenderNode is a default renderer of a single node of a syntax tree. For
-// block nodes it will be called twice: first time with entering=true, second
-// time with entering=false, so that it could know when it's working on an open
-// tag and when on close. It writes the result to w.
-//
-// The return value is a way to tell the calling walker to adjust its walk
-// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
-// can ask the walker to skip a subtree of this node by returning SkipChildren.
-// The typical behavior is to return GoToNext, which asks for the usual
-// traversal to the next node.
-func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
- attrs := []string{}
- switch node.Type {
- case Text:
- if r.Flags&Smartypants != 0 {
- var tmp bytes.Buffer
- escapeHTML(&tmp, node.Literal)
- r.sr.Process(w, tmp.Bytes())
- } else {
- if node.Parent.Type == Link {
- escLink(w, node.Literal)
- } else {
- escapeHTML(w, node.Literal)
- }
- }
- case Softbreak:
- r.cr(w)
- // TODO: make it configurable via out(renderer.softbreak)
- case Hardbreak:
- if r.Flags&UseXHTML == 0 {
- r.out(w, brTag)
- } else {
- r.out(w, brXHTMLTag)
- }
- r.cr(w)
- case Emph:
- if entering {
- r.out(w, emTag)
- } else {
- r.out(w, emCloseTag)
- }
- case Strong:
- if entering {
- r.out(w, strongTag)
- } else {
- r.out(w, strongCloseTag)
- }
- case Del:
- if entering {
- r.out(w, delTag)
- } else {
- r.out(w, delCloseTag)
- }
- case HTMLSpan:
- if r.Flags&SkipHTML != 0 {
- break
- }
- r.out(w, node.Literal)
- case Link:
- // mark it but don't link it if it is not a safe link: no smartypants
- dest := node.LinkData.Destination
- if needSkipLink(r.Flags, dest) {
- if entering {
- r.out(w, ttTag)
- } else {
- r.out(w, ttCloseTag)
- }
- } else {
- if entering {
- dest = r.addAbsPrefix(dest)
- var hrefBuf bytes.Buffer
- hrefBuf.WriteString("href=\"")
- escLink(&hrefBuf, dest)
- hrefBuf.WriteByte('"')
- attrs = append(attrs, hrefBuf.String())
- if node.NoteID != 0 {
- r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
- break
- }
- attrs = appendLinkAttrs(attrs, r.Flags, dest)
- if len(node.LinkData.Title) > 0 {
- var titleBuff bytes.Buffer
- titleBuff.WriteString("title=\"")
- escapeHTML(&titleBuff, node.LinkData.Title)
- titleBuff.WriteByte('"')
- attrs = append(attrs, titleBuff.String())
- }
- r.tag(w, aTag, attrs)
- } else {
- if node.NoteID != 0 {
- break
- }
- r.out(w, aCloseTag)
- }
- }
- case Image:
- if r.Flags&SkipImages != 0 {
- return SkipChildren
- }
- if entering {
- dest := node.LinkData.Destination
- dest = r.addAbsPrefix(dest)
- if r.disableTags == 0 {
- //if options.safe && potentiallyUnsafe(dest) {
- //out(w, ``))
- }
- }
- case Code:
- r.out(w, codeTag)
- escapeAllHTML(w, node.Literal)
- r.out(w, codeCloseTag)
- case Document:
- break
- case Paragraph:
- if skipParagraphTags(node) {
- break
- }
- if entering {
- // TODO: untangle this clusterfuck about when the newlines need
- // to be added and when not.
- if node.Prev != nil {
- switch node.Prev.Type {
- case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
- r.cr(w)
- }
- }
- if node.Parent.Type == BlockQuote && node.Prev == nil {
- r.cr(w)
- }
- r.out(w, pTag)
- } else {
- r.out(w, pCloseTag)
- if !(node.Parent.Type == Item && node.Next == nil) {
- r.cr(w)
- }
- }
- case BlockQuote:
- if entering {
- r.cr(w)
- r.out(w, blockquoteTag)
- } else {
- r.out(w, blockquoteCloseTag)
- r.cr(w)
- }
- case HTMLBlock:
- if r.Flags&SkipHTML != 0 {
- break
- }
- r.cr(w)
- r.out(w, node.Literal)
- r.cr(w)
- case Heading:
- headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
- openTag, closeTag := headingTagsFromLevel(headingLevel)
- if entering {
- if node.IsTitleblock {
- attrs = append(attrs, `class="title"`)
- }
- if node.HeadingID != "" {
- id := r.ensureUniqueHeadingID(node.HeadingID)
- if r.HeadingIDPrefix != "" {
- id = r.HeadingIDPrefix + id
- }
- if r.HeadingIDSuffix != "" {
- id = id + r.HeadingIDSuffix
- }
- attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
- }
- r.cr(w)
- r.tag(w, openTag, attrs)
- } else {
- r.out(w, closeTag)
- if !(node.Parent.Type == Item && node.Next == nil) {
- r.cr(w)
- }
- }
- case HorizontalRule:
- r.cr(w)
- r.outHRTag(w)
- r.cr(w)
- case List:
- openTag := ulTag
- closeTag := ulCloseTag
- if node.ListFlags&ListTypeOrdered != 0 {
- openTag = olTag
- closeTag = olCloseTag
- }
- if node.ListFlags&ListTypeDefinition != 0 {
- openTag = dlTag
- closeTag = dlCloseTag
- }
- if entering {
- if node.IsFootnotesList {
- r.out(w, footnotesDivBytes)
- r.outHRTag(w)
- r.cr(w)
- }
- r.cr(w)
- if node.Parent.Type == Item && node.Parent.Parent.Tight {
- r.cr(w)
- }
- r.tag(w, openTag[:len(openTag)-1], attrs)
- r.cr(w)
- } else {
- r.out(w, closeTag)
- //cr(w)
- //if node.parent.Type != Item {
- // cr(w)
- //}
- if node.Parent.Type == Item && node.Next != nil {
- r.cr(w)
- }
- if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
- r.cr(w)
- }
- if node.IsFootnotesList {
- r.out(w, footnotesCloseDivBytes)
- }
- }
- case Item:
- openTag := liTag
- closeTag := liCloseTag
- if node.ListFlags&ListTypeDefinition != 0 {
- openTag = ddTag
- closeTag = ddCloseTag
- }
- if node.ListFlags&ListTypeTerm != 0 {
- openTag = dtTag
- closeTag = dtCloseTag
- }
- if entering {
- if itemOpenCR(node) {
- r.cr(w)
- }
- if node.ListData.RefLink != nil {
- slug := slugify(node.ListData.RefLink)
- r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
- break
- }
- r.out(w, openTag)
- } else {
- if node.ListData.RefLink != nil {
- slug := slugify(node.ListData.RefLink)
- if r.Flags&FootnoteReturnLinks != 0 {
- r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
- }
- }
- r.out(w, closeTag)
- r.cr(w)
- }
- case CodeBlock:
- attrs = appendLanguageAttr(attrs, node.Info)
- r.cr(w)
- r.out(w, preTag)
- r.tag(w, codeTag[:len(codeTag)-1], attrs)
- escapeAllHTML(w, node.Literal)
- r.out(w, codeCloseTag)
- r.out(w, preCloseTag)
- if node.Parent.Type != Item {
- r.cr(w)
- }
- case Table:
- if entering {
- r.cr(w)
- r.out(w, tableTag)
- } else {
- r.out(w, tableCloseTag)
- r.cr(w)
- }
- case TableCell:
- openTag := tdTag
- closeTag := tdCloseTag
- if node.IsHeader {
- openTag = thTag
- closeTag = thCloseTag
- }
- if entering {
- align := cellAlignment(node.Align)
- if align != "" {
- attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
- }
- if node.Prev == nil {
- r.cr(w)
- }
- r.tag(w, openTag, attrs)
- } else {
- r.out(w, closeTag)
- r.cr(w)
- }
- case TableHead:
- if entering {
- r.cr(w)
- r.out(w, theadTag)
- } else {
- r.out(w, theadCloseTag)
- r.cr(w)
- }
- case TableBody:
- if entering {
- r.cr(w)
- r.out(w, tbodyTag)
- // XXX: this is to adhere to a rather silly test. Should fix test.
- if node.FirstChild == nil {
- r.cr(w)
- }
- } else {
- r.out(w, tbodyCloseTag)
- r.cr(w)
- }
- case TableRow:
- if entering {
- r.cr(w)
- r.out(w, trTag)
- } else {
- r.out(w, trCloseTag)
- r.cr(w)
- }
- default:
- panic("Unknown node type " + node.Type.String())
- }
- return GoToNext
-}
-
-// RenderHeader writes HTML document preamble and TOC if requested.
-func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
- r.writeDocumentHeader(w)
- if r.Flags&TOC != 0 {
- r.writeTOC(w, ast)
- }
-}
-
-// RenderFooter writes HTML document footer.
-func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
- if r.Flags&CompletePage == 0 {
- return
- }
- io.WriteString(w, "\n