Rename option names

This commit is contained in:
yuin 2019-05-04 22:25:41 +09:00
parent 45222d6b03
commit bdde5e8472
6 changed files with 113 additions and 83 deletions

View file

@ -67,7 +67,7 @@ Customize a parser and a renderer:
md := goldmark.NewMarkdown( md := goldmark.NewMarkdown(
goldmark.WithExtensions(extension.GFM), goldmark.WithExtensions(extension.GFM),
goldmark.WithParserOptions( goldmark.WithParserOptions(
parser.WithHeadingID(), parser.WithAutoHeadingID(),
), ),
goldmark.WithRendererOptions( goldmark.WithRendererOptions(
html.WithHardWraps(), html.WithHardWraps(),
@ -90,7 +90,7 @@ Parser and Renderer options
| `parser.WithBlockParsers` | A `util.PrioritizedSlice` whose elements are `parser.BlockParser` | Parsers for parsing block level elements. | | `parser.WithBlockParsers` | A `util.PrioritizedSlice` whose elements are `parser.BlockParser` | Parsers for parsing block level elements. |
| `parser.WithInlineParsers` | A `util.PrioritizedSlice` whose elements are `parser.InlineParser` | Parsers for parsing inline level elements. | | `parser.WithInlineParsers` | A `util.PrioritizedSlice` whose elements are `parser.InlineParser` | Parsers for parsing inline level elements. |
| `parser.WithParagraphTransformers` | A `util.PrioritizedSlice` whose elements are `parser.ParagraphTransformer` | Transformers for transforming paragraph nodes. | | `parser.WithParagraphTransformers` | A `util.PrioritizedSlice` whose elements are `parser.ParagraphTransformer` | Transformers for transforming paragraph nodes. |
| `parser.WithHeadingID` | `-` | Enables custom heading ids( `{#custom-id}` ) and auto heading ids. | | `parser.WithAutoHeadingID` | `-` | Enables auto heading ids. |
| `parser.WithFilterTags` | `...string` | HTML tag names forbidden in HTML blocks and Raw HTMLs. | | `parser.WithFilterTags` | `...string` | HTML tag names forbidden in HTML blocks and Raw HTMLs. |
### HTML Renderer options ### HTML Renderer options

View file

@ -320,13 +320,21 @@ func (n *BaseNode) Text(source []byte) []byte {
func (n *BaseNode) SetAttribute(name, value []byte) { func (n *BaseNode) SetAttribute(name, value []byte) {
if n.attributes == nil { if n.attributes == nil {
n.attributes = make([]Attribute, 0, 10) n.attributes = make([]Attribute, 0, 10)
n.attributes = append(n.attributes, Attribute{name, value}) } else {
return for i, a := range n.attributes {
if bytes.Equal(a.Name, name) {
n.attributes[i].Name = name
n.attributes[i].Value = value
return
}
}
} }
for i, a := range n.attributes { if len(name) == 1 {
if bytes.Equal(a.Name, name) { if name[0] == '#' {
n.attributes[i].Name = name n.attributes = append(n.attributes, Attribute{attrNameID, value})
n.attributes[i].Value = value return
} else if name[0] == '.' {
n.attributes = append(n.attributes, Attribute{attrNameClass, value})
return return
} }
} }

View file

@ -9,14 +9,14 @@ import (
// A HeadingConfig struct is a data structure that holds configuration of the renderers related to headings. // A HeadingConfig struct is a data structure that holds configuration of the renderers related to headings.
type HeadingConfig struct { type HeadingConfig struct {
HeadingID bool AutoHeadingID bool
} }
// SetOption implements SetOptioner. // SetOption implements SetOptioner.
func (b *HeadingConfig) SetOption(name OptionName, value interface{}) { func (b *HeadingConfig) SetOption(name OptionName, value interface{}) {
switch name { switch name {
case HeadingID: case AutoHeadingID:
b.HeadingID = true b.AutoHeadingID = true
} }
} }
@ -25,27 +25,27 @@ type HeadingOption interface {
SetHeadingOption(*HeadingConfig) SetHeadingOption(*HeadingConfig)
} }
// HeadingID is an option name that enables custom and auto IDs for headings. // AutoHeadingID is an option name that enables auto IDs for headings.
var HeadingID OptionName = "HeadingID" var AutoHeadingID OptionName = "AutoHeadingID"
type withHeadingID struct { type withAutoHeadingID struct {
} }
func (o *withHeadingID) SetConfig(c *Config) { func (o *withAutoHeadingID) SetConfig(c *Config) {
c.Options[HeadingID] = true c.Options[AutoHeadingID] = true
} }
func (o *withHeadingID) SetHeadingOption(p *HeadingConfig) { func (o *withAutoHeadingID) SetHeadingOption(p *HeadingConfig) {
p.HeadingID = true p.AutoHeadingID = true
} }
// WithHeadingID is a functional option that enables custom heading ids and // WithAutoHeadingID is a functional option that enables custom heading ids and
// auto generated heading ids. // auto generated heading ids.
func WithHeadingID() interface { func WithAutoHeadingID() interface {
Option Option
HeadingOption HeadingOption
} { } {
return &withHeadingID{} return &withAutoHeadingID{}
} }
var atxHeadingRegexp = regexp.MustCompile(`^[ ]{0,3}(#{1,6})(?:\s+(.*?)\s*([\s]#+\s*)?)?\n?$`) var atxHeadingRegexp = regexp.MustCompile(`^[ ]{0,3}(#{1,6})(?:\s+(.*?)\s*([\s]#+\s*)?)?\n?$`)
@ -104,10 +104,10 @@ func (b *atxHeadingParser) Continue(node ast.Node, reader text.Reader, pc Contex
} }
func (b *atxHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) { func (b *atxHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) {
if !b.HeadingID { if !b.AutoHeadingID {
return return
} }
parseOrGenerateHeadingID(node.(*ast.Heading), reader, pc) generateAutoHeadingID(node.(*ast.Heading), reader, pc)
} }
func (b *atxHeadingParser) CanInterruptParagraph() bool { func (b *atxHeadingParser) CanInterruptParagraph() bool {
@ -118,30 +118,13 @@ func (b *atxHeadingParser) CanAcceptIndentedLine() bool {
return false return false
} }
var headingIDRegexp = regexp.MustCompile(`^(.*[^\\])({#([^}]+)}\s*)\n?$`) var attrAutoHeadingIDPrefix = []byte("heading")
var headingIDMap = NewContextKey() var attrNameID = []byte("#")
var attrNameID = []byte("id")
func parseOrGenerateHeadingID(node *ast.Heading, reader text.Reader, pc Context) { func generateAutoHeadingID(node *ast.Heading, reader text.Reader, pc Context) {
existsv := pc.Get(headingIDMap)
var exists map[string]bool
if existsv == nil {
exists = map[string]bool{}
pc.Set(headingIDMap, exists)
} else {
exists = existsv.(map[string]bool)
}
lastIndex := node.Lines().Len() - 1 lastIndex := node.Lines().Len() - 1
lastLine := node.Lines().At(lastIndex) lastLine := node.Lines().At(lastIndex)
line := lastLine.Value(reader.Source()) line := lastLine.Value(reader.Source())
m := headingIDRegexp.FindSubmatchIndex(line) headingID := pc.IDs().Generate(line, attrAutoHeadingIDPrefix)
var headingID []byte
if m != nil {
headingID = line[m[6]:m[7]]
lastLine.Stop -= m[5] - m[4]
node.Lines().Set(lastIndex, lastLine)
} else {
headingID = util.GenerateLinkID(line, exists)
}
node.SetAttribute(attrNameID, headingID) node.SetAttribute(attrNameID, headingID)
} }

View file

@ -53,6 +53,67 @@ func (r *reference) String() string {
return fmt.Sprintf("Reference{Label:%s, Destination:%s, Title:%s}", r.label, r.destination, r.title) return fmt.Sprintf("Reference{Label:%s, Destination:%s, Title:%s}", r.label, r.destination, r.title)
} }
// An IDs interface is a collection of the element ids.
type IDs interface {
// Generate generates a new element id.
Generate(value, prefix []byte) []byte
// Put puts a given element id to the used ids table.
Put(value []byte)
}
type ids struct {
values map[string]bool
}
func newIDs() IDs {
return &ids{
values: map[string]bool{},
}
}
func (s *ids) Generate(value, prefix []byte) []byte {
value = util.TrimLeftSpace(value)
value = util.TrimRightSpace(value)
result := []byte{}
for i := 0; i < len(value); {
v := value[i]
l := util.UTF8Len(v)
i += int(l)
if l != 1 {
continue
}
if util.IsAlphaNumeric(v) {
result = append(result, v)
} else if util.IsSpace(v) {
result = append(result, '-')
}
}
if len(result) == 0 {
if prefix != nil {
result = append(make([]byte, 0, len(prefix)), prefix...)
} else {
result = []byte("id")
}
}
if _, ok := s.values[util.BytesToReadOnlyString(result)]; !ok {
s.values[util.BytesToReadOnlyString(result)] = true
return result
}
for i := 1; ; i++ {
newResult := fmt.Sprintf("%s%d", result, i)
if _, ok := s.values[newResult]; !ok {
s.values[newResult] = true
return []byte(newResult)
}
}
}
func (s *ids) Put(value []byte) {
s.values[util.BytesToReadOnlyString(value)] = true
}
// ContextKey is a key that is used to set arbitary values to the context. // ContextKey is a key that is used to set arbitary values to the context.
type ContextKey int type ContextKey int
@ -87,6 +148,9 @@ type Context interface {
// References returns a list of references. // References returns a list of references.
References() []Reference References() []Reference
// IDs returns a collection of the element ids.
IDs() IDs
// BlockOffset returns a first non-space character position on current line. // BlockOffset returns a first non-space character position on current line.
// This value is valid only for BlockParser.Open. // This value is valid only for BlockParser.Open.
BlockOffset() int BlockOffset() int
@ -123,6 +187,7 @@ type Context interface {
type parseContext struct { type parseContext struct {
store []interface{} store []interface{}
ids IDs
refs map[string]Reference refs map[string]Reference
blockOffset int blockOffset int
delimiters *Delimiter delimiters *Delimiter
@ -135,6 +200,7 @@ func NewContext() Context {
return &parseContext{ return &parseContext{
store: make([]interface{}, ContextKeyMax+1), store: make([]interface{}, ContextKeyMax+1),
refs: map[string]Reference{}, refs: map[string]Reference{},
ids: newIDs(),
blockOffset: 0, blockOffset: 0,
delimiters: nil, delimiters: nil,
lastDelimiter: nil, lastDelimiter: nil,
@ -150,6 +216,10 @@ func (p *parseContext) Set(key ContextKey, value interface{}) {
p.store[key] = value p.store[key] = value
} }
func (p *parseContext) IDs() IDs {
return p.ids
}
func (p *parseContext) BlockOffset() int { func (p *parseContext) BlockOffset() int {
return p.blockOffset return p.blockOffset
} }

View file

@ -94,10 +94,10 @@ func (b *setextHeadingParser) Close(node ast.Node, reader text.Reader, pc Contex
tmp.Parent().RemoveChild(tmp.Parent(), tmp) tmp.Parent().RemoveChild(tmp.Parent(), tmp)
} }
if !b.HeadingID { if !b.AutoHeadingID {
return return
} }
parseOrGenerateHeadingID(heading, reader, pc) generateAutoHeadingID(heading, reader, pc)
} }
func (b *setextHeadingParser) CanInterruptParagraph() bool { func (b *setextHeadingParser) CanInterruptParagraph() bool {

View file

@ -3,7 +3,6 @@ package util
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"net/url" "net/url"
"sort" "sort"
@ -574,7 +573,7 @@ func FindAttributeIndex(b []byte, canEscapeQuotes bool) [4]int {
i++ i++
result[1] = i result[1] = i
result[2] = i result[2] = i
for ; i < l && !IsSpace(b[i]); i++ { for ; i < l && !IsSpace(b[i]) && (!IsPunct(b[i]) || b[i] == '_' || b[i] == '-'); i++ {
} }
result[3] = i result[3] = i
return result return result
@ -677,41 +676,6 @@ func FindHTMLAttributeIndex(b []byte, canEscapeQuotes bool) [4]int {
return result return result
} }
// GenerateLinkID generates an ID for links.
func GenerateLinkID(value []byte, exists map[string]bool) []byte {
value = TrimLeftSpace(value)
value = TrimRightSpace(value)
result := []byte{}
for i := 0; i < len(value); {
v := value[i]
l := utf8lenTable[v]
i += int(l)
if l != 1 {
continue
}
if IsAlphaNumeric(v) {
result = append(result, v)
} else if v == ' ' {
result = append(result, '-')
}
}
if len(result) == 0 {
result = []byte("id")
}
if _, ok := exists[string(result)]; !ok {
exists[string(result)] = true
return result
}
for i := 1; ; i++ {
newResult := fmt.Sprintf("%s%d", result, i)
if _, ok := exists[newResult]; !ok {
exists[newResult] = true
return []byte(newResult)
}
}
}
var spaces = []byte(" \t\n\x0b\x0c\x0d") var spaces = []byte(" \t\n\x0b\x0c\x0d")
var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
@ -723,6 +687,11 @@ var urlEscapeTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
var utf8lenTable = [256]int8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 99, 99, 99, 99, 99, 99, 99, 99} var utf8lenTable = [256]int8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 99, 99, 99, 99, 99, 99, 99, 99}
// UTF8Len returns a byte length of the utf-8 character.
func UTF8Len(b byte) int8 {
return utf8lenTable[b]
}
// IsPunct returns true if the given character is a punctuation, otherwise false. // IsPunct returns true if the given character is a punctuation, otherwise false.
func IsPunct(c byte) bool { func IsPunct(c byte) bool {
return punctTable[c] == 1 return punctTable[c] == 1