...

Source file src/github.com/yuin/goldmark/parser/delimiter.go

Documentation: github.com/yuin/goldmark/parser

     1  package parser
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/yuin/goldmark/ast"
     8  	"github.com/yuin/goldmark/text"
     9  	"github.com/yuin/goldmark/util"
    10  )
    11  
    12  // A DelimiterProcessor interface provides a set of functions about
    13  // Delimiter nodes.
    14  type DelimiterProcessor interface {
    15  	// IsDelimiter returns true if given character is a delimiter, otherwise false.
    16  	IsDelimiter(byte) bool
    17  
    18  	// CanOpenCloser returns true if given opener can close given closer, otherwise false.
    19  	CanOpenCloser(opener, closer *Delimiter) bool
    20  
    21  	// OnMatch will be called when new matched delimiter found.
    22  	// OnMatch should return a new Node correspond to the matched delimiter.
    23  	OnMatch(consumes int) ast.Node
    24  }
    25  
    26  // A Delimiter struct represents a delimiter like '*' of the Markdown text.
    27  type Delimiter struct {
    28  	ast.BaseInline
    29  
    30  	Segment text.Segment
    31  
    32  	// CanOpen is set true if this delimiter can open a span for a new node.
    33  	// See https://spec.commonmark.org/0.30/#can-open-emphasis for details.
    34  	CanOpen bool
    35  
    36  	// CanClose is set true if this delimiter can close a span for a new node.
    37  	// See https://spec.commonmark.org/0.30/#can-open-emphasis for details.
    38  	CanClose bool
    39  
    40  	// Length is a remaining length of this delimiter.
    41  	Length int
    42  
    43  	// OriginalLength is a original length of this delimiter.
    44  	OriginalLength int
    45  
    46  	// Char is a character of this delimiter.
    47  	Char byte
    48  
    49  	// PreviousDelimiter is a previous sibling delimiter node of this delimiter.
    50  	PreviousDelimiter *Delimiter
    51  
    52  	// NextDelimiter is a next sibling delimiter node of this delimiter.
    53  	NextDelimiter *Delimiter
    54  
    55  	// Processor is a DelimiterProcessor associated with this delimiter.
    56  	Processor DelimiterProcessor
    57  }
    58  
    59  // Inline implements Inline.Inline.
    60  func (d *Delimiter) Inline() {}
    61  
    62  // Dump implements Node.Dump.
    63  func (d *Delimiter) Dump(source []byte, level int) {
    64  	fmt.Printf("%sDelimiter: \"%s\"\n", strings.Repeat("    ", level), string(d.Text(source)))
    65  }
    66  
    67  var kindDelimiter = ast.NewNodeKind("Delimiter")
    68  
    69  // Kind implements Node.Kind
    70  func (d *Delimiter) Kind() ast.NodeKind {
    71  	return kindDelimiter
    72  }
    73  
    74  // Text implements Node.Text
    75  func (d *Delimiter) Text(source []byte) []byte {
    76  	return d.Segment.Value(source)
    77  }
    78  
    79  // ConsumeCharacters consumes delimiters.
    80  func (d *Delimiter) ConsumeCharacters(n int) {
    81  	d.Length -= n
    82  	d.Segment = d.Segment.WithStop(d.Segment.Start + d.Length)
    83  }
    84  
    85  // CalcComsumption calculates how many characters should be used for opening
    86  // a new span correspond to given closer.
    87  func (d *Delimiter) CalcComsumption(closer *Delimiter) int {
    88  	if (d.CanClose || closer.CanOpen) && (d.OriginalLength+closer.OriginalLength)%3 == 0 && closer.OriginalLength%3 != 0 {
    89  		return 0
    90  	}
    91  	if d.Length >= 2 && closer.Length >= 2 {
    92  		return 2
    93  	}
    94  	return 1
    95  }
    96  
    97  // NewDelimiter returns a new Delimiter node.
    98  func NewDelimiter(canOpen, canClose bool, length int, char byte, processor DelimiterProcessor) *Delimiter {
    99  	c := &Delimiter{
   100  		BaseInline:        ast.BaseInline{},
   101  		CanOpen:           canOpen,
   102  		CanClose:          canClose,
   103  		Length:            length,
   104  		OriginalLength:    length,
   105  		Char:              char,
   106  		PreviousDelimiter: nil,
   107  		NextDelimiter:     nil,
   108  		Processor:         processor,
   109  	}
   110  	return c
   111  }
   112  
   113  // ScanDelimiter scans a delimiter by given DelimiterProcessor.
   114  func ScanDelimiter(line []byte, before rune, min int, processor DelimiterProcessor) *Delimiter {
   115  	i := 0
   116  	c := line[i]
   117  	j := i
   118  	if !processor.IsDelimiter(c) {
   119  		return nil
   120  	}
   121  	for ; j < len(line) && c == line[j]; j++ {
   122  	}
   123  	if (j - i) >= min {
   124  		after := rune(' ')
   125  		if j != len(line) {
   126  			after = util.ToRune(line, j)
   127  		}
   128  
   129  		canOpen, canClose := false, false
   130  		beforeIsPunctuation := util.IsPunctRune(before)
   131  		beforeIsWhitespace := util.IsSpaceRune(before)
   132  		afterIsPunctuation := util.IsPunctRune(after)
   133  		afterIsWhitespace := util.IsSpaceRune(after)
   134  
   135  		isLeft := !afterIsWhitespace &&
   136  			(!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation)
   137  		isRight := !beforeIsWhitespace &&
   138  			(!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation)
   139  
   140  		if line[i] == '_' {
   141  			canOpen = isLeft && (!isRight || beforeIsPunctuation)
   142  			canClose = isRight && (!isLeft || afterIsPunctuation)
   143  		} else {
   144  			canOpen = isLeft
   145  			canClose = isRight
   146  		}
   147  		return NewDelimiter(canOpen, canClose, j-i, c, processor)
   148  	}
   149  	return nil
   150  }
   151  
   152  // ProcessDelimiters processes the delimiter list in the context.
   153  // Processing will be stop when reaching the bottom.
   154  //
   155  // If you implement an inline parser that can have other inline nodes as
   156  // children, you should call this function when nesting span has closed.
   157  func ProcessDelimiters(bottom ast.Node, pc Context) {
   158  	lastDelimiter := pc.LastDelimiter()
   159  	if lastDelimiter == nil {
   160  		return
   161  	}
   162  	var closer *Delimiter
   163  	if bottom != nil {
   164  		if bottom != lastDelimiter {
   165  			for c := lastDelimiter.PreviousSibling(); c != nil && c != bottom; {
   166  				if d, ok := c.(*Delimiter); ok {
   167  					closer = d
   168  				}
   169  				c = c.PreviousSibling()
   170  			}
   171  		}
   172  	} else {
   173  		closer = pc.FirstDelimiter()
   174  	}
   175  	if closer == nil {
   176  		pc.ClearDelimiters(bottom)
   177  		return
   178  	}
   179  	for closer != nil {
   180  		if !closer.CanClose {
   181  			closer = closer.NextDelimiter
   182  			continue
   183  		}
   184  		consume := 0
   185  		found := false
   186  		maybeOpener := false
   187  		var opener *Delimiter
   188  		for opener = closer.PreviousDelimiter; opener != nil && opener != bottom; opener = opener.PreviousDelimiter {
   189  			if opener.CanOpen && opener.Processor.CanOpenCloser(opener, closer) {
   190  				maybeOpener = true
   191  				consume = opener.CalcComsumption(closer)
   192  				if consume > 0 {
   193  					found = true
   194  					break
   195  				}
   196  			}
   197  		}
   198  		if !found {
   199  			next := closer.NextDelimiter
   200  			if !maybeOpener && !closer.CanOpen {
   201  				pc.RemoveDelimiter(closer)
   202  			}
   203  			closer = next
   204  			continue
   205  		}
   206  		opener.ConsumeCharacters(consume)
   207  		closer.ConsumeCharacters(consume)
   208  
   209  		node := opener.Processor.OnMatch(consume)
   210  
   211  		parent := opener.Parent()
   212  		child := opener.NextSibling()
   213  
   214  		for child != nil && child != closer {
   215  			next := child.NextSibling()
   216  			node.AppendChild(node, child)
   217  			child = next
   218  		}
   219  		parent.InsertAfter(parent, opener, node)
   220  
   221  		for c := opener.NextDelimiter; c != nil && c != closer; {
   222  			next := c.NextDelimiter
   223  			pc.RemoveDelimiter(c)
   224  			c = next
   225  		}
   226  
   227  		if opener.Length == 0 {
   228  			pc.RemoveDelimiter(opener)
   229  		}
   230  
   231  		if closer.Length == 0 {
   232  			next := closer.NextDelimiter
   233  			pc.RemoveDelimiter(closer)
   234  			closer = next
   235  		}
   236  	}
   237  	pc.ClearDelimiters(bottom)
   238  }
   239  

View as plain text