commit
db4c2c1320
18
ast/node.go
18
ast/node.go
|
@ -39,6 +39,16 @@ const (
|
|||
DocumentMatterBack
|
||||
)
|
||||
|
||||
// CitationTypes holds the type of a citation, informative, normative or suppressed
|
||||
type CitationTypes int
|
||||
|
||||
const (
|
||||
CitationTypeNone CitationTypes = iota
|
||||
CitationTypeSuppressed
|
||||
CitationTypeInformative
|
||||
CitationTypeNormative
|
||||
)
|
||||
|
||||
// Node defines an ast node
|
||||
type Node interface {
|
||||
AsContainer() *Container
|
||||
|
@ -240,6 +250,14 @@ type CrossReference struct {
|
|||
Destination []byte // Destination is where the reference points to
|
||||
}
|
||||
|
||||
// Citation is a citation node.
|
||||
type Citation struct {
|
||||
Leaf
|
||||
|
||||
Destination [][]byte // Destination is where the citation points to. Multiple ones are allowed.
|
||||
Type []CitationTypes // 1:1 mapping of destination and citation type
|
||||
}
|
||||
|
||||
// Image represents markdown image node
|
||||
type Image struct {
|
||||
Container
|
||||
|
|
|
@ -849,6 +849,24 @@ func (r *Renderer) matter(w io.Writer, node *ast.DocumentMatter, entering bool)
|
|||
r.documentMatter = node.Matter
|
||||
}
|
||||
|
||||
func (r *Renderer) citation(w io.Writer, node *ast.Citation) {
|
||||
for i, c := range node.Destination {
|
||||
attr := []string{`class="none"`}
|
||||
switch node.Type[i] {
|
||||
case ast.CitationTypeNormative:
|
||||
attr[0] = `class="normative"`
|
||||
case ast.CitationTypeInformative:
|
||||
attr[0] = `class="informative"`
|
||||
case ast.CitationTypeSuppressed:
|
||||
attr[0] = `class="suppressed"`
|
||||
}
|
||||
r.outTag(w, "<cite", attr)
|
||||
r.out(w, []byte("["))
|
||||
r.out(w, c)
|
||||
r.outs(w, "]</cite>")
|
||||
}
|
||||
}
|
||||
|
||||
// RenderNode renders a markdown node to HTML
|
||||
func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.WalkStatus {
|
||||
if r.opts.RenderNodeHook != nil {
|
||||
|
@ -882,6 +900,8 @@ func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Wal
|
|||
case *ast.CrossReference:
|
||||
link := &ast.Link{Destination: node.Destination}
|
||||
r.link(w, link, entering)
|
||||
case *ast.Citation:
|
||||
r.citation(w, node)
|
||||
case *ast.Image:
|
||||
if r.opts.Flags&SkipImages != 0 {
|
||||
return ast.SkipChildren
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gomarkdown/markdown/ast"
|
||||
)
|
||||
|
||||
// citation parses a citation. In its most simple form [@ref], we allow multiple
|
||||
// being separated by semicolons and a sub reference inside ala pandoc: [@ref p. 23].
|
||||
// Each citation can have a modifier: !, ? or - wich mean:
|
||||
//
|
||||
// ! - normative
|
||||
// ? - formative
|
||||
// - - suppressed
|
||||
func citation(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
// look for the matching closing bracket
|
||||
i := offset + 1
|
||||
for level := 1; level > 0 && i < len(data); i++ {
|
||||
switch {
|
||||
case data[i] == '\n':
|
||||
// no newlines allowed.
|
||||
return 0, nil
|
||||
|
||||
case data[i-1] == '\\':
|
||||
continue
|
||||
|
||||
case data[i] == '[':
|
||||
level++
|
||||
|
||||
case data[i] == ']':
|
||||
level--
|
||||
if level <= 0 {
|
||||
i-- // compensate for extra i++ in for loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i >= len(data) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
node := &ast.Citation{}
|
||||
|
||||
citations := bytes.Split(data[1:i], []byte(";"))
|
||||
for _, citation := range citations {
|
||||
citation = bytes.TrimSpace(citation)
|
||||
j := 0
|
||||
if citation[j] != '@' {
|
||||
// not a citation, drop out entirely.
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
citeType := ast.CitationTypeInformative
|
||||
j = 1
|
||||
switch citation[j] {
|
||||
case '!':
|
||||
citeType = ast.CitationTypeNormative
|
||||
j++
|
||||
case '?':
|
||||
citeType = ast.CitationTypeInformative
|
||||
j++
|
||||
case '-':
|
||||
citeType = ast.CitationTypeSuppressed
|
||||
j++
|
||||
}
|
||||
node.Destination = append(node.Destination, citation[j:])
|
||||
node.Type = append(node.Type, citeType)
|
||||
}
|
||||
|
||||
return i + 1, node
|
||||
}
|
|
@ -177,6 +177,7 @@ const (
|
|||
linkImg
|
||||
linkDeferredFootnote
|
||||
linkInlineFootnote
|
||||
linkCitation
|
||||
)
|
||||
|
||||
func isReferenceStyleLink(data []byte, pos int, t linkType) bool {
|
||||
|
@ -200,7 +201,7 @@ func maybeInlineFootnote(p *Parser, data []byte, offset int) (int, ast.Node) {
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
// '[': parse a link or an image or a footnote
|
||||
// '[': parse a link or an image or a footnote or a citation
|
||||
func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
// no links allowed inside regular links, footnote, and deferred footnotes
|
||||
if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
|
||||
|
@ -217,6 +218,10 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
|||
case offset >= 0 && data[offset] == '!':
|
||||
t = linkImg
|
||||
offset++
|
||||
// [@citation], [@-citation], [@?citation], [@!citation]
|
||||
case p.extensions&Mmark != 0 && len(data)-1 > offset && data[offset+1] == '@':
|
||||
t = linkCitation
|
||||
// [text] == regular link
|
||||
// ^[text] == inline footnote
|
||||
// [^refId] == deferred footnote
|
||||
case p.extensions&Footnotes != 0:
|
||||
|
@ -226,13 +231,16 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
|||
} else if len(data)-1 > offset && data[offset+1] == '^' {
|
||||
t = linkDeferredFootnote
|
||||
}
|
||||
// [text] == regular link
|
||||
default:
|
||||
t = linkNormal
|
||||
}
|
||||
|
||||
data = data[offset:]
|
||||
|
||||
if t == linkCitation {
|
||||
return citation(p, data, 0)
|
||||
}
|
||||
|
||||
var (
|
||||
i = 1
|
||||
noteID int
|
||||
|
|
|
@ -44,3 +44,24 @@ Caption: Shakespeare.
|
|||
</blockquote>
|
||||
<figcaption>Shakespeare.</figcaption>
|
||||
</figure>
|
||||
---
|
||||
# Test Citations
|
||||
[@RFC1034]
|
||||
---
|
||||
<h1>Test_Citations</h1>
|
||||
|
||||
<p><cite class="informative">[RFC1034]</cite></p>
|
||||
---
|
||||
# Test Multiple Citations
|
||||
[@RFC1034; @!RFC1035]
|
||||
---
|
||||
<h1>Test Multiple Citations</h1>
|
||||
|
||||
<p><cite class="informative">[RFC1034]</cite><cite class="normative">[RFC1035]</cite></p>
|
||||
---
|
||||
# Test Multiple Citations with modifier
|
||||
[@-RFC1034] [@?RFC1035]
|
||||
---
|
||||
<h1>Test Multiple Citations with modifier</h1>
|
||||
|
||||
<p><cite class="suppressed">[RFC1034]</cite> <cite class="informative">[RFC1035]</cite></p>
|
||||
|
|
Loading…
Reference in New Issue