Source File
table.go
Belonging Package
github.com/yuin/goldmark/extension
package extension
import (
gast
)
var escapedPipeCellListKey = parser.NewContextKey()
type escapedPipeCell struct {
Cell *ast.TableCell
Pos []int
Transformed bool
}
type TableCellAlignMethod int
type TableConfig struct {
html.Config
type TableOption interface {
SetTableOption(*TableConfig)
}
func () TableConfig {
return TableConfig{
Config: html.NewConfig(),
TableCellAlignMethod: TableCellAlignDefault,
}
}
func ( *TableConfig) ( renderer.OptionName, interface{}) {
switch {
case optTableCellAlignMethod:
.TableCellAlignMethod = .(TableCellAlignMethod)
default:
.Config.SetOption(, )
}
}
type withTableHTMLOptions struct {
value []html.Option
}
func ( *withTableHTMLOptions) ( *renderer.Config) {
if .value != nil {
for , := range .value {
.(renderer.Option).SetConfig()
}
}
}
func ( *withTableHTMLOptions) ( *TableConfig) {
if .value != nil {
for , := range .value {
.SetHTMLOption(&.Config)
}
}
}
func ( ...html.Option) TableOption {
return &withTableHTMLOptions{}
}
const optTableCellAlignMethod renderer.OptionName = "TableTableCellAlignMethod"
type withTableCellAlignMethod struct {
value TableCellAlignMethod
}
func ( *withTableCellAlignMethod) ( *renderer.Config) {
.Options[optTableCellAlignMethod] = .value
}
func ( *withTableCellAlignMethod) ( *TableConfig) {
.TableCellAlignMethod = .value
}
func ( TableCellAlignMethod) TableOption {
return &withTableCellAlignMethod{}
}
func ( []byte) bool {
for , := range {
if !(util.IsSpace() || == '-' || == '|' || == ':') {
return false
}
}
return true
}
var tableDelimLeft = regexp.MustCompile(`^\s*\:\-+\s*$`)
var tableDelimRight = regexp.MustCompile(`^\s*\-+\:\s*$`)
var tableDelimCenter = regexp.MustCompile(`^\s*\:\-+\:\s*$`)
var tableDelimNone = regexp.MustCompile(`^\s*\-+\s*$`)
type tableParagraphTransformer struct {
}
var defaultTableParagraphTransformer = &tableParagraphTransformer{}
func () parser.ParagraphTransformer {
return defaultTableParagraphTransformer
}
func ( *tableParagraphTransformer) ( *gast.Paragraph, text.Reader, parser.Context) {
:= .Lines()
if .Len() < 2 {
return
}
for := 1; < .Len(); ++ {
:= .parseDelimiter(.At(), )
if == nil {
continue
}
:= .parseRow(.At(-1), , true, , )
if == nil || len() != .ChildCount() {
return
}
:= ast.NewTable()
.Alignments =
.AppendChild(, ast.NewTableHeader())
for := + 1; < .Len(); ++ {
.AppendChild(, .parseRow(.At(), , false, , ))
}
.Lines().SetSliced(0, -1)
.Parent().InsertAfter(.Parent(), , )
if .Lines().Len() == 0 {
.Parent().RemoveChild(.Parent(), )
} else {
:= .Lines().At( - 2)
.Stop = .Stop - 1 // trim last newline(\n)
.Lines().Set(-2, )
}
}
}
func ( *tableParagraphTransformer) ( text.Segment, []ast.Alignment, bool, text.Reader, parser.Context) *ast.TableRow {
:= .Source()
:= .Value()
:= 0
+= util.TrimLeftSpaceLength()
:= len()
-= util.TrimRightSpaceLength()
:= ast.NewTableRow()
if len() > 0 && [] == '|' {
++
}
if len() > 0 && [-1] == '|' {
--
}
:= 0
for ; < ; ++ {
:= ast.AlignNone
if >= len() {
if ! {
return
}
} else {
= []
}
var *escapedPipeCell
:= ast.NewTableCell()
.Alignment =
:= false
:=
for ; < ; ++ {
if [] == '`' {
= true
}
if [] == '|' {
if == 0 || [-1] != '\\' {
break
} else if {
if == nil {
= &escapedPipeCell{, []int{}, false}
:= .ComputeIfAbsent(escapedPipeCellListKey,
func() interface{} {
return []*escapedPipeCell{}
}).([]*escapedPipeCell)
= append(, )
.Set(escapedPipeCellListKey, )
}
.Pos = append(.Pos, .Start+-1)
}
}
}
:= text.NewSegment(.Start+, .Start+)
= .TrimLeftSpace()
= .TrimRightSpace()
.Lines().Append()
.AppendChild(, )
= + 1
}
for ; < len(); ++ {
.AppendChild(, ast.NewTableCell())
}
return
}
func ( *tableParagraphTransformer) ( text.Segment, text.Reader) []ast.Alignment {
:= .Value(.Source())
if !isTableDelim() {
return nil
}
:= bytes.Split(, []byte{'|'})
if util.IsBlank([0]) {
= [1:]
}
if len() > 0 && util.IsBlank([len()-1]) {
= [:len()-1]
}
var []ast.Alignment
for , := range {
if tableDelimLeft.Match() {
= append(, ast.AlignLeft)
} else if tableDelimRight.Match() {
= append(, ast.AlignRight)
} else if tableDelimCenter.Match() {
= append(, ast.AlignCenter)
} else if tableDelimNone.Match() {
= append(, ast.AlignNone)
} else {
return nil
}
}
return
}
type tableASTTransformer struct {
}
var defaultTableASTTransformer = &tableASTTransformer{}
func () parser.ASTTransformer {
return defaultTableASTTransformer
}
func ( *tableASTTransformer) ( *gast.Document, text.Reader, parser.Context) {
:= .Get(escapedPipeCellListKey)
if == nil {
return
}
.Set(escapedPipeCellListKey, nil)
for , := range .([]*escapedPipeCell) {
if .Transformed {
continue
}
_ = gast.Walk(.Cell, func( gast.Node, bool) (gast.WalkStatus, error) {
if ! || .Kind() != gast.KindCodeSpan {
return gast.WalkContinue, nil
}
for := .FirstChild(); != nil; {
:= .NextSibling()
if .Kind() != gast.KindText {
=
continue
}
:= .Parent()
:= &.(*gast.Text).Segment
:=
for , := range .([]*escapedPipeCell) {
for , := range .Pos {
if .Start <= && < .Stop {
:= .(*gast.Text).Segment
:= gast.NewRawTextSegment(.WithStop())
:= gast.NewRawTextSegment(.WithStart( + 1))
.InsertAfter(, , )
.InsertAfter(, , )
.RemoveChild(, )
=
.Transformed = true
}
}
}
=
}
return gast.WalkContinue, nil
})
}
}
type TableHTMLRenderer struct {
TableConfig
}
func ( ...TableOption) renderer.NodeRenderer {
:= &TableHTMLRenderer{
TableConfig: NewTableConfig(),
}
for , := range {
.SetTableOption(&.TableConfig)
}
return
}
var TableAttributeFilter = html.GlobalAttributeFilter.Extend(
[]byte("align"), // [Deprecated]
[]byte("bgcolor"), // [Deprecated]
[]byte("border"), // [Deprecated]
[]byte("cellpadding"), // [Deprecated]
[]byte("cellspacing"), // [Deprecated]
[]byte("frame"), // [Deprecated]
[]byte("rules"), // [Deprecated]
[]byte("summary"), // [Deprecated]
[]byte("width"), // [Deprecated]
)
func ( *TableHTMLRenderer) ( util.BufWriter, []byte, gast.Node, bool) (gast.WalkStatus, error) {
if {
_, _ = .WriteString("<table")
if .Attributes() != nil {
html.RenderAttributes(, , TableAttributeFilter)
}
_, _ = .WriteString(">\n")
} else {
_, _ = .WriteString("</table>\n")
}
return gast.WalkContinue, nil
}
var TableHeaderAttributeFilter = html.GlobalAttributeFilter.Extend(
[]byte("align"), // [Deprecated since HTML4] [Obsolete since HTML5]
[]byte("bgcolor"), // [Not Standardized]
[]byte("char"), // [Deprecated since HTML4] [Obsolete since HTML5]
[]byte("charoff"), // [Deprecated since HTML4] [Obsolete since HTML5]
[]byte("valign"), // [Deprecated since HTML4] [Obsolete since HTML5]
)
func ( *TableHTMLRenderer) ( util.BufWriter, []byte, gast.Node, bool) (gast.WalkStatus, error) {
if {
_, _ = .WriteString("<thead")
if .Attributes() != nil {
html.RenderAttributes(, , TableHeaderAttributeFilter)
}
_, _ = .WriteString(">\n")
_, _ = .WriteString("<tr>\n") // Header <tr> has no separate handle
} else {
_, _ = .WriteString("</tr>\n")
_, _ = .WriteString("</thead>\n")
if .NextSibling() != nil {
_, _ = .WriteString("<tbody>\n")
}
}
return gast.WalkContinue, nil
}
var TableRowAttributeFilter = html.GlobalAttributeFilter.Extend(
[]byte("align"), // [Obsolete since HTML5]
[]byte("bgcolor"), // [Obsolete since HTML5]
[]byte("char"), // [Obsolete since HTML5]
[]byte("charoff"), // [Obsolete since HTML5]
[]byte("valign"), // [Obsolete since HTML5]
)
func ( *TableHTMLRenderer) ( util.BufWriter, []byte, gast.Node, bool) (gast.WalkStatus, error) {
if {
_, _ = .WriteString("<tr")
if .Attributes() != nil {
html.RenderAttributes(, , TableRowAttributeFilter)
}
_, _ = .WriteString(">\n")
} else {
_, _ = .WriteString("</tr>\n")
if .Parent().LastChild() == {
_, _ = .WriteString("</tbody>\n")
}
}
return gast.WalkContinue, nil
}
var TableThCellAttributeFilter = html.GlobalAttributeFilter.Extend(
[]byte("abbr"), // [OK] Contains a short abbreviated description of the cell's content [NOT OK in <td>]
[]byte("align"), // [Obsolete since HTML5]
[]byte("axis"), // [Obsolete since HTML5]
[]byte("bgcolor"), // [Not Standardized]
[]byte("char"), // [Obsolete since HTML5]
[]byte("charoff"), // [Obsolete since HTML5]
[]byte("colspan"), // [OK] Number of columns that the cell is to span
[]byte("headers"), // [OK] This attribute contains a list of space-separated strings, each corresponding to the id attribute of the <th> elements that apply to this element
[]byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
[]byte("rowspan"), // [OK] Number of rows that the cell is to span
[]byte("scope"), // [OK] This enumerated attribute defines the cells that the header (defined in the <th>) element relates to [NOT OK in <td>]
[]byte("valign"), // [Obsolete since HTML5]
[]byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
)
var TableTdCellAttributeFilter = html.GlobalAttributeFilter.Extend(
[]byte("abbr"), // [Obsolete since HTML5] [OK in <th>]
[]byte("align"), // [Obsolete since HTML5]
[]byte("axis"), // [Obsolete since HTML5]
[]byte("bgcolor"), // [Not Standardized]
[]byte("char"), // [Obsolete since HTML5]
[]byte("charoff"), // [Obsolete since HTML5]
[]byte("colspan"), // [OK] Number of columns that the cell is to span
[]byte("headers"), // [OK] This attribute contains a list of space-separated strings, each corresponding to the id attribute of the <th> elements that apply to this element
[]byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
[]byte("rowspan"), // [OK] Number of rows that the cell is to span
[]byte("scope"), // [Obsolete since HTML5] [OK in <th>]
[]byte("valign"), // [Obsolete since HTML5]
[]byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
)
func ( *TableHTMLRenderer) ( util.BufWriter, []byte, gast.Node, bool) (gast.WalkStatus, error) {
:= .(*ast.TableCell)
:= "td"
if .Parent().Kind() == ast.KindTableHeader {
= "th"
}
if {
fmt.Fprintf(, "<%s", )
if .Alignment != ast.AlignNone {
:= .TableConfig.TableCellAlignMethod
if == TableCellAlignDefault {
if .Config.XHTML {
= TableCellAlignAttribute
} else {
= TableCellAlignStyle
}
}
switch {
case TableCellAlignAttribute:
if , := .AttributeString("align"); ! { // Skip align render if overridden
fmt.Fprintf(, ` align="%s"`, .Alignment.String())
}
case TableCellAlignStyle:
, := .AttributeString("style")
var util.CopyOnWriteBuffer
if {
= util.NewCopyOnWriteBuffer(.([]byte))
.AppendByte(';')
}
:= fmt.Sprintf("text-align:%s", .Alignment.String())
.AppendString()
.SetAttributeString("style", .Bytes())
}
}
if .Attributes() != nil {
if == "td" {
html.RenderAttributes(, , TableTdCellAttributeFilter) // <td>
} else {
html.RenderAttributes(, , TableThCellAttributeFilter) // <th>
}
}
_ = .WriteByte('>')
} else {
fmt.Fprintf(, "</%s>\n", )
}
return gast.WalkContinue, nil
}
type table struct {
options []TableOption
}
var Table = &table{
options: []TableOption{},
}
func ( ...TableOption) goldmark.Extender {
return &table{
options: ,
}
}
func ( *table) ( goldmark.Markdown) {
.Parser().AddOptions(
parser.WithParagraphTransformers(
util.Prioritized(NewTableParagraphTransformer(), 200),
),
parser.WithASTTransformers(
util.Prioritized(defaultTableASTTransformer, 0),
),
)
.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewTableHTMLRenderer(.options...), 500),
))
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |