From 05645dd3c4666d1c9454cf5ff3e4270ab6325cc6 Mon Sep 17 00:00:00 2001 From: yuin Date: Thu, 30 May 2019 16:07:04 +0900 Subject: [PATCH] Add String node --- ast/inline.go | 95 ++++++++++++++++++- extension/ast/typographer.go | 32 ------- extension/typographer.go | 62 ++++-------- renderer/html/html.go | 178 +++++++++++++++++++---------------- 4 files changed, 209 insertions(+), 158 deletions(-) delete mode 100644 extension/ast/typographer.go diff --git a/ast/inline.go b/ast/inline.go index 7404a3f..8c20402 100644 --- a/ast/inline.go +++ b/ast/inline.go @@ -56,8 +56,26 @@ const ( textSoftLineBreak = 1 << iota textHardLineBreak textRaw + textCode ) +func textFlagsString(flags uint8) string { + buf := []string{} + if flags&textSoftLineBreak != 0 { + buf = append(buf, "SoftLineBreak") + } + if flags&textHardLineBreak != 0 { + buf = append(buf, "HardLineBreak") + } + if flags&textRaw != 0 { + buf = append(buf, "Raw") + } + if flags&textCode != 0 { + buf = append(buf, "Code") + } + return strings.Join(buf, ", ") +} + // Inline implements Inline.Inline. func (n *Text) Inline() { } @@ -130,7 +148,11 @@ func (n *Text) Text(source []byte) []byte { // Dump implements Node.Dump. func (n *Text) Dump(source []byte, level int) { - fmt.Printf("%sText: \"%s\"\n", strings.Repeat(" ", level), strings.TrimRight(string(n.Text(source)), "\n")) + fs := textFlagsString(n.flags) + if len(fs) != 0 { + fs = "(" + fs + ")" + } + fmt.Printf("%sText%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Text(source)), "\n")) } // KindText is a NodeKind of the Text node. @@ -192,6 +214,77 @@ func MergeOrReplaceTextSegment(parent Node, n Node, s textm.Segment) { } } +// A String struct is a textual content that has a concrete value +type String struct { + BaseInline + + Value []byte + flags uint8 +} + +// Inline implements Inline.Inline. +func (n *String) Inline() { +} + +// IsRaw returns true if this text should be rendered without unescaping +// back slash escapes and resolving references. +func (n *String) IsRaw() bool { + return n.flags&textRaw != 0 +} + +// SetRaw sets whether this text should be rendered as raw contents. +func (n *String) SetRaw(v bool) { + if v { + n.flags |= textRaw + } else { + n.flags = n.flags &^ textRaw + } +} + +// IsCode returns true if this text should be rendered without any +// modifications. +func (n *String) IsCode() bool { + return n.flags&textCode != 0 +} + +// SetCode sets whether this text should be rendered without any modifications. +func (n *String) SetCode(v bool) { + if v { + n.flags |= textCode + } else { + n.flags = n.flags &^ textCode + } +} + +// Text implements Node.Text. +func (n *String) Text(source []byte) []byte { + return n.Value +} + +// Dump implements Node.Dump. +func (n *String) Dump(source []byte, level int) { + fs := textFlagsString(n.flags) + if len(fs) != 0 { + fs = "(" + fs + ")" + } + fmt.Printf("%sString%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Value), "\n")) +} + +// KindString is a NodeKind of the String node. +var KindString = NewNodeKind("String") + +// Kind implements Node.Kind. +func (n *String) Kind() NodeKind { + return KindString +} + +// NewString returns a new String node. +func NewString(v []byte) *String { + return &String{ + Value: v, + } +} + // A CodeSpan struct represents a code span of Markdown text. type CodeSpan struct { BaseInline diff --git a/extension/ast/typographer.go b/extension/ast/typographer.go deleted file mode 100644 index 4101b4d..0000000 --- a/extension/ast/typographer.go +++ /dev/null @@ -1,32 +0,0 @@ -package ast - -import ( - gast "github.com/yuin/goldmark/ast" -) - -// A TypographicText struct represents text that -// typographic text replaces certain punctuations. -type TypographicText struct { - gast.BaseInline - Value []byte -} - -// Dump implements Node.Dump. -func (n *TypographicText) Dump(source []byte, level int) { - gast.DumpHelper(n, source, level, nil, nil) -} - -// KindTypographicText is a NodeKind of the TypographicText node. -var KindTypographicText = gast.NewNodeKind("TypographicText") - -// Kind implements Node.Kind. -func (n *TypographicText) Kind() gast.NodeKind { - return KindTypographicText -} - -// NewTypographicText returns a new TypographicText node. -func NewTypographicText(value []byte) *TypographicText { - return &TypographicText{ - Value: value, - } -} diff --git a/extension/typographer.go b/extension/typographer.go index 490fb5a..a267980 100644 --- a/extension/typographer.go +++ b/extension/typographer.go @@ -3,10 +3,7 @@ package extension import ( "github.com/yuin/goldmark" gast "github.com/yuin/goldmark/ast" - "github.com/yuin/goldmark/extension/ast" "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer" - "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" ) @@ -147,13 +144,15 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser if len(line) > 2 { if c == '-' { if s.Substitutions[EmDash] != nil && line[1] == '-' && line[2] == '-' { // --- - node := ast.NewTypographicText(s.Substitutions[EmDash]) + node := gast.NewString(s.Substitutions[EmDash]) + node.SetCode(true) block.Advance(3) return node } } else if c == '.' { if s.Substitutions[Ellipsis] != nil && line[1] == '.' && line[2] == '.' { // ... - node := ast.NewTypographicText(s.Substitutions[Ellipsis]) + node := gast.NewString(s.Substitutions[Ellipsis]) + node.SetCode(true) block.Advance(3) return node } @@ -163,20 +162,23 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser if len(line) > 1 { if c == '<' { if s.Substitutions[LeftAngleQuote] != nil && line[1] == '<' { // << - node := ast.NewTypographicText(s.Substitutions[LeftAngleQuote]) + node := gast.NewString(s.Substitutions[LeftAngleQuote]) + node.SetCode(true) block.Advance(2) return node } return nil } else if c == '>' { if s.Substitutions[RightAngleQuote] != nil && line[1] == '>' { // >> - node := ast.NewTypographicText(s.Substitutions[RightAngleQuote]) + node := gast.NewString(s.Substitutions[RightAngleQuote]) + node.SetCode(true) block.Advance(2) return node } return nil } else if s.Substitutions[EnDash] != nil && c == '-' && line[1] == '-' { // -- - node := ast.NewTypographicText(s.Substitutions[EnDash]) + node := gast.NewString(s.Substitutions[EnDash]) + node.SetCode(true) block.Advance(2) return node } @@ -188,24 +190,28 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser } if c == '\'' { if s.Substitutions[LeftSingleQuote] != nil && d.CanOpen && !d.CanClose { - node := ast.NewTypographicText(s.Substitutions[LeftSingleQuote]) + node := gast.NewString(s.Substitutions[LeftSingleQuote]) + node.SetCode(true) block.Advance(1) return node } if s.Substitutions[RightSingleQuote] != nil && d.CanClose && !d.CanOpen { - node := ast.NewTypographicText(s.Substitutions[RightSingleQuote]) + node := gast.NewString(s.Substitutions[RightSingleQuote]) + node.SetCode(true) block.Advance(1) return node } } if c == '"' { if s.Substitutions[LeftDoubleQuote] != nil && d.CanOpen && !d.CanClose { - node := ast.NewTypographicText(s.Substitutions[LeftDoubleQuote]) + node := gast.NewString(s.Substitutions[LeftDoubleQuote]) + node.SetCode(true) block.Advance(1) return node } if s.Substitutions[RightDoubleQuote] != nil && d.CanClose && !d.CanOpen { - node := ast.NewTypographicText(s.Substitutions[RightDoubleQuote]) + node := gast.NewString(s.Substitutions[RightDoubleQuote]) + node.SetCode(true) block.Advance(1) return node } @@ -218,35 +224,6 @@ func (s *typographerParser) CloseBlock(parent gast.Node, pc parser.Context) { // nothing to do } -// TypographerHTMLRenderer is a renderer.NodeRenderer implementation that -// renders Typographer nodes. -type TypographerHTMLRenderer struct { - html.Config -} - -// NewTypographerHTMLRenderer returns a new TypographerHTMLRenderer. -func NewTypographerHTMLRenderer(opts ...html.Option) renderer.NodeRenderer { - r := &TypographerHTMLRenderer{ - Config: html.NewConfig(), - } - for _, opt := range opts { - opt.SetHTMLOption(&r.Config) - } - return r -} - -// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs. -func (r *TypographerHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { - reg.Register(ast.KindTypographicText, r.renderTypographicText) -} - -func (r *TypographerHTMLRenderer) renderTypographicText(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { - if entering { - w.Write(n.(*ast.TypographicText).Value) - } - return gast.WalkContinue, nil -} - type typographer struct { options []TypographerOption } @@ -265,7 +242,4 @@ func (e *typographer) Extend(m goldmark.Markdown) { m.Parser().AddOptions(parser.WithInlineParsers( util.Prioritized(NewTypographerParser(e.options...), 9999), )) - m.Renderer().AddOptions(renderer.WithNodeRenderers( - util.Prioritized(NewTypographerHTMLRenderer(), 500), - )) } diff --git a/renderer/html/html.go b/renderer/html/html.go index 8551b78..bfd9761 100644 --- a/renderer/html/html.go +++ b/renderer/html/html.go @@ -183,6 +183,7 @@ func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { reg.Register(ast.KindLink, r.renderLink) reg.Register(ast.KindRawHTML, r.renderRawHTML) reg.Register(ast.KindText, r.renderText) + reg.Register(ast.KindString, r.renderString) } func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) { @@ -198,40 +199,38 @@ func (r *Renderer) renderDocument(w util.BufWriter, source []byte, node ast.Node return ast.WalkContinue, nil } -var attrNameID = []byte("id") - func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.Heading) if entering { - w.WriteString("') + _ = w.WriteByte('>') } else { - w.WriteString("\n") + _, _ = w.WriteString("\n") } return ast.WalkContinue, nil } func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - w.WriteString("
\n") + _, _ = w.WriteString("
\n") } else { - w.WriteString("
\n") + _, _ = w.WriteString("
\n") } return ast.WalkContinue, nil } func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - w.WriteString("
")
+		_, _ = w.WriteString("
")
 		r.writeLines(w, source, n)
 	} else {
-		w.WriteString("
\n") + _, _ = w.WriteString("
\n") } return ast.WalkContinue, nil } @@ -239,17 +238,17 @@ func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.FencedCodeBlock) if entering { - w.WriteString("
')
+		_ = w.WriteByte('>')
 		r.writeLines(w, source, n)
 	} else {
-		w.WriteString("
\n") + _, _ = w.WriteString("\n") } return ast.WalkContinue, nil } @@ -261,18 +260,18 @@ func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Nod l := n.Lines().Len() for i := 0; i < l; i++ { line := n.Lines().At(i) - w.Write(line.Value(source)) + _, _ = w.Write(line.Value(source)) } } else { - w.WriteString("\n") + _, _ = w.WriteString("\n") } } else { if n.HasClosure() { if r.Unsafe { closure := n.ClosureLine - w.Write(closure.Value(source)) + _, _ = w.Write(closure.Value(source)) } else { - w.WriteString("\n") + _, _ = w.WriteString("\n") } } } @@ -286,41 +285,41 @@ func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, en tag = "ol" } if entering { - w.WriteByte('<') - w.WriteString(tag) + _ = w.WriteByte('<') + _, _ = w.WriteString(tag) if n.IsOrdered() && n.Start != 1 { fmt.Fprintf(w, " start=\"%d\">\n", n.Start) } else { - w.WriteString(">\n") + _, _ = w.WriteString(">\n") } } else { - w.WriteString("\n") + _, _ = w.WriteString("\n") } return ast.WalkContinue, nil } func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - w.WriteString("
  • ") + _, _ = w.WriteString("
  • ") fc := n.FirstChild() if fc != nil { if _, ok := fc.(*ast.TextBlock); !ok { - w.WriteByte('\n') + _ = w.WriteByte('\n') } } } else { - w.WriteString("
  • \n") + _, _ = w.WriteString("\n") } return ast.WalkContinue, nil } func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - w.WriteString("

    ") + _, _ = w.WriteString("

    ") } else { - w.WriteString("

    \n") + _, _ = w.WriteString("

    \n") } return ast.WalkContinue, nil } @@ -328,7 +327,7 @@ func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node, func (r *Renderer) renderTextBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { if _, ok := n.NextSibling().(ast.Node); ok && n.FirstChild() != nil { - w.WriteByte('\n') + _ = w.WriteByte('\n') } } return ast.WalkContinue, nil @@ -339,9 +338,9 @@ func (r *Renderer) renderThemanticBreak(w util.BufWriter, source []byte, n ast.N return ast.WalkContinue, nil } if r.XHTML { - w.WriteString("
    \n") + _, _ = w.WriteString("
    \n") } else { - w.WriteString("
    \n") + _, _ = w.WriteString("
    \n") } return ast.WalkContinue, nil } @@ -351,22 +350,22 @@ func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node if !entering { return ast.WalkContinue, nil } - w.WriteString(``) - w.Write(util.EscapeHTML(label)) - w.WriteString(``) + _, _ = w.Write(util.EscapeHTML(util.URLEscape(url, false))) + _, _ = w.WriteString(`">`) + _, _ = w.Write(util.EscapeHTML(label)) + _, _ = w.WriteString(``) return ast.WalkContinue, nil } func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - w.WriteString("") + _, _ = w.WriteString("") for c := n.FirstChild(); c != nil; c = c.NextSibling() { segment := c.(*ast.Text).Segment value := segment.Value(source) @@ -381,7 +380,7 @@ func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, e } return ast.WalkSkipChildren, nil } - w.WriteString("") + _, _ = w.WriteString("") return ast.WalkContinue, nil } @@ -392,13 +391,13 @@ func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node tag = "strong" } if entering { - w.WriteByte('<') - w.WriteString(tag) - w.WriteByte('>') + _ = w.WriteByte('<') + _, _ = w.WriteString(tag) + _ = w.WriteByte('>') } else { - w.WriteString("') + _, _ = w.WriteString("') } return ast.WalkContinue, nil } @@ -406,19 +405,19 @@ func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node func (r *Renderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.Link) if entering { - w.WriteString("') + _ = w.WriteByte('>') } else { - w.WriteString("") + _, _ = w.WriteString("") } return ast.WalkContinue, nil } @@ -427,22 +426,22 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, e return ast.WalkContinue, nil } n := node.(*ast.Image) - w.WriteString("`)
-	w.Write(n.Text(source))
-	w.WriteByte('") + _, _ = w.WriteString(" />") } else { - w.WriteString(">") + _, _ = w.WriteString(">") } return ast.WalkSkipChildren, nil } @@ -456,11 +455,11 @@ func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, l := n.Segments.Len() for i := 0; i < l; i++ { segment := n.Segments.At(i) - w.Write(segment.Value(source)) + _, _ = w.Write(segment.Value(source)) } return ast.WalkSkipChildren, nil } - w.WriteString("") + _, _ = w.WriteString("") return ast.WalkSkipChildren, nil } @@ -476,12 +475,29 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en r.Writer.Write(w, segment.Value(source)) if n.HardLineBreak() || (n.SoftLineBreak() && r.HardWraps) { if r.XHTML { - w.WriteString("
    \n") + _, _ = w.WriteString("
    \n") } else { - w.WriteString("
    \n") + _, _ = w.WriteString("
    \n") } } else if n.SoftLineBreak() { - w.WriteByte('\n') + _ = w.WriteByte('\n') + } + } + return ast.WalkContinue, nil +} + +func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + n := node.(*ast.String) + if n.IsCode() { + _, _ = w.Write(n.Value) + } else { + if n.IsRaw() { + r.Writer.RawWrite(w, n.Value) + } else { + r.Writer.Write(w, n.Value) } } return ast.WalkContinue, nil @@ -490,11 +506,11 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en // RenderAttributes renders given node's attributes. func (r *Renderer) RenderAttributes(w util.BufWriter, node ast.Node) { for _, attr := range node.Attributes() { - w.WriteString(" ") - w.Write(attr.Name) - w.WriteString(`="`) - w.Write(util.EscapeHTML(attr.Value)) - w.WriteByte('"') + _, _ = w.WriteString(" ") + _, _ = w.Write(attr.Name) + _, _ = w.WriteString(`="`) + _, _ = w.Write(util.EscapeHTML(attr.Value)) + _ = w.WriteByte('"') } } @@ -516,11 +532,11 @@ func escapeRune(writer util.BufWriter, r rune) { if r < 256 { v := util.EscapeHTMLByte(byte(r)) if v != nil { - writer.Write(v) + _, _ = writer.Write(v) return } } - writer.WriteRune(util.ToValidRune(r)) + _, _ = writer.WriteRune(util.ToValidRune(r)) } func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) { @@ -529,21 +545,21 @@ func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) { for i := 0; i < l; i++ { v := util.EscapeHTMLByte(source[i]) if v != nil { - writer.Write(source[i-n : i]) + _, _ = writer.Write(source[i-n : i]) n = 0 - writer.Write(v) + _, _ = writer.Write(v) continue } n++ } if n != 0 { - writer.Write(source[l-n:]) + _, _ = writer.Write(source[l-n:]) } } func (d *defaultWriter) Write(writer util.BufWriter, source []byte) { escaped := false - ok := false + var ok bool limit := len(source) n := 0 for i := 0; i < limit; i++ {