...

Source file src/github.com/yuin/goldmark/extension/definition_list.go

Documentation: github.com/yuin/goldmark/extension

     1  package extension
     2  
     3  import (
     4  	"github.com/yuin/goldmark"
     5  	gast "github.com/yuin/goldmark/ast"
     6  	"github.com/yuin/goldmark/extension/ast"
     7  	"github.com/yuin/goldmark/parser"
     8  	"github.com/yuin/goldmark/renderer"
     9  	"github.com/yuin/goldmark/renderer/html"
    10  	"github.com/yuin/goldmark/text"
    11  	"github.com/yuin/goldmark/util"
    12  )
    13  
    14  type definitionListParser struct {
    15  }
    16  
    17  var defaultDefinitionListParser = &definitionListParser{}
    18  
    19  // NewDefinitionListParser return a new parser.BlockParser that
    20  // can parse PHP Markdown Extra Definition lists.
    21  func NewDefinitionListParser() parser.BlockParser {
    22  	return defaultDefinitionListParser
    23  }
    24  
    25  func (b *definitionListParser) Trigger() []byte {
    26  	return []byte{':'}
    27  }
    28  
    29  func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
    30  	if _, ok := parent.(*ast.DefinitionList); ok {
    31  		return nil, parser.NoChildren
    32  	}
    33  	line, _ := reader.PeekLine()
    34  	pos := pc.BlockOffset()
    35  	indent := pc.BlockIndent()
    36  	if pos < 0 || line[pos] != ':' || indent != 0 {
    37  		return nil, parser.NoChildren
    38  	}
    39  
    40  	last := parent.LastChild()
    41  	// need 1 or more spaces after ':'
    42  	w, _ := util.IndentWidth(line[pos+1:], pos+1)
    43  	if w < 1 {
    44  		return nil, parser.NoChildren
    45  	}
    46  	if w >= 8 { // starts with indented code
    47  		w = 5
    48  	}
    49  	w += pos + 1 /* 1 = ':' */
    50  
    51  	para, lastIsParagraph := last.(*gast.Paragraph)
    52  	var list *ast.DefinitionList
    53  	status := parser.HasChildren
    54  	var ok bool
    55  	if lastIsParagraph {
    56  		list, ok = last.PreviousSibling().(*ast.DefinitionList)
    57  		if ok { // is not first item
    58  			list.Offset = w
    59  			list.TemporaryParagraph = para
    60  		} else { // is first item
    61  			list = ast.NewDefinitionList(w, para)
    62  			status |= parser.RequireParagraph
    63  		}
    64  	} else if list, ok = last.(*ast.DefinitionList); ok { // multiple description
    65  		list.Offset = w
    66  		list.TemporaryParagraph = nil
    67  	} else {
    68  		return nil, parser.NoChildren
    69  	}
    70  
    71  	return list, status
    72  }
    73  
    74  func (b *definitionListParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
    75  	line, _ := reader.PeekLine()
    76  	if util.IsBlank(line) {
    77  		return parser.Continue | parser.HasChildren
    78  	}
    79  	list, _ := node.(*ast.DefinitionList)
    80  	w, _ := util.IndentWidth(line, reader.LineOffset())
    81  	if w < list.Offset {
    82  		return parser.Close
    83  	}
    84  	pos, padding := util.IndentPosition(line, reader.LineOffset(), list.Offset)
    85  	reader.AdvanceAndSetPadding(pos, padding)
    86  	return parser.Continue | parser.HasChildren
    87  }
    88  
    89  func (b *definitionListParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
    90  	// nothing to do
    91  }
    92  
    93  func (b *definitionListParser) CanInterruptParagraph() bool {
    94  	return true
    95  }
    96  
    97  func (b *definitionListParser) CanAcceptIndentedLine() bool {
    98  	return false
    99  }
   100  
   101  type definitionDescriptionParser struct {
   102  }
   103  
   104  var defaultDefinitionDescriptionParser = &definitionDescriptionParser{}
   105  
   106  // NewDefinitionDescriptionParser return a new parser.BlockParser that
   107  // can parse definition description starts with ':'.
   108  func NewDefinitionDescriptionParser() parser.BlockParser {
   109  	return defaultDefinitionDescriptionParser
   110  }
   111  
   112  func (b *definitionDescriptionParser) Trigger() []byte {
   113  	return []byte{':'}
   114  }
   115  
   116  func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
   117  	line, _ := reader.PeekLine()
   118  	pos := pc.BlockOffset()
   119  	indent := pc.BlockIndent()
   120  	if pos < 0 || line[pos] != ':' || indent != 0 {
   121  		return nil, parser.NoChildren
   122  	}
   123  	list, _ := parent.(*ast.DefinitionList)
   124  	if list == nil {
   125  		return nil, parser.NoChildren
   126  	}
   127  	para := list.TemporaryParagraph
   128  	list.TemporaryParagraph = nil
   129  	if para != nil {
   130  		lines := para.Lines()
   131  		l := lines.Len()
   132  		for i := 0; i < l; i++ {
   133  			term := ast.NewDefinitionTerm()
   134  			segment := lines.At(i)
   135  			term.Lines().Append(segment.TrimRightSpace(reader.Source()))
   136  			list.AppendChild(list, term)
   137  		}
   138  		para.Parent().RemoveChild(para.Parent(), para)
   139  	}
   140  	cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
   141  	reader.AdvanceAndSetPadding(cpos+1, padding)
   142  
   143  	return ast.NewDefinitionDescription(), parser.HasChildren
   144  }
   145  
   146  func (b *definitionDescriptionParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
   147  	// definitionListParser detects end of the description.
   148  	// so this method will never be called.
   149  	return parser.Continue | parser.HasChildren
   150  }
   151  
   152  func (b *definitionDescriptionParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
   153  	desc := node.(*ast.DefinitionDescription)
   154  	desc.IsTight = !desc.HasBlankPreviousLines()
   155  	if desc.IsTight {
   156  		for gc := desc.FirstChild(); gc != nil; gc = gc.NextSibling() {
   157  			paragraph, ok := gc.(*gast.Paragraph)
   158  			if ok {
   159  				textBlock := gast.NewTextBlock()
   160  				textBlock.SetLines(paragraph.Lines())
   161  				desc.ReplaceChild(desc, paragraph, textBlock)
   162  			}
   163  		}
   164  	}
   165  }
   166  
   167  func (b *definitionDescriptionParser) CanInterruptParagraph() bool {
   168  	return true
   169  }
   170  
   171  func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool {
   172  	return false
   173  }
   174  
   175  // DefinitionListHTMLRenderer is a renderer.NodeRenderer implementation that
   176  // renders DefinitionList nodes.
   177  type DefinitionListHTMLRenderer struct {
   178  	html.Config
   179  }
   180  
   181  // NewDefinitionListHTMLRenderer returns a new DefinitionListHTMLRenderer.
   182  func NewDefinitionListHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
   183  	r := &DefinitionListHTMLRenderer{
   184  		Config: html.NewConfig(),
   185  	}
   186  	for _, opt := range opts {
   187  		opt.SetHTMLOption(&r.Config)
   188  	}
   189  	return r
   190  }
   191  
   192  // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
   193  func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
   194  	reg.Register(ast.KindDefinitionList, r.renderDefinitionList)
   195  	reg.Register(ast.KindDefinitionTerm, r.renderDefinitionTerm)
   196  	reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
   197  }
   198  
   199  // DefinitionListAttributeFilter defines attribute names which dl elements can have.
   200  var DefinitionListAttributeFilter = html.GlobalAttributeFilter
   201  
   202  func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
   203  	if entering {
   204  		if n.Attributes() != nil {
   205  			_, _ = w.WriteString("<dl")
   206  			html.RenderAttributes(w, n, DefinitionListAttributeFilter)
   207  			_, _ = w.WriteString(">\n")
   208  		} else {
   209  			_, _ = w.WriteString("<dl>\n")
   210  		}
   211  	} else {
   212  		_, _ = w.WriteString("</dl>\n")
   213  	}
   214  	return gast.WalkContinue, nil
   215  }
   216  
   217  // DefinitionTermAttributeFilter defines attribute names which dd elements can have.
   218  var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
   219  
   220  func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
   221  	if entering {
   222  		if n.Attributes() != nil {
   223  			_, _ = w.WriteString("<dt")
   224  			html.RenderAttributes(w, n, DefinitionTermAttributeFilter)
   225  			_ = w.WriteByte('>')
   226  		} else {
   227  			_, _ = w.WriteString("<dt>")
   228  		}
   229  	} else {
   230  		_, _ = w.WriteString("</dt>\n")
   231  	}
   232  	return gast.WalkContinue, nil
   233  }
   234  
   235  // DefinitionDescriptionAttributeFilter defines attribute names which dd elements can have.
   236  var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
   237  
   238  func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
   239  	if entering {
   240  		n := node.(*ast.DefinitionDescription)
   241  		_, _ = w.WriteString("<dd")
   242  		if n.Attributes() != nil {
   243  			html.RenderAttributes(w, n, DefinitionDescriptionAttributeFilter)
   244  		}
   245  		if n.IsTight {
   246  			_, _ = w.WriteString(">")
   247  		} else {
   248  			_, _ = w.WriteString(">\n")
   249  		}
   250  	} else {
   251  		_, _ = w.WriteString("</dd>\n")
   252  	}
   253  	return gast.WalkContinue, nil
   254  }
   255  
   256  type definitionList struct {
   257  }
   258  
   259  // DefinitionList is an extension that allow you to use PHP Markdown Extra Definition lists.
   260  var DefinitionList = &definitionList{}
   261  
   262  func (e *definitionList) Extend(m goldmark.Markdown) {
   263  	m.Parser().AddOptions(parser.WithBlockParsers(
   264  		util.Prioritized(NewDefinitionListParser(), 101),
   265  		util.Prioritized(NewDefinitionDescriptionParser(), 102),
   266  	))
   267  	m.Renderer().AddOptions(renderer.WithNodeRenderers(
   268  		util.Prioritized(NewDefinitionListHTMLRenderer(), 500),
   269  	))
   270  }
   271  

View as plain text