diff --git a/ast/ast.go b/ast/ast.go index 9bffe67..9576efe 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -174,7 +174,7 @@ type Node interface { RemoveAttributes() } -// A BaseNode struct implements the Node interface. +// A BaseNode struct implements the Node interface partialliy. type BaseNode struct { firstChild Node lastChild Node diff --git a/ast/block.go b/ast/block.go index d473482..fc0b3c2 100644 --- a/ast/block.go +++ b/ast/block.go @@ -7,7 +7,7 @@ import ( textm "github.com/yuin/goldmark/text" ) -// A BaseBlock struct implements the Node interface. +// A BaseBlock struct implements the Node interface partialliy. type BaseBlock struct { BaseNode blankPreviousLines bool diff --git a/ast/inline.go b/ast/inline.go index 23dcad4..b221695 100644 --- a/ast/inline.go +++ b/ast/inline.go @@ -8,7 +8,7 @@ import ( "github.com/yuin/goldmark/util" ) -// A BaseInline struct implements the Node interface. +// A BaseInline struct implements the Node interface partialliy. type BaseInline struct { BaseNode } diff --git a/extension/_test/table.txt b/extension/_test/table.txt index 42e977a..2dc26f2 100644 --- a/extension/_test/table.txt +++ b/extension/_test/table.txt @@ -61,7 +61,7 @@ bar | baz -b \| az +b | az b | im diff --git a/extension/table.go b/extension/table.go index 580ca9e..c40bdef 100644 --- a/extension/table.go +++ b/extension/table.go @@ -15,6 +15,13 @@ import ( "github.com/yuin/goldmark/util" ) +var escapedPipeCellListKey = parser.NewContextKey() + +type escapedPipeCell struct { + Cell *ast.TableCell + Pos []int +} + // TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format. type TableCellAlignMethod int @@ -148,7 +155,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text. if alignments == nil { continue } - header := b.parseRow(lines.At(i-1), alignments, true, reader) + header := b.parseRow(lines.At(i-1), alignments, true, reader, pc) if header == nil || len(alignments) != header.ChildCount() { return } @@ -156,7 +163,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text. table.Alignments = alignments table.AppendChild(table, ast.NewTableHeader(header)) for j := i + 1; j < lines.Len(); j++ { - table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader)) + table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader, pc)) } node.Lines().SetSliced(0, i-1) node.Parent().InsertAfter(node.Parent(), node, table) @@ -170,7 +177,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text. } } -func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader) *ast.TableRow { +func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow { source := reader.Source() line := segment.Value(source) pos := 0 @@ -194,18 +201,39 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments [] } else { alignment = alignments[i] } - closure := util.FindClosure(line[pos:], byte(0), '|', true, false) - if closure < 0 { - closure = len(line[pos:]) - } + + var escapedCell *escapedPipeCell node := ast.NewTableCell() - seg := text.NewSegment(segment.Start+pos, segment.Start+pos+closure) + node.Alignment = alignment + hasBacktick := false + closure := pos + for ; closure < limit; closure++ { + if line[closure] == '`' { + hasBacktick = true + } + if line[closure] == '|' { + if closure == 0 || line[closure-1] != '\\' { + break + } else if hasBacktick { + if escapedCell == nil { + escapedCell = &escapedPipeCell{node, []int{}} + escapedList := pc.ComputeIfAbsent(escapedPipeCellListKey, + func() interface{} { + return []*escapedPipeCell{} + }).([]*escapedPipeCell) + escapedList = append(escapedList, escapedCell) + pc.Set(escapedPipeCellListKey, escapedList) + } + escapedCell.Pos = append(escapedCell.Pos, segment.Start+closure-1) + } + } + } + seg := text.NewSegment(segment.Start+pos, segment.Start+closure) seg = seg.TrimLeftSpace(source) seg = seg.TrimRightSpace(source) node.Lines().Append(seg) - node.Alignment = alignment row.AppendChild(row, node) - pos += closure + 1 + pos = closure + 1 } for ; i < len(alignments); i++ { row.AppendChild(row, ast.NewTableCell()) @@ -243,6 +271,49 @@ func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader return alignments } +type tableASTTransformer struct { +} + +var defaultTableASTTransformer = &tableASTTransformer{} + +// NewTableASTTransformer returns a parser.ASTTransformer for tables. +func NewTableASTTransformer() parser.ASTTransformer { + return defaultTableASTTransformer +} + +func (a *tableASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) { + lst := pc.Get(escapedPipeCellListKey) + if lst == nil { + return + } + pc.Set(escapedPipeCellListKey, nil) + for _, v := range lst.([]*escapedPipeCell) { + _ = gast.Walk(v.Cell, func(n gast.Node, entering bool) (gast.WalkStatus, error) { + if n.Kind() != gast.KindCodeSpan { + return gast.WalkContinue, nil + } + c := n.FirstChild() + for c != nil { + next := c.NextSibling() + if c.Kind() == gast.KindText { + t := c.(*gast.Text) + for _, pos := range v.Pos { + if t.Segment.Start <= pos && t.Segment.Stop > pos { + n1 := gast.NewRawTextSegment(t.Segment.WithStop(pos)) + n2 := gast.NewRawTextSegment(t.Segment.WithStart(pos + 1)) + n.InsertAfter(n, c, n1) + n.InsertAfter(n, n1, n2) + n.RemoveChild(n, c) + } + } + } + c = next + } + return gast.WalkContinue, nil + }) + } +} + // TableHTMLRenderer is a renderer.NodeRenderer implementation that // renders Table nodes. type TableHTMLRenderer struct { @@ -454,9 +525,14 @@ func NewTable(opts ...TableOption) goldmark.Extender { } func (e *table) Extend(m goldmark.Markdown) { - m.Parser().AddOptions(parser.WithParagraphTransformers( - util.Prioritized(NewTableParagraphTransformer(), 200), - )) + m.Parser().AddOptions( + parser.WithParagraphTransformers( + util.Prioritized(NewTableParagraphTransformer(), 200), + ), + parser.WithASTTransformers( + util.Prioritized(defaultTableASTTransformer, 0), + ), + ) m.Renderer().AddOptions(renderer.WithNodeRenderers( util.Prioritized(NewTableHTMLRenderer(e.options...), 500), )) diff --git a/parser/parser.go b/parser/parser.go index def13db..e58b5ee 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -138,6 +138,9 @@ type Context interface { // Get returns a value associated with the given key. Get(ContextKey) interface{} + // ComputeIfAbsent computes a value if a value associated with the given key is absent and returns the value. + ComputeIfAbsent(ContextKey, func() interface{}) interface{} + // Set sets the given value to the context. Set(ContextKey, interface{}) @@ -252,6 +255,15 @@ func (p *parseContext) Get(key ContextKey) interface{} { return p.store[key] } +func (p *parseContext) ComputeIfAbsent(key ContextKey, f func() interface{}) interface{} { + v := p.store[key] + if v == nil { + v = f() + p.store[key] = v + } + return v +} + func (p *parseContext) Set(key ContextKey, value interface{}) { p.store[key] = value }