Merge pull request #33 from mmarkdown/citations

Citations
This commit is contained in:
Miek Gieben 2018-08-03 19:14:36 +01:00 committed by GitHub
commit db4c2c1320
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 2 deletions

View File

@ -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

View File

@ -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

72
parser/citation.go Normal file
View File

@ -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
}

View File

@ -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

21
testdata/mmark.test vendored
View File

@ -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>