mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Closes #33 : Now NodeRenderers render attributes
This commit is contained in:
parent
0a62f6ae63
commit
7d8bee11ca
6 changed files with 309 additions and 32 deletions
|
|
@ -8,25 +8,25 @@
|
||||||
|
|
||||||
## Title3 ## {#id_3 .class-3}
|
## Title3 ## {#id_3 .class-3}
|
||||||
|
|
||||||
## Title4 ## {attr3=value3}
|
## Title4 ## {data-attr3=value3}
|
||||||
|
|
||||||
## Title5 ## {#id_5 attr5=value5}
|
## Title5 ## {#id_5 data-attr5=value5}
|
||||||
|
|
||||||
## Title6 ## {#id_6 .class6 attr6=value6}
|
## Title6 ## {#id_6 .class6 data-attr6=value6}
|
||||||
|
|
||||||
## Title7 ## {#id_7 attr7="value \"7"}
|
## Title7 ## {#id_7 data-attr7="value \"7"}
|
||||||
|
|
||||||
## Title8 {#id .className attrName=attrValue class="class1 class2"}
|
## Title8 {#id .className data-attrName=attrValue class="class1 class2"}
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<h2 id="title-0">Title 0</h2>
|
<h2 id="title-0">Title 0</h2>
|
||||||
<h2 id="id_1" class="class-1">Title1</h2>
|
<h2 id="id_1" class="class-1">Title1</h2>
|
||||||
<h2 id="id_2">Title2</h2>
|
<h2 id="id_2">Title2</h2>
|
||||||
<h2 id="id_3" class="class-3">Title3</h2>
|
<h2 id="id_3" class="class-3">Title3</h2>
|
||||||
<h2 attr3="value3" id="title4">Title4</h2>
|
<h2 data-attr3="value3" id="title4">Title4</h2>
|
||||||
<h2 id="id_5" attr5="value5">Title5</h2>
|
<h2 id="id_5" data-attr5="value5">Title5</h2>
|
||||||
<h2 id="id_6" class="class6" attr6="value6">Title6</h2>
|
<h2 id="id_6" class="class6" data-attr6="value6">Title6</h2>
|
||||||
<h2 id="id_7" attr7="value "7">Title7</h2>
|
<h2 id="id_7" data-attr7="value "7">Title7</h2>
|
||||||
<h2 id="id" class="className class1 class2" attrName="attrValue">Title8</h2>
|
<h2 id="id" class="className class1 class2" data-attrName="attrValue">Title8</h2>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
2
|
2
|
||||||
|
|
|
||||||
|
|
@ -196,31 +196,56 @@ func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFunc
|
||||||
reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
|
reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefinitionListAttributeFilter defines attribute names which dl elements can have.
|
||||||
|
var DefinitionListAttributeFilter = html.GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<dl>\n")
|
if n.Attributes() != nil {
|
||||||
|
_, _ = w.WriteString("<dl")
|
||||||
|
html.RenderAttributes(w, n, DefinitionListAttributeFilter)
|
||||||
|
_, _ = w.WriteString(">\n")
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("<dl>\n")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</dl>\n")
|
_, _ = w.WriteString("</dl>\n")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefinitionTermAttributeFilter defines attribute names which dd elements can have.
|
||||||
|
var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<dt>")
|
if n.Attributes() != nil {
|
||||||
|
_, _ = w.WriteString("<dt")
|
||||||
|
html.RenderAttributes(w, n, DefinitionTermAttributeFilter)
|
||||||
|
_ = w.WriteByte('>')
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("<dt>")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</dt>\n")
|
_, _ = w.WriteString("</dt>\n")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefinitionDescriptionAttributeFilter defines attribute names which dd elements can have.
|
||||||
|
var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.DefinitionDescription)
|
n := node.(*ast.DefinitionDescription)
|
||||||
|
_, _ = w.WriteString("<dd")
|
||||||
|
if n.Attributes() != nil {
|
||||||
|
html.RenderAttributes(w, n, DefinitionDescriptionAttributeFilter)
|
||||||
|
}
|
||||||
if n.IsTight {
|
if n.IsTight {
|
||||||
_, _ = w.WriteString("<dd>")
|
_, _ = w.WriteString(">")
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("<dd>\n")
|
_, _ = w.WriteString(">\n")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</dd>\n")
|
_, _ = w.WriteString("</dd>\n")
|
||||||
|
|
|
||||||
|
|
@ -268,8 +268,11 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString(`<li id="fn:`)
|
_, _ = w.WriteString(`<li id="fn:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
_, _ = w.WriteString(`" role="doc-endnote">`)
|
_, _ = w.WriteString(`" role="doc-endnote"`)
|
||||||
_, _ = w.WriteString("\n")
|
if node.Attributes() != nil {
|
||||||
|
html.RenderAttributes(w, node, html.ListItemAttributeFilter)
|
||||||
|
}
|
||||||
|
_, _ = w.WriteString(">\n")
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</li>\n")
|
_, _ = w.WriteString("</li>\n")
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +287,11 @@ func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byt
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<")
|
_, _ = w.WriteString("<")
|
||||||
_, _ = w.WriteString(tag)
|
_, _ = w.WriteString(tag)
|
||||||
_, _ = w.WriteString(` class="footnotes" role="doc-endnotes">`)
|
_, _ = w.WriteString(` class="footnotes" role="doc-endnotes"`)
|
||||||
|
if node.Attributes() != nil {
|
||||||
|
html.RenderAttributes(w, node, html.GlobalAttributeFilter)
|
||||||
|
}
|
||||||
|
_ = w.WriteByte('>')
|
||||||
if r.Config.XHTML {
|
if r.Config.XHTML {
|
||||||
_, _ = w.WriteString("\n<hr />\n")
|
_, _ = w.WriteString("\n<hr />\n")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,20 @@ func (r *StrikethroughHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncR
|
||||||
reg.Register(ast.KindStrikethrough, r.renderStrikethrough)
|
reg.Register(ast.KindStrikethrough, r.renderStrikethrough)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StrikethroughAttributeFilter defines attribute names which dd elements can have.
|
||||||
|
var StrikethroughAttributeFilter = html.GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *StrikethroughHTMLRenderer) renderStrikethrough(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *StrikethroughHTMLRenderer) renderStrikethrough(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString("<del>")
|
if n.Attributes() != nil {
|
||||||
|
_, _ = w.WriteString("<del")
|
||||||
|
html.RenderAttributes(w, n, StrikethroughAttributeFilter)
|
||||||
|
_ = w.WriteByte('>')
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("<del>")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</del>")
|
_, _ = w.WriteString("</del>")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,18 +194,43 @@ func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalAttributeFilter defines attribute names which any elements can have.
|
||||||
|
var GlobalAttributeFilter = util.NewBytesFilter(
|
||||||
|
[]byte("accesskey"),
|
||||||
|
[]byte("autocapitalize"),
|
||||||
|
[]byte("class"),
|
||||||
|
[]byte("contenteditable"),
|
||||||
|
[]byte("contextmenu"),
|
||||||
|
[]byte("dir"),
|
||||||
|
[]byte("draggable"),
|
||||||
|
[]byte("dropzone"),
|
||||||
|
[]byte("hidden"),
|
||||||
|
[]byte("id"),
|
||||||
|
[]byte("itemprop"),
|
||||||
|
[]byte("lang"),
|
||||||
|
[]byte("slot"),
|
||||||
|
[]byte("spellcheck"),
|
||||||
|
[]byte("style"),
|
||||||
|
[]byte("tabindex"),
|
||||||
|
[]byte("title"),
|
||||||
|
[]byte("translate"),
|
||||||
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HeadingAttributeFilter defines attribute names which heading elements can have
|
||||||
|
var HeadingAttributeFilter = GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.Heading)
|
n := node.(*ast.Heading)
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<h")
|
_, _ = w.WriteString("<h")
|
||||||
_ = w.WriteByte("0123456"[n.Level])
|
_ = w.WriteByte("0123456"[n.Level])
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
r.RenderAttributes(w, node)
|
RenderAttributes(w, node, HeadingAttributeFilter)
|
||||||
}
|
}
|
||||||
_ = w.WriteByte('>')
|
_ = w.WriteByte('>')
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -216,9 +241,20 @@ func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node,
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockquoteAttributeFilter defines attribute names which blockquote elements can have
|
||||||
|
var BlockquoteAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
|
[]byte("cite"),
|
||||||
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<blockquote>\n")
|
if n.Attributes() != nil {
|
||||||
|
_, _ = w.WriteString("<blockquote")
|
||||||
|
RenderAttributes(w, n, BlockquoteAttributeFilter)
|
||||||
|
_ = w.WriteByte('>')
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("<blockquote>\n")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</blockquote>\n")
|
_, _ = w.WriteString("</blockquote>\n")
|
||||||
}
|
}
|
||||||
|
|
@ -278,6 +314,12 @@ func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Nod
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAttributeFilter defines attribute names which list elements can have.
|
||||||
|
var ListAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
|
[]byte("start"),
|
||||||
|
[]byte("reversed"),
|
||||||
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.List)
|
n := node.(*ast.List)
|
||||||
tag := "ul"
|
tag := "ul"
|
||||||
|
|
@ -288,10 +330,12 @@ func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
_ = w.WriteByte('<')
|
_ = w.WriteByte('<')
|
||||||
_, _ = w.WriteString(tag)
|
_, _ = w.WriteString(tag)
|
||||||
if n.IsOrdered() && n.Start != 1 {
|
if n.IsOrdered() && n.Start != 1 {
|
||||||
fmt.Fprintf(w, " start=\"%d\">\n", n.Start)
|
fmt.Fprintf(w, " start=\"%d\"", n.Start)
|
||||||
} else {
|
|
||||||
_, _ = w.WriteString(">\n")
|
|
||||||
}
|
}
|
||||||
|
if n.Attributes() != nil {
|
||||||
|
RenderAttributes(w, n, ListAttributeFilter)
|
||||||
|
}
|
||||||
|
_, _ = w.WriteString(">\n")
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</")
|
_, _ = w.WriteString("</")
|
||||||
_, _ = w.WriteString(tag)
|
_, _ = w.WriteString(tag)
|
||||||
|
|
@ -300,9 +344,20 @@ func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListItemAttributeFilter defines attribute names which list item elements can have.
|
||||||
|
var ListItemAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
|
[]byte("value"),
|
||||||
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<li>")
|
if n.Attributes() != nil {
|
||||||
|
_, _ = w.WriteString("<li")
|
||||||
|
RenderAttributes(w, n, ListItemAttributeFilter)
|
||||||
|
_ = w.WriteByte('>')
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("<li>")
|
||||||
|
}
|
||||||
fc := n.FirstChild()
|
fc := n.FirstChild()
|
||||||
if fc != nil {
|
if fc != nil {
|
||||||
if _, ok := fc.(*ast.TextBlock); !ok {
|
if _, ok := fc.(*ast.TextBlock); !ok {
|
||||||
|
|
@ -315,9 +370,18 @@ func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n ast.Node, e
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParagraphAttributeFilter defines attribute names which paragraph elements can have.
|
||||||
|
var ParagraphAttributeFilter = GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<p>")
|
if n.Attributes() != nil {
|
||||||
|
_, _ = w.WriteString("<p")
|
||||||
|
RenderAttributes(w, n, ParagraphAttributeFilter)
|
||||||
|
_ = w.WriteByte('>')
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("<p>")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</p>\n")
|
_, _ = w.WriteString("</p>\n")
|
||||||
}
|
}
|
||||||
|
|
@ -333,18 +397,41 @@ func (r *Renderer) renderTextBlock(w util.BufWriter, source []byte, n ast.Node,
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ThematicAttributeFilter defines attribute names which hr elements can have.
|
||||||
|
var ThematicAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
|
[]byte("align"),
|
||||||
|
[]byte("color"),
|
||||||
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderThematicBreak(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderThematicBreak(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
_, _ = w.WriteString("<hr")
|
||||||
|
if n.Attributes() != nil {
|
||||||
|
RenderAttributes(w, n, ThematicAttributeFilter)
|
||||||
|
}
|
||||||
if r.XHTML {
|
if r.XHTML {
|
||||||
_, _ = w.WriteString("<hr />\n")
|
_, _ = w.WriteString(" />\n")
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("<hr>\n")
|
_, _ = w.WriteString(">\n")
|
||||||
}
|
}
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LinkAttributeFilter defines attribute names which link elements can have.
|
||||||
|
var LinkAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
|
[]byte("download"),
|
||||||
|
// []byte("href"),
|
||||||
|
[]byte("hreflang"),
|
||||||
|
[]byte("media"),
|
||||||
|
[]byte("ping"),
|
||||||
|
[]byte("referrerpolicy"),
|
||||||
|
[]byte("rel"),
|
||||||
|
[]byte("shape"),
|
||||||
|
[]byte("target"),
|
||||||
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.AutoLink)
|
n := node.(*ast.AutoLink)
|
||||||
if !entering {
|
if !entering {
|
||||||
|
|
@ -357,15 +444,30 @@ func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node
|
||||||
_, _ = w.WriteString("mailto:")
|
_, _ = w.WriteString("mailto:")
|
||||||
}
|
}
|
||||||
_, _ = w.Write(util.EscapeHTML(util.URLEscape(url, false)))
|
_, _ = w.Write(util.EscapeHTML(util.URLEscape(url, false)))
|
||||||
_, _ = w.WriteString(`">`)
|
if n.Attributes() != nil {
|
||||||
|
_ = w.WriteByte('"')
|
||||||
|
RenderAttributes(w, n, LinkAttributeFilter)
|
||||||
|
_ = w.WriteByte('>')
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString(`">`)
|
||||||
|
}
|
||||||
_, _ = w.Write(util.EscapeHTML(label))
|
_, _ = w.Write(util.EscapeHTML(label))
|
||||||
_, _ = w.WriteString(`</a>`)
|
_, _ = w.WriteString(`</a>`)
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CodeAttributeFilter defines attribute names which code elements can have.
|
||||||
|
var CodeAttributeFilter = GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<code>")
|
if n.Attributes() != nil {
|
||||||
|
_, _ = w.WriteString("<code")
|
||||||
|
RenderAttributes(w, n, CodeAttributeFilter)
|
||||||
|
_ = w.WriteByte('>')
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("<code>")
|
||||||
|
}
|
||||||
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
segment := c.(*ast.Text).Segment
|
segment := c.(*ast.Text).Segment
|
||||||
value := segment.Value(source)
|
value := segment.Value(source)
|
||||||
|
|
@ -384,6 +486,9 @@ func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, e
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EmphasisAttributeFilter defines attribute names which emphasis elements can have.
|
||||||
|
var EmphasisAttributeFilter = GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.Emphasis)
|
n := node.(*ast.Emphasis)
|
||||||
tag := "em"
|
tag := "em"
|
||||||
|
|
@ -393,6 +498,9 @@ func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node
|
||||||
if entering {
|
if entering {
|
||||||
_ = w.WriteByte('<')
|
_ = w.WriteByte('<')
|
||||||
_, _ = w.WriteString(tag)
|
_, _ = w.WriteString(tag)
|
||||||
|
if n.Attributes() != nil {
|
||||||
|
RenderAttributes(w, n, EmphasisAttributeFilter)
|
||||||
|
}
|
||||||
_ = w.WriteByte('>')
|
_ = w.WriteByte('>')
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</")
|
_, _ = w.WriteString("</")
|
||||||
|
|
@ -415,12 +523,34 @@ func (r *Renderer) renderLink(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
r.Writer.Write(w, n.Title)
|
r.Writer.Write(w, n.Title)
|
||||||
_ = w.WriteByte('"')
|
_ = w.WriteByte('"')
|
||||||
}
|
}
|
||||||
|
if n.Attributes() != nil {
|
||||||
|
RenderAttributes(w, n, LinkAttributeFilter)
|
||||||
|
}
|
||||||
_ = w.WriteByte('>')
|
_ = w.WriteByte('>')
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.WriteString("</a>")
|
_, _ = w.WriteString("</a>")
|
||||||
}
|
}
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageAttributeFilter defines attribute names which image elements can have.
|
||||||
|
var ImageAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
|
[]byte("align"),
|
||||||
|
[]byte("border"),
|
||||||
|
[]byte("crossorigin"),
|
||||||
|
[]byte("decoding"),
|
||||||
|
[]byte("height"),
|
||||||
|
[]byte("importance"),
|
||||||
|
[]byte("intrinsicsize"),
|
||||||
|
[]byte("ismap"),
|
||||||
|
[]byte("loading"),
|
||||||
|
[]byte("referrerpolicy"),
|
||||||
|
[]byte("sizes"),
|
||||||
|
[]byte("srcset"),
|
||||||
|
[]byte("usemap"),
|
||||||
|
[]byte("width"),
|
||||||
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
|
|
@ -438,6 +568,9 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, e
|
||||||
r.Writer.Write(w, n.Title)
|
r.Writer.Write(w, n.Title)
|
||||||
_ = w.WriteByte('"')
|
_ = w.WriteByte('"')
|
||||||
}
|
}
|
||||||
|
if n.Attributes() != nil {
|
||||||
|
RenderAttributes(w, n, ImageAttributeFilter)
|
||||||
|
}
|
||||||
if r.XHTML {
|
if r.XHTML {
|
||||||
_, _ = w.WriteString(" />")
|
_, _ = w.WriteString(" />")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -503,13 +636,22 @@ func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node,
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderAttributes renders given node's attributes.
|
var dataPrefix = []byte("data-")
|
||||||
func (r *Renderer) RenderAttributes(w util.BufWriter, node ast.Node) {
|
|
||||||
|
|
||||||
|
// RenderAttributes renders given node's attributes.
|
||||||
|
// You can specify attribute names to render by the filter.
|
||||||
|
// If filter is nil, RenderAttributes renders all attributes.
|
||||||
|
func RenderAttributes(w util.BufWriter, node ast.Node, filter util.BytesFilter) {
|
||||||
for _, attr := range node.Attributes() {
|
for _, attr := range node.Attributes() {
|
||||||
|
if filter != nil && !filter.Contains(attr.Name) {
|
||||||
|
if !bytes.HasPrefix(attr.Name, dataPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
_, _ = w.WriteString(" ")
|
_, _ = w.WriteString(" ")
|
||||||
_, _ = w.Write(attr.Name)
|
_, _ = w.Write(attr.Name)
|
||||||
_, _ = w.WriteString(`="`)
|
_, _ = w.WriteString(`="`)
|
||||||
|
// TODO: convert numeric values to strings
|
||||||
_, _ = w.Write(util.EscapeHTML(attr.Value.([]byte)))
|
_, _ = w.Write(util.EscapeHTML(attr.Value.([]byte)))
|
||||||
_ = w.WriteByte('"')
|
_ = w.WriteByte('"')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
94
util/util.go
94
util/util.go
|
|
@ -798,3 +798,97 @@ func (s PrioritizedSlice) Remove(v interface{}) PrioritizedSlice {
|
||||||
func Prioritized(v interface{}, priority int) PrioritizedValue {
|
func Prioritized(v interface{}, priority int) PrioritizedValue {
|
||||||
return PrioritizedValue{v, priority}
|
return PrioritizedValue{v, priority}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bytesHash(b []byte) uint64 {
|
||||||
|
var hash uint64 = 5381
|
||||||
|
for _, c := range b {
|
||||||
|
hash = ((hash << 5) + hash) + uint64(c)
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesFilter is a efficient data structure for checking whether bytes exist or not.
|
||||||
|
// BytesFilter is thread-safe.
|
||||||
|
type BytesFilter interface {
|
||||||
|
// Add adds given bytes to this set.
|
||||||
|
Add([]byte)
|
||||||
|
|
||||||
|
// Contains return true if this set contains given bytes, otherwise false.
|
||||||
|
Contains([]byte) bool
|
||||||
|
|
||||||
|
// Extend copies this filter and adds given bytes to new filter.
|
||||||
|
Extend(...[]byte) BytesFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
type bytesFilter struct {
|
||||||
|
chars [256]uint8
|
||||||
|
threshold int
|
||||||
|
slots [][][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBytesFilter(elements ...[]byte) BytesFilter {
|
||||||
|
s := &bytesFilter{
|
||||||
|
threshold: 3,
|
||||||
|
slots: make([][][]byte, 64),
|
||||||
|
}
|
||||||
|
for _, element := range elements {
|
||||||
|
s.Add(element)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *bytesFilter) Add(b []byte) {
|
||||||
|
l := len(b)
|
||||||
|
m := s.threshold
|
||||||
|
if l < s.threshold {
|
||||||
|
m = l
|
||||||
|
}
|
||||||
|
for i := 0; i < m; i++ {
|
||||||
|
s.chars[b[i]] |= 1 << i
|
||||||
|
}
|
||||||
|
h := bytesHash(b) % uint64(len(s.slots))
|
||||||
|
slot := s.slots[h]
|
||||||
|
if slot == nil {
|
||||||
|
slot = [][]byte{}
|
||||||
|
}
|
||||||
|
s.slots[h] = append(slot, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *bytesFilter) Extend(bs ...[]byte) BytesFilter {
|
||||||
|
newFilter := NewBytesFilter().(*bytesFilter)
|
||||||
|
newFilter.chars = s.chars
|
||||||
|
newFilter.threshold = s.threshold
|
||||||
|
for k, v := range s.slots {
|
||||||
|
newSlot := make([][]byte, len(v))
|
||||||
|
copy(newSlot, v)
|
||||||
|
newFilter.slots[k] = v
|
||||||
|
}
|
||||||
|
for _, b := range bs {
|
||||||
|
newFilter.Add(b)
|
||||||
|
}
|
||||||
|
return newFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *bytesFilter) Contains(b []byte) bool {
|
||||||
|
l := len(b)
|
||||||
|
m := s.threshold
|
||||||
|
if l < s.threshold {
|
||||||
|
m = l
|
||||||
|
}
|
||||||
|
for i := 0; i < m; i++ {
|
||||||
|
if (s.chars[b[i]] & (1 << i)) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h := bytesHash(b) % uint64(len(s.slots))
|
||||||
|
slot := s.slots[h]
|
||||||
|
if slot == nil || len(slot) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, element := range slot {
|
||||||
|
if bytes.Equal(element, b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue