github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/markup/goldmark/internal/extensions/attributes/attributes.go (about)

     1  package attributes
     2  
     3  import (
     4  	"github.com/yuin/goldmark"
     5  	"github.com/yuin/goldmark/ast"
     6  	"github.com/yuin/goldmark/parser"
     7  	"github.com/yuin/goldmark/text"
     8  	"github.com/yuin/goldmark/util"
     9  )
    10  
    11  // This extenion is based on/inspired by https://github.com/mdigger/goldmark-attributes
    12  // MIT License
    13  // Copyright (c) 2019 Dmitry Sedykh
    14  
    15  var (
    16  	kindAttributesBlock = ast.NewNodeKind("AttributesBlock")
    17  
    18  	defaultParser                        = new(attrParser)
    19  	defaultTransformer                   = new(transformer)
    20  	attributes         goldmark.Extender = new(attrExtension)
    21  )
    22  
    23  func New() goldmark.Extender {
    24  	return attributes
    25  }
    26  
    27  type attrExtension struct{}
    28  
    29  func (a *attrExtension) Extend(m goldmark.Markdown) {
    30  	m.Parser().AddOptions(
    31  		parser.WithBlockParsers(
    32  			util.Prioritized(defaultParser, 100)),
    33  		parser.WithASTTransformers(
    34  			util.Prioritized(defaultTransformer, 100),
    35  		),
    36  	)
    37  }
    38  
    39  type attrParser struct{}
    40  
    41  func (a *attrParser) CanAcceptIndentedLine() bool {
    42  	return false
    43  }
    44  
    45  func (a *attrParser) CanInterruptParagraph() bool {
    46  	return true
    47  }
    48  
    49  func (a *attrParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
    50  }
    51  
    52  func (a *attrParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
    53  	return parser.Close
    54  }
    55  
    56  func (a *attrParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
    57  	if attrs, ok := parser.ParseAttributes(reader); ok {
    58  		// add attributes
    59  		var node = &attributesBlock{
    60  			BaseBlock: ast.BaseBlock{},
    61  		}
    62  		for _, attr := range attrs {
    63  			node.SetAttribute(attr.Name, attr.Value)
    64  		}
    65  		return node, parser.NoChildren
    66  	}
    67  	return nil, parser.RequireParagraph
    68  }
    69  
    70  func (a *attrParser) Trigger() []byte {
    71  	return []byte{'{'}
    72  }
    73  
    74  type attributesBlock struct {
    75  	ast.BaseBlock
    76  }
    77  
    78  func (a *attributesBlock) Dump(source []byte, level int) {
    79  	attrs := a.Attributes()
    80  	list := make(map[string]string, len(attrs))
    81  	for _, attr := range attrs {
    82  		var (
    83  			name  = util.BytesToReadOnlyString(attr.Name)
    84  			value = util.BytesToReadOnlyString(util.EscapeHTML(attr.Value.([]byte)))
    85  		)
    86  		list[name] = value
    87  	}
    88  	ast.DumpHelper(a, source, level, list, nil)
    89  }
    90  
    91  func (a *attributesBlock) Kind() ast.NodeKind {
    92  	return kindAttributesBlock
    93  }
    94  
    95  type transformer struct{}
    96  
    97  func (a *transformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
    98  	var attributes = make([]ast.Node, 0, 500)
    99  	ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
   100  		if entering && node.Kind() == kindAttributesBlock {
   101  			// Attributes for fenced code blocks are handled in their own extension,
   102  			// but note that we currently only support code block attributes when
   103  			// CodeFences=true.
   104  			if node.PreviousSibling().Kind() != ast.KindFencedCodeBlock && !node.HasBlankPreviousLines() {
   105  				attributes = append(attributes, node)
   106  				return ast.WalkSkipChildren, nil
   107  			}
   108  		}
   109  
   110  		return ast.WalkContinue, nil
   111  	})
   112  
   113  	for _, attr := range attributes {
   114  		if prev := attr.PreviousSibling(); prev != nil &&
   115  			prev.Type() == ast.TypeBlock {
   116  			for _, attr := range attr.Attributes() {
   117  				if _, found := prev.Attribute(attr.Name); !found {
   118  					prev.SetAttribute(attr.Name, attr.Value)
   119  				}
   120  			}
   121  		}
   122  		// remove attributes node
   123  		attr.Parent().RemoveChild(attr.Parent(), attr)
   124  	}
   125  }