Source File
html.go
Belonging Package
github.com/russross/blackfriday/v2
package blackfriday
import (
)
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 = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
declaration = "<![A-Z]+" + "\\s+[^>]*>"
doubleQuotedValue = "\"[^\"]*\""
htmlComment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
processingInstruction = "[<][?].*?[?][>]"
singleQuotedValue = "'[^']*'"
tagName = "[A-Za-z][A-Za-z0-9-]*"
unquotedValue = "[^\"'=<>`\\x00-\\x20]+"
)
type HTMLRenderer struct {
HTMLRendererParameters
closeTag string // how to end singleton tags: either " />" or ">"
headingIDs map[string]int
lastOutputLen int
disableTags int
sr *SPRenderer
}
const (
xhtmlClose = " />"
htmlClose = ">"
)
:= htmlClose
if .Flags&UseXHTML != 0 {
= xhtmlClose
}
if .FootnoteReturnLinkContents == "" {
.FootnoteReturnLinkContents = `<sup>[return]</sup>`
}
return &HTMLRenderer{
HTMLRendererParameters: ,
closeTag: ,
headingIDs: make(map[string]int),
sr: NewSmartypantsRenderer(.Flags),
}
}
func ( []byte, string) bool {
, := findHTMLTagPos(, )
return
}
func ( []byte, int, byte) int {
:= false
:= false
:= false
:=
for < len() {
switch {
case [] == && ! && ! && !:
return
case [] == '\'':
= !
case [] == '"':
= !
case [] == '`':
= !
}
++
}
return
}
func ( []byte, string) (bool, int) {
:= 0
if < len() && [0] != '<' {
return false, -1
}
++
= skipSpace(, )
if < len() && [] == '/' {
++
}
= skipSpace(, )
:= 0
for ; < len(); , = +1, +1 {
if >= len() {
break
}
if strings.ToLower(string([]))[0] != [] {
return false, -1
}
}
if == len() {
return false, -1
}
:= skipUntilCharIgnoreQuotes(, , '>')
if >= {
return true,
}
return false, -1
}
func ( []byte, int) int {
for < len() && isspace([]) {
++
}
return
}
if [0] == '#' {
return true
}
if bytes.HasPrefix(, []byte("../")) {
return true
}
return false
}
func ( *HTMLRenderer) ( string) string {
for , := .headingIDs[]; ; , = .headingIDs[] {
:= fmt.Sprintf("%s-%d", , +1)
if , := .headingIDs[]; ! {
.headingIDs[] = + 1
=
} else {
= + "-1"
}
}
if , := .headingIDs[]; ! {
.headingIDs[] = 0
}
return
}
func ( *HTMLRenderer) ( []byte) []byte {
if .AbsolutePrefix != "" && isRelativeLink() && [0] != '.' {
:= .AbsolutePrefix
if [0] != '/' {
+= "/"
}
+= string()
return []byte()
}
return
}
func ( []string, HTMLFlags, []byte) []string {
if isRelativeLink() {
return
}
:= []string{}
if &NofollowLinks != 0 {
= append(, "nofollow")
}
if &NoreferrerLinks != 0 {
= append(, "noreferrer")
}
if &NoopenerLinks != 0 {
= append(, "noopener")
}
if &HrefTargetBlank != 0 {
= append(, "target=\"_blank\"")
}
if len() == 0 {
return
}
:= fmt.Sprintf("rel=%q", strings.Join(, " "))
return append(, )
}
func ( []byte) bool {
return bytes.HasPrefix(, []byte("mailto:"))
}
func ( HTMLFlags, []byte) bool {
if &SkipLinks != 0 {
return true
}
return &Safelink != 0 && !isSafeLink() && !isMailto()
}
func ( *Node) bool {
:= .Parent.Type
return != Link && != CodeBlock && != Code
}
func ( []string, []byte) []string {
if len() == 0 {
return
}
:= bytes.IndexAny(, "\t ")
if < 0 {
= len()
}
return append(, fmt.Sprintf("class=\"language-%s\"", [:]))
}
func ( *HTMLRenderer) ( io.Writer, []byte, []string) {
.Write()
if len() > 0 {
.Write(spaceBytes)
.Write([]byte(strings.Join(, " ")))
}
.Write(gtBytes)
.lastOutputLen = 1
}
func ( string, *Node) []byte {
:= + string(slugify(.Destination))
:= fmt.Sprintf(`<a href="#fn:%s">%d</a>`, , .NoteID)
return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, , ))
}
func ( string, []byte) []byte {
return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, , ))
}
func (, string, []byte) []byte {
const = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
return []byte(fmt.Sprintf(, , , ))
}
func ( *Node) bool {
if .Prev == nil {
return false
}
:= .Parent.ListData
return !.Tight && .ListFlags&ListTypeDefinition == 0
}
func ( *Node) bool {
:= .Parent.Parent
if == nil || .Type != List {
return false
}
:= .Tight || .Parent.ListFlags&ListTypeTerm != 0
return .Type == List &&
}
func ( CellAlignFlags) string {
switch {
case TableAlignmentLeft:
return "left"
case TableAlignmentRight:
return "right"
case TableAlignmentCenter:
return "center"
default:
return ""
}
}
func ( *HTMLRenderer) ( io.Writer, []byte) {
if .disableTags > 0 {
.Write(htmlTagRe.ReplaceAll(, []byte{}))
} else {
.Write()
}
.lastOutputLen = len()
}
func ( *HTMLRenderer) ( io.Writer) {
if .lastOutputLen > 0 {
.out(, nlBytes)
}
}
var (
nlBytes = []byte{'\n'}
gtBytes = []byte{'>'}
spaceBytes = []byte{' '}
)
var (
brTag = []byte("<br>")
brXHTMLTag = []byte("<br />")
emTag = []byte("<em>")
emCloseTag = []byte("</em>")
strongTag = []byte("<strong>")
strongCloseTag = []byte("</strong>")
delTag = []byte("<del>")
delCloseTag = []byte("</del>")
ttTag = []byte("<tt>")
ttCloseTag = []byte("</tt>")
aTag = []byte("<a")
aCloseTag = []byte("</a>")
preTag = []byte("<pre>")
preCloseTag = []byte("</pre>")
codeTag = []byte("<code>")
codeCloseTag = []byte("</code>")
pTag = []byte("<p>")
pCloseTag = []byte("</p>")
blockquoteTag = []byte("<blockquote>")
blockquoteCloseTag = []byte("</blockquote>")
hrTag = []byte("<hr>")
hrXHTMLTag = []byte("<hr />")
ulTag = []byte("<ul>")
ulCloseTag = []byte("</ul>")
olTag = []byte("<ol>")
olCloseTag = []byte("</ol>")
dlTag = []byte("<dl>")
dlCloseTag = []byte("</dl>")
liTag = []byte("<li>")
liCloseTag = []byte("</li>")
ddTag = []byte("<dd>")
ddCloseTag = []byte("</dd>")
dtTag = []byte("<dt>")
dtCloseTag = []byte("</dt>")
tableTag = []byte("<table>")
tableCloseTag = []byte("</table>")
tdTag = []byte("<td")
tdCloseTag = []byte("</td>")
thTag = []byte("<th")
thCloseTag = []byte("</th>")
theadTag = []byte("<thead>")
theadCloseTag = []byte("</thead>")
tbodyTag = []byte("<tbody>")
tbodyCloseTag = []byte("</tbody>")
trTag = []byte("<tr>")
trCloseTag = []byte("</tr>")
h1Tag = []byte("<h1")
h1CloseTag = []byte("</h1>")
h2Tag = []byte("<h2")
h2CloseTag = []byte("</h2>")
h3Tag = []byte("<h3")
h3CloseTag = []byte("</h3>")
h4Tag = []byte("<h4")
h4CloseTag = []byte("</h4>")
h5Tag = []byte("<h5")
h5CloseTag = []byte("</h5>")
h6Tag = []byte("<h6")
h6CloseTag = []byte("</h6>")
footnotesDivBytes = []byte("\n<div class=\"footnotes\">\n\n")
footnotesCloseDivBytes = []byte("\n</div>\n")
)
func ( int) ([]byte, []byte) {
if <= 1 {
return h1Tag, h1CloseTag
}
switch {
case 2:
return h2Tag, h2CloseTag
case 3:
return h3Tag, h3CloseTag
case 4:
return h4Tag, h4CloseTag
case 5:
return h5Tag, h5CloseTag
}
return h6Tag, h6CloseTag
}
func ( *HTMLRenderer) ( io.Writer) {
if .Flags&UseXHTML == 0 {
.out(, hrTag)
} else {
.out(, hrXHTMLTag)
}
}
func ( *HTMLRenderer) ( io.Writer, *Node, bool) WalkStatus {
:= []string{}
switch .Type {
case Text:
if .Flags&Smartypants != 0 {
var bytes.Buffer
escapeHTML(&, .Literal)
.sr.Process(, .Bytes())
} else {
if .Parent.Type == Link {
escLink(, .Literal)
} else {
escapeHTML(, .Literal)
}
}
case Softbreak:
case Hardbreak:
if .Flags&UseXHTML == 0 {
.out(, brTag)
} else {
.out(, brXHTMLTag)
}
.cr()
case Emph:
if {
.out(, emTag)
} else {
.out(, emCloseTag)
}
case Strong:
if {
.out(, strongTag)
} else {
.out(, strongCloseTag)
}
case Del:
if {
.out(, delTag)
} else {
.out(, delCloseTag)
}
case HTMLSpan:
if .Flags&SkipHTML != 0 {
break
}
.out(, .Literal)
:= .LinkData.Destination
if needSkipLink(.Flags, ) {
if {
.out(, ttTag)
} else {
.out(, ttCloseTag)
}
} else {
if {
= .addAbsPrefix()
var bytes.Buffer
.WriteString("href=\"")
escLink(&, )
.WriteByte('"')
= append(, .String())
if .NoteID != 0 {
.out(, footnoteRef(.FootnoteAnchorPrefix, ))
break
}
= appendLinkAttrs(, .Flags, )
if len(.LinkData.Title) > 0 {
var bytes.Buffer
.WriteString("title=\"")
escapeHTML(&, .LinkData.Title)
.WriteByte('"')
= append(, .String())
}
.tag(, aTag, )
} else {
if .NoteID != 0 {
break
}
.out(, aCloseTag)
}
}
case Image:
if .Flags&SkipImages != 0 {
return SkipChildren
}
if {
:= .LinkData.Destination
= .addAbsPrefix()
}
.disableTags++
} else {
.disableTags--
if .disableTags == 0 {
if .LinkData.Title != nil {
.out(, []byte(`" title="`))
escapeHTML(, .LinkData.Title)
}
.out(, []byte(`" />`))
}
}
case Code:
.out(, codeTag)
escapeHTML(, .Literal)
.out(, codeCloseTag)
case Document:
break
case Paragraph:
if skipParagraphTags() {
break
}
if .Prev != nil {
switch .Prev.Type {
case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
.cr()
}
}
if .Parent.Type == BlockQuote && .Prev == nil {
.cr()
}
.out(, pTag)
} else {
.out(, pCloseTag)
if !(.Parent.Type == Item && .Next == nil) {
.cr()
}
}
case BlockQuote:
if {
.cr()
.out(, blockquoteTag)
} else {
.out(, blockquoteCloseTag)
.cr()
}
case HTMLBlock:
if .Flags&SkipHTML != 0 {
break
}
.cr()
.out(, .Literal)
.cr()
case Heading:
:= .HTMLRendererParameters.HeadingLevelOffset + .Level
, := headingTagsFromLevel()
if {
if .IsTitleblock {
= append(, `class="title"`)
}
if .HeadingID != "" {
:= .ensureUniqueHeadingID(.HeadingID)
if .HeadingIDPrefix != "" {
= .HeadingIDPrefix +
}
if .HeadingIDSuffix != "" {
= + .HeadingIDSuffix
}
= append(, fmt.Sprintf(`id="%s"`, ))
}
.cr()
.tag(, , )
} else {
.out(, )
if !(.Parent.Type == Item && .Next == nil) {
.cr()
}
}
case HorizontalRule:
.cr()
.outHRTag()
.cr()
case List:
:= ulTag
:= ulCloseTag
if .ListFlags&ListTypeOrdered != 0 {
= olTag
= olCloseTag
}
if .ListFlags&ListTypeDefinition != 0 {
= dlTag
= dlCloseTag
}
if {
if .IsFootnotesList {
.out(, footnotesDivBytes)
.outHRTag()
.cr()
}
.cr()
if .Parent.Type == Item && .Parent.Parent.Tight {
.cr()
}
.tag(, [:len()-1], )
.cr()
} else {
if .Parent.Type == Item && .Next != nil {
.cr()
}
if .Parent.Type == Document || .Parent.Type == BlockQuote {
.cr()
}
if .IsFootnotesList {
.out(, footnotesCloseDivBytes)
}
}
case Item:
:= liTag
:= liCloseTag
if .ListFlags&ListTypeDefinition != 0 {
= ddTag
= ddCloseTag
}
if .ListFlags&ListTypeTerm != 0 {
= dtTag
= dtCloseTag
}
if {
if itemOpenCR() {
.cr()
}
if .ListData.RefLink != nil {
:= slugify(.ListData.RefLink)
.out(, footnoteItem(.FootnoteAnchorPrefix, ))
break
}
.out(, )
} else {
if .ListData.RefLink != nil {
:= slugify(.ListData.RefLink)
if .Flags&FootnoteReturnLinks != 0 {
.out(, footnoteReturnLink(.FootnoteAnchorPrefix, .FootnoteReturnLinkContents, ))
}
}
.out(, )
.cr()
}
case CodeBlock:
= appendLanguageAttr(, .Info)
.cr()
.out(, preTag)
.tag(, codeTag[:len(codeTag)-1], )
escapeHTML(, .Literal)
.out(, codeCloseTag)
.out(, preCloseTag)
if .Parent.Type != Item {
.cr()
}
case Table:
if {
.cr()
.out(, tableTag)
} else {
.out(, tableCloseTag)
.cr()
}
case TableCell:
:= tdTag
:= tdCloseTag
if .IsHeader {
= thTag
= thCloseTag
}
if {
:= cellAlignment(.Align)
if != "" {
= append(, fmt.Sprintf(`align="%s"`, ))
}
if .Prev == nil {
.cr()
}
.tag(, , )
} else {
.out(, )
.cr()
}
case TableHead:
if {
.cr()
.out(, theadTag)
} else {
.out(, theadCloseTag)
.cr()
}
case TableBody:
if {
.cr()
func ( *HTMLRenderer) ( io.Writer, *Node) {
.writeDocumentHeader()
if .Flags&TOC != 0 {
.writeTOC(, )
}
}
func ( *HTMLRenderer) ( io.Writer, *Node) {
if .Flags&CompletePage == 0 {
return
}
io.WriteString(, "\n</body>\n</html>\n")
}
func ( *HTMLRenderer) ( io.Writer) {
if .Flags&CompletePage == 0 {
return
}
:= ""
if .Flags&UseXHTML != 0 {
io.WriteString(, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
io.WriteString(, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
io.WriteString(, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
= " /"
} else {
io.WriteString(, "<!DOCTYPE html>\n")
io.WriteString(, "<html>\n")
}
io.WriteString(, "<head>\n")
io.WriteString(, " <title>")
if .Flags&Smartypants != 0 {
.sr.Process(, []byte(.Title))
} else {
escapeHTML(, []byte(.Title))
}
io.WriteString(, "</title>\n")
io.WriteString(, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
io.WriteString(, Version)
io.WriteString(, "\"")
io.WriteString(, )
io.WriteString(, ">\n")
io.WriteString(, " <meta charset=\"utf-8\"")
io.WriteString(, )
io.WriteString(, ">\n")
if .CSS != "" {
io.WriteString(, " <link rel=\"stylesheet\" type=\"text/css\" href=\"")
escapeHTML(, []byte(.CSS))
io.WriteString(, "\"")
io.WriteString(, )
io.WriteString(, ">\n")
}
if .Icon != "" {
io.WriteString(, " <link rel=\"icon\" type=\"image/x-icon\" href=\"")
escapeHTML(, []byte(.Icon))
io.WriteString(, "\"")
io.WriteString(, )
io.WriteString(, ">\n")
}
io.WriteString(, "</head>\n")
io.WriteString(, "<body>\n\n")
}
func ( *HTMLRenderer) ( io.Writer, *Node) {
:= bytes.Buffer{}
:= false
:= 0
:= 0
.Walk(func( *Node, bool) WalkStatus {
if .Type == Heading && !.HeadingData.IsTitleblock {
=
if {
.HeadingID = fmt.Sprintf("toc_%d", )
if .Level == {
.WriteString("</li>\n\n<li>")
} else if .Level < {
for .Level < {
--
.WriteString("</li>\n</ul>")
}
.WriteString("</li>\n\n<li>")
} else {
for .Level > {
++
.WriteString("\n<ul>\n<li>")
}
}
fmt.Fprintf(&, `<a href="#toc_%d">`, )
++
} else {
.WriteString("</a>")
}
return GoToNext
}
if {
return .RenderNode(&, , )
}
return GoToNext
})
for ; > 0; -- {
.WriteString("</li>\n</ul>")
}
if .Len() > 0 {
io.WriteString(, "<nav>\n")
.Write(.Bytes())
io.WriteString(, "\n\n</nav>\n")
}
.lastOutputLen = .Len()
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |