From 2ddc99baff6cbd351deb9ae5ac0797bded26801b Mon Sep 17 00:00:00 2001
From: yuin Pomaceous fruit of plants of the genus Malus in
+the family Rosaceae. The fruit of an evergreen tree of the genus Citrus. This is a definition with two paragraphs. Lorem ipsum
+dolor sit amet, consectetuer adipiscing elit. Aliquam
+hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+vitae, risus. Second definition for term 1, also wrapped in a paragraph
+because of the blank line preceding it. This definition has a code block, a blockquote and a list. block quote
+on two lines. That's some text with a footnote.1 And that's the footnote. That's the second paragraph. Visit www.commonmark.org/help for more information. www.google.com/search?q=Markup+(business) www.google.com/search?q=Markup+(business))) (www.google.com/search?q=Markup+(business)) (www.google.com/search?q=Markup+(business) www.google.com/search?q=(business))+ok www.google.com/search?q=commonmark&hl=en www.google.com/search?q=commonmark&hl;Title 0
+Title1
+Title2
+Title3
+Title4
+Title5
+Title6
+Title7
+//= = = = = = = = = = = = = = = = = = = = = = = =//
diff --git a/ast/inline.go b/ast/inline.go
index e1f12e2..7404a3f 100644
--- a/ast/inline.go
+++ b/ast/inline.go
@@ -362,10 +362,13 @@ const (
// An AutoLink struct represents an autolink of the Markdown text.
type AutoLink struct {
BaseInline
- // Value is a link text of this node.
- Value *Text
// Type is a type of this autolink.
AutoLinkType AutoLinkType
+
+ // Protocol specified a protocol of the link.
+ Protocol []byte
+
+ value *Text
}
// Inline implements Inline.Inline.
@@ -373,7 +376,7 @@ func (n *AutoLink) Inline() {}
// Dump implenets Node.Dump
func (n *AutoLink) Dump(source []byte, level int) {
- segment := n.Value.Segment
+ segment := n.value.Segment
m := map[string]string{
"Value": string(segment.Value(source)),
}
@@ -388,11 +391,29 @@ func (n *AutoLink) Kind() NodeKind {
return KindAutoLink
}
+// URL returns an url of this node.
+func (n *AutoLink) URL(source []byte) []byte {
+ if n.Protocol != nil {
+ s := n.value.Segment
+ ret := make([]byte, 0, len(n.Protocol)+s.Len()+3)
+ ret = append(ret, n.Protocol...)
+ ret = append(ret, ':', '/', '/')
+ ret = append(ret, n.value.Text(source)...)
+ return ret
+ }
+ return n.value.Text(source)
+}
+
+// Label returns a label of this node.
+func (n *AutoLink) Label(source []byte) []byte {
+ return n.value.Text(source)
+}
+
// NewAutoLink returns a new AutoLink node.
func NewAutoLink(typ AutoLinkType, value *Text) *AutoLink {
return &AutoLink{
BaseInline: BaseInline{},
- Value: value,
+ value: value,
AutoLinkType: typ,
}
}
diff --git a/commonmark_test.go b/commonmark_test.go
index d975900..64d5e3d 100644
--- a/commonmark_test.go
+++ b/commonmark_test.go
@@ -1,7 +1,6 @@
package goldmark
import (
- "bytes"
"encoding/json"
"io/ioutil"
"testing"
@@ -27,30 +26,17 @@ func TestSpec(t *testing.T) {
if err := json.Unmarshal(bs, &testCases); err != nil {
panic(err)
}
+ cases := []MarkdownTestCase{}
+ for _, c := range testCases {
+ cases = append(cases, MarkdownTestCase{
+ No: c.Example,
+ Markdown: c.Markdown,
+ Expected: c.HTML,
+ })
+ }
markdown := New(WithRendererOptions(
html.WithXHTML(),
html.WithUnsafe(),
))
- for _, testCase := range testCases {
- var out bytes.Buffer
- if err := markdown.Convert([]byte(testCase.Markdown), &out); err != nil {
- panic(err)
- }
- if !bytes.Equal(bytes.TrimSpace(out.Bytes()), bytes.TrimSpace([]byte(testCase.HTML))) {
- format := `============= case %d ================
-Markdown:
------------
-%s
-
-Expected:
-----------
-%s
-
-Actual
----------
-%s
-`
- t.Errorf(format, testCase.Example, testCase.Markdown, testCase.HTML, out.Bytes())
- }
- }
+ DoTestCases(markdown, cases, t)
}
diff --git a/extension/_test/definition_list.txt b/extension/_test/definition_list.txt
new file mode 100644
index 0000000..2ddc3ea
--- /dev/null
+++ b/extension/_test/definition_list.txt
@@ -0,0 +1,143 @@
+1
+//- - - - - - - - -//
+Apple
+: Pomaceous fruit of plants of the genus Malus in
+the family Rosaceae.
+
+Orange
+: The fruit of an evergreen tree of the genus Citrus.
+//- - - - - - - - -//
+
+
+//= = = = = = = = = = = = = = = = = = = = = = = =//
+
+
+
+2
+//- - - - - - - - -//
+Apple
+: Pomaceous fruit of plants of the genus Malus in
+ the family Rosaceae.
+: An American computer company.
+
+Orange
+: The fruit of an evergreen tree of the genus Citrus.
+//- - - - - - - - -//
+
+
+//= = = = = = = = = = = = = = = = = = = = = = = =//
+
+
+
+3
+//- - - - - - - - -//
+Term 1
+Term 2
+: Definition a
+
+Term 3
+: Definition b
+//- - - - - - - - -//
+
+
+//= = = = = = = = = = = = = = = = = = = = = = = =//
+
+
+
+4
+//- - - - - - - - -//
+Apple
+
+: Pomaceous fruit of plants of the genus Malus in
+ the family Rosaceae.
+
+Orange
+
+: The fruit of an evergreen tree of the genus Citrus.
+//- - - - - - - - -//
+
+
+//= = = = = = = = = = = = = = = = = = = = = = = =//
+
+
+5
+//- - - - - - - - -//
+Term 1
+
+: This is a definition with two paragraphs. Lorem ipsum
+ dolor sit amet, consectetuer adipiscing elit. Aliquam
+ hendrerit mi posuere lectus.
+
+ Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+ vitae, risus.
+
+: Second definition for term 1, also wrapped in a paragraph
+ because of the blank line preceding it.
+
+Term 2
+
+: This definition has a code block, a blockquote and a list.
+
+ code block.
+
+ > block quote
+ > on two lines.
+
+ 1. first list item
+ 2. second list item
+//- - - - - - - - -//
+
+
+//= = = = = = = = = = = = = = = = = = = = = = = =//
+
diff --git a/extension/_test/footnote.txt b/extension/_test/footnote.txt
new file mode 100644
index 0000000..6a4ea5c
--- /dev/null
+++ b/extension/_test/footnote.txt
@@ -0,0 +1,22 @@
+1
+//- - - - - - - - -//
+That's some text with a footnote.[^1]
+
+[^1]: And that's the footnote.
+
+ That's the second paragraph.
+//- - - - - - - - -//
+
+code block.
+
+
+
+
+
+
+
+
(Visit https://encrypted.google.com/search?q=Markup+(business))
+Anonymous FTP is available at ftp://foo.bar.baz.
+//= = = = = = = = = = = = = = = = = = = = = = = =// + + + +8 +//- - - - - - - - -// +foo@bar.baz +//- - - - - - - - -// + +//= = = = = = = = = = = = = = = = = = = = = = = =// + + + +9 +//- - - - - - - - -// +hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is. +//- - - - - - - - -// +hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is.
+//= = = = = = = = = = = = = = = = = = = = = = = =// + + + +10 +//- - - - - - - - -// +a.b-c_d@a.b + +a.b-c_d@a.b. + +a.b-c_d@a.b- + +a.b-c_d@a.b_ +//- - - - - - - - -// + + +a.b-c_d@a.b-
+a.b-c_d@a.b_
+//= = = = = = = = = = = = = = = = = = = = = = = =// diff --git a/extension/_test/strikethrough.txt b/extension/_test/strikethrough.txt new file mode 100644 index 0000000..dbb48f6 --- /dev/null +++ b/extension/_test/strikethrough.txt @@ -0,0 +1,18 @@ +1 +//- - - - - - - - -// +~~Hi~~ Hello, world! +//- - - - - - - - -// +Hi Hello, world!
This ~~has a
+new paragraph~~.
+//= = = = = = = = = = = = = = = = = = = = = = = =// diff --git a/extension/_test/table.txt b/extension/_test/table.txt new file mode 100644 index 0000000..ce0d558 --- /dev/null +++ b/extension/_test/table.txt @@ -0,0 +1,190 @@ +1 +//- - - - - - - - -// +| foo | bar | +| --- | --- | +| baz | bim | +//- - - - - - - - -// +| foo | +bar | +
|---|---|
| baz | +bim | +
| abc | +defghi | +
|---|---|
| bar | +baz | +
| f|oo | +
|---|
b \| az |
+
| b | im | +
| abc | +def | +
|---|---|
| bar | +baz | +
++//= = = = = = = = = = = = = = = = = = = = = = = =// + + + +5 +//- - - - - - - - -// +| abc | def | +| --- | --- | +| bar | baz | +bar + +bar +//- - - - - - - - -// +bar
+
| abc | +def | +
|---|---|
| bar | +baz | +
| bar | ++ |
bar
+//= = = = = = = = = = = = = = = = = = = = = = = =// + + + +6 +//- - - - - - - - -// +| abc | def | +| --- | +| bar | +//- - - - - - - - -// +| abc | def | +| --- | +| bar |
+//= = = = = = = = = = = = = = = = = = = = = = = =// + + + +7 +//- - - - - - - - -// +| abc | def | +| --- | --- | +| bar | +| bar | baz | boo | +//- - - - - - - - -// +| abc | +def | +
|---|---|
| bar | ++ |
| bar | +baz | +
| abc | +def | +
|---|
This should ‘be’ replaced
+//= = = = = = = = = = = = = = = = = = = = = = = =// + +2 +//- - - - - - - - -// +This should "be" replaced +//- - - - - - - - -// +This should “be” replaced
+//= = = = = = = = = = = = = = = = = = = = = = = =// + +3 +//- - - - - - - - -// +**--** *---* a...<< b>> +//- - - - - - - - -// +– — a…« b»
+//= = = = = = = = = = = = = = = = = = = = = = = =// diff --git a/extension/ast/table.go b/extension/ast/table.go index 7396b86..1d8890b 100644 --- a/extension/ast/table.go +++ b/extension/ast/table.go @@ -102,7 +102,8 @@ func NewTableRow(alignments []Alignment) *TableRow { // A TableHeader struct represents a table header of Markdown(GFM) text. type TableHeader struct { - *TableRow + gast.BaseBlock + Alignments []Alignment } // KindTableHeader is a NodeKind of the TableHeader node. @@ -113,9 +114,20 @@ func (n *TableHeader) Kind() gast.NodeKind { return KindTableHeader } +// Dump implements Node.Dump. +func (n *TableHeader) Dump(source []byte, level int) { + gast.DumpHelper(n, source, level, nil, nil) +} + // NewTableHeader returns a new TableHeader node. func NewTableHeader(row *TableRow) *TableHeader { - return &TableHeader{row} + n := &TableHeader{} + for c := row.FirstChild(); c != nil; { + next := c.NextSibling() + n.AppendChild(n, c) + c = next + } + return n } // A TableCell struct represents a table cell of a Markdown(GFM) text. diff --git a/extension/definition_list_test.go b/extension/definition_list_test.go new file mode 100644 index 0000000..1cbf167 --- /dev/null +++ b/extension/definition_list_test.go @@ -0,0 +1,19 @@ +package extension + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/renderer/html" + "testing" +) + +func TestDefinitionList(t *testing.T) { + markdown := goldmark.New( + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + DefinitionList, + ), + ) + goldmark.DoTestCaseFile(markdown, "_test/definition_list.txt", t) +} diff --git a/extension/footnote_test.go b/extension/footnote_test.go new file mode 100644 index 0000000..b5ce90c --- /dev/null +++ b/extension/footnote_test.go @@ -0,0 +1,19 @@ +package extension + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/renderer/html" + "testing" +) + +func TestFootnote(t *testing.T) { + markdown := goldmark.New( + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + Footnote, + ), + ) + goldmark.DoTestCaseFile(markdown, "_test/footnote.txt", t) +} diff --git a/extension/gfm.go b/extension/gfm.go index b16a56d..a570fbd 100644 --- a/extension/gfm.go +++ b/extension/gfm.go @@ -2,7 +2,6 @@ package extension import ( "github.com/yuin/goldmark" - "github.com/yuin/goldmark/parser" ) type gfm struct { @@ -11,20 +10,7 @@ type gfm struct { // GFM is an extension that provides Github Flavored markdown functionalities. var GFM = &gfm{} -var filterTags = []string{ - "title", - "textarea", - "style", - "xmp", - "iframe", - "noembed", - "noframes", - "script", - "plaintext", -} - func (e *gfm) Extend(m goldmark.Markdown) { - m.Parser().AddOptions(parser.WithFilterTags(filterTags...)) Linkify.Extend(m) Table.Extend(m) Strikethrough.Extend(m) diff --git a/extension/linkify.go b/extension/linkify.go index f0544bf..0efe2ce 100644 --- a/extension/linkify.go +++ b/extension/linkify.go @@ -48,13 +48,14 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont } var m []int - typ := ast.AutoLinkType(ast.AutoLinkEmail) - typ = ast.AutoLinkURL + var protocol []byte + var typ ast.AutoLinkType = ast.AutoLinkURL if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) { m = urlRegexp.FindSubmatchIndex(line) } if m == nil && bytes.HasPrefix(line, domainWWW) { m = wwwURLRegxp.FindSubmatchIndex(line) + protocol = []byte("http") } if m != nil { lastChar := line[m[1]-1] @@ -70,7 +71,7 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont } } if closing > 0 { - m[1]-- + m[1] -= closing } } else if lastChar == ';' { i := m[1] - 2 @@ -119,7 +120,9 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont consumes += m[1] block.Advance(consumes) n := ast.NewTextSegment(text.NewSegment(start, start+m[1])) - return ast.NewAutoLink(typ, n) + link := ast.NewAutoLink(typ, n) + link.Protocol = protocol + return link } func (s *linkifyParser) CloseBlock(parent ast.Node, pc parser.Context) { diff --git a/extension/linkify_test.go b/extension/linkify_test.go new file mode 100644 index 0000000..5a80b95 --- /dev/null +++ b/extension/linkify_test.go @@ -0,0 +1,19 @@ +package extension + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/renderer/html" + "testing" +) + +func TestLinkify(t *testing.T) { + markdown := goldmark.New( + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + Linkify, + ), + ) + goldmark.DoTestCaseFile(markdown, "_test/linkify.txt", t) +} diff --git a/extension/strikethrough_test.go b/extension/strikethrough_test.go new file mode 100644 index 0000000..891409e --- /dev/null +++ b/extension/strikethrough_test.go @@ -0,0 +1,19 @@ +package extension + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/renderer/html" + "testing" +) + +func TestStrikethrough(t *testing.T) { + markdown := goldmark.New( + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + Strikethrough, + ), + ) + goldmark.DoTestCaseFile(markdown, "_test/strikethrough.txt", t) +} diff --git a/extension/table.go b/extension/table.go index 8f20f9f..4ff7b73 100644 --- a/extension/table.go +++ b/extension/table.go @@ -41,7 +41,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text. if alignments == nil { return } - header := b.parseRow(lines.At(0), alignments, reader) + header := b.parseRow(lines.At(0), alignments, true, reader) if header == nil || len(alignments) != header.ChildCount() { return } @@ -50,15 +50,14 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text. table.AppendChild(table, ast.NewTableHeader(header)) if lines.Len() > 2 { for i := 2; i < lines.Len(); i++ { - table.AppendChild(table, b.parseRow(lines.At(i), alignments, reader)) + table.AppendChild(table, b.parseRow(lines.At(i), alignments, false, reader)) } } node.Parent().InsertBefore(node.Parent(), node, table) node.Parent().RemoveChild(node.Parent(), node) - return } -func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, reader text.Reader) *ast.TableRow { +func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader) *ast.TableRow { source := reader.Source() line := segment.Value(source) pos := 0 @@ -72,7 +71,16 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments [] if len(line) > 0 && line[limit-1] == '|' { limit-- } - for i := 0; pos < limit; i++ { + i := 0 + for ; pos < limit; i++ { + alignment := ast.AlignNone + if i >= len(alignments) { + if !isHeader { + return row + } + } else { + alignment = alignments[i] + } closure := util.FindClosure(line[pos:], byte(0), '|', true, false) if closure < 0 { closure = len(line[pos:]) @@ -82,10 +90,13 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments [] segment = segment.TrimLeftSpace(source) segment = segment.TrimRightSpace(source) node.Lines().Append(segment) - node.Alignment = alignments[i] + node.Alignment = alignment row.AppendChild(row, node) pos += closure + 1 } + for ; i < len(alignments); i++ { + row.AppendChild(row, ast.NewTableCell()) + } return row } @@ -175,9 +186,6 @@ func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n if n.NextSibling() != nil { w.WriteString("\n") } - if n.Parent().LastChild() == n { - w.WriteString("\n") - } } return gast.WalkContinue, nil } @@ -197,7 +205,7 @@ func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n ga func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) { n := node.(*ast.TableCell) tag := "td" - if n.Parent().Parent().FirstChild() == n.Parent() { + if n.Parent().Kind() == ast.KindTableHeader { tag = "th" } if entering { diff --git a/extension/table_test.go b/extension/table_test.go new file mode 100644 index 0000000..7e38896 --- /dev/null +++ b/extension/table_test.go @@ -0,0 +1,19 @@ +package extension + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/renderer/html" + "testing" +) + +func TestTable(t *testing.T) { + markdown := goldmark.New( + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + Table, + ), + ) + goldmark.DoTestCaseFile(markdown, "_test/table.txt", t) +} diff --git a/extension/tasklist_test.go b/extension/tasklist_test.go new file mode 100644 index 0000000..6fc74d5 --- /dev/null +++ b/extension/tasklist_test.go @@ -0,0 +1,19 @@ +package extension + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/renderer/html" + "testing" +) + +func TestTaskList(t *testing.T) { + markdown := goldmark.New( + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + TaskList, + ), + ) + goldmark.DoTestCaseFile(markdown, "_test/tasklist.txt", t) +} diff --git a/extension/typographer_test.go b/extension/typographer_test.go new file mode 100644 index 0000000..6646314 --- /dev/null +++ b/extension/typographer_test.go @@ -0,0 +1,19 @@ +package extension + +import ( + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/renderer/html" + "testing" +) + +func TestTypographer(t *testing.T) { + markdown := goldmark.New( + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + Typographer, + ), + ) + goldmark.DoTestCaseFile(markdown, "_test/typographer.txt", t) +} diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..f1a0b0f --- /dev/null +++ b/options_test.go @@ -0,0 +1,16 @@ +package goldmark + +import ( + "github.com/yuin/goldmark/parser" + "testing" +) + +func TestDefinitionList(t *testing.T) { + markdown := New( + WithParserOptions( + parser.WithAttribute(), + parser.WithAutoHeadingID(), + ), + ) + DoTestCaseFile(markdown, "_test/options.txt", t) +} diff --git a/parser/atx_heading.go b/parser/atx_heading.go index 9c63ba1..fbcfc5c 100644 --- a/parser/atx_heading.go +++ b/parser/atx_heading.go @@ -125,7 +125,7 @@ func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) break } node.SetAttribute(line[as+ai[0]:as+ai[1]], - line[as+ai[2]:as+ai[3]]) + util.UnescapePunctuations(line[as+ai[2]:as+ai[3]])) as += ai[3] + skip } for ; as < stop && util.IsSpace(line[as]); as++ { @@ -208,7 +208,8 @@ func parseLastLineAttributes(node ast.Node, reader text.Reader, pc Context) { indicies := util.FindAttributeIndiciesReverse(line, true) if indicies != nil { for _, index := range indicies { - node.SetAttribute(line[index[0]:index[1]], line[index[2]:index[3]]) + node.SetAttribute(line[index[0]:index[1]], + util.UnescapePunctuations(line[index[2]:index[3]])) } lastLine.Stop = lastLine.Start + indicies[0][0] - 1 lastLine.TrimRightSpace(reader.Source()) diff --git a/parser/html_block.go b/parser/html_block.go index 831ba7d..eb822ef 100644 --- a/parser/html_block.go +++ b/parser/html_block.go @@ -9,48 +9,6 @@ import ( "strings" ) -// An HTMLConfig struct is a data structure that holds configuration of the renderers related to raw htmls. -type HTMLConfig struct { - FilterTags map[string]bool -} - -// SetOption implements SetOptioner. -func (b *HTMLConfig) SetOption(name OptionName, value interface{}) { - switch name { - case optFilterTags: - b.FilterTags = value.(map[string]bool) - } -} - -// A HTMLOption interface sets options for the raw HTML parsers. -type HTMLOption interface { - Option - SetHTMLOption(*HTMLConfig) -} - -const optFilterTags OptionName = "FilterTags" - -type withFilterTags struct { - value map[string]bool -} - -func (o *withFilterTags) SetParserOption(c *Config) { - c.Options[optFilterTags] = o.value -} - -func (o *withFilterTags) SetHTMLOption(p *HTMLConfig) { - p.FilterTags = o.value -} - -// WithFilterTags is a functional otpion that specify forbidden tag names. -func WithFilterTags(names ...string) HTMLOption { - m := map[string]bool{} - for _, name := range names { - m[name] = true - } - return &withFilterTags{m} -} - var allowedBlockTags = map[string]bool{ "address": true, "article": true, @@ -137,17 +95,14 @@ var htmlBlockType6Regexp = regexp.MustCompile(`^[ ]{0,3}?([a-zA-Z0-9]+)(?:\s.* var htmlBlockType7Regexp = regexp.MustCompile(`^[ ]{0,3}<(/)?([a-zA-Z0-9]+)(` + attributePattern + `*)(:?>|/>)\s*\n?$`) type htmlBlockParser struct { - HTMLConfig } +var defaultHtmlBlockParser = &htmlBlockParser{} + // NewHTMLBlockParser return a new BlockParser that can parse html // blocks. -func NewHTMLBlockParser(opts ...HTMLOption) BlockParser { - p := &htmlBlockParser{} - for _, o := range opts { - o.SetHTMLOption(&p.HTMLConfig) - } - return p +func NewHTMLBlockParser() BlockParser { + return defaultHtmlBlockParser } func (b *htmlBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) { @@ -191,11 +146,6 @@ func (b *htmlBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) } } if node != nil { - if b.FilterTags != nil { - if _, ok := b.FilterTags[tagName]; ok { - return nil, NoChildren - } - } reader.Advance(segment.Len() - 1) node.Lines().Append(segment) return node, NoChildren diff --git a/parser/parser.go b/parser/parser.go index 615c1de..6352353 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -84,6 +84,9 @@ func (s *ids) Generate(value, prefix []byte) []byte { continue } if util.IsAlphaNumeric(v) { + if 'A' <= v && v <= 'Z' { + v += 'a' - 'A' + } result = append(result, v) } else if util.IsSpace(v) { result = append(result, '-') diff --git a/parser/raw_html.go b/parser/raw_html.go index fcc76e2..d7ba414 100644 --- a/parser/raw_html.go +++ b/parser/raw_html.go @@ -9,17 +9,14 @@ import ( ) type rawHTMLParser struct { - HTMLConfig } +var defaultRawHTMLParser = &rawHTMLParser{} + // NewRawHTMLParser return a new InlineParser that can parse // inline htmls -func NewRawHTMLParser(opts ...HTMLOption) InlineParser { - p := &rawHTMLParser{} - for _, o := range opts { - o.SetHTMLOption(&p.HTMLConfig) - } - return p +func NewRawHTMLParser() InlineParser { + return defaultRawHTMLParser } func (s *rawHTMLParser) Trigger() []byte { @@ -74,22 +71,7 @@ var dummyMatch = [][]byte{} func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, pc Context) ast.Node { sline, ssegment := block.Position() - var m [][]byte - if s.FilterTags != nil { - m = block.FindSubMatch(reg) - } else { - if block.Match(reg) { - m = dummyMatch - } - } - - if m != nil { - if s.FilterTags != nil && len(m) > 1 { - tagName := string(m[1]) - if _, ok := s.FilterTags[tagName]; ok { - return nil - } - } + if block.Match(reg) { node := ast.NewRawHTML() eline, esegment := block.Position() block.SetPosition(sline, ssegment) diff --git a/renderer/html/html.go b/renderer/html/html.go index 0fd3dcd..8551b78 100644 --- a/renderer/html/html.go +++ b/renderer/html/html.go @@ -352,14 +352,14 @@ func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node return ast.WalkContinue, nil } w.WriteString(``) - w.Write(util.EscapeHTML(value)) + w.Write(util.EscapeHTML(label)) w.WriteString(``) return ast.WalkContinue, nil } @@ -493,7 +493,7 @@ func (r *Renderer) RenderAttributes(w util.BufWriter, node ast.Node) { w.WriteString(" ") w.Write(attr.Name) w.WriteString(`="`) - w.Write(attr.Value) + w.Write(util.EscapeHTML(attr.Value)) w.WriteByte('"') } } diff --git a/testutil.go b/testutil.go new file mode 100644 index 0000000..4917e58 --- /dev/null +++ b/testutil.go @@ -0,0 +1,103 @@ +package goldmark + +import ( + "bufio" + "bytes" + "fmt" + "github.com/yuin/goldmark/util" + "os" + "strconv" + "strings" + testing "testing" +) + +type MarkdownTestCase struct { + No int + Markdown string + Expected string +} + +const attributeSeparator = "//- - - - - - - - -//" +const caseSeparator = "//= = = = = = = = = = = = = = = = = = = = = = = =//" + +func DoTestCaseFile(m Markdown, filename string, t *testing.T) { + fp, err := os.Open(filename) + if err != nil { + panic(err) + } + defer fp.Close() + + scanner := bufio.NewScanner(fp) + c := MarkdownTestCase{ + No: -1, + Markdown: "", + Expected: "", + } + cases := []MarkdownTestCase{} + line := 0 + for scanner.Scan() { + line++ + if util.IsBlank([]byte(scanner.Text())) { + continue + } + c.No, err = strconv.Atoi(scanner.Text()) + if err != nil { + panic(fmt.Sprintf("%s: invalid case No at line %d", filename, line)) + } + if !scanner.Scan() { + panic(fmt.Sprintf("%s: invalid case at line %d", filename, line)) + } + line++ + if scanner.Text() != attributeSeparator { + panic(fmt.Sprintf("%s: invalid separator '%s' at line %d", filename, scanner.Text(), line)) + } + buf := []string{} + for scanner.Scan() { + line++ + text := scanner.Text() + if text == attributeSeparator { + break + } + buf = append(buf, text) + } + c.Markdown = strings.Join(buf, "\n") + buf = []string{} + for scanner.Scan() { + line++ + text := scanner.Text() + if text == caseSeparator { + break + } + buf = append(buf, text) + } + c.Expected = strings.Join(buf, "\n") + cases = append(cases, c) + } + DoTestCases(m, cases, t) +} + +func DoTestCases(m Markdown, cases []MarkdownTestCase, t *testing.T) { + for _, testCase := range cases { + var out bytes.Buffer + if err := m.Convert([]byte(testCase.Markdown), &out); err != nil { + panic(err) + } + if !bytes.Equal(bytes.TrimSpace(out.Bytes()), bytes.TrimSpace([]byte(testCase.Expected))) { + format := `============= case %d ================ +Markdown: +----------- +%s + +Expected: +---------- +%s + +Actual +--------- +%s +` + t.Errorf(format, testCase.No, testCase.Markdown, testCase.Expected, out.Bytes()) + } + + } +}