@@ -36,7 +36,7 @@ type PassthroughInline struct {
3636 Delimiters * Delimiters
3737}
3838
39- func NewPassthroughInline (segment text.Segment , delimiters * Delimiters ) * PassthroughInline {
39+ func newPassthroughInline (segment text.Segment , delimiters * Delimiters ) * PassthroughInline {
4040 return & PassthroughInline {
4141 Segment : segment ,
4242 Delimiters : delimiters ,
@@ -69,15 +69,15 @@ type inlinePassthroughParser struct {
6969 PassthroughDelimiters []Delimiters
7070}
7171
72- func NewInlinePassthroughParser (ds []Delimiters ) parser.InlineParser {
72+ func newInlinePassthroughParser (ds []Delimiters ) parser.InlineParser {
7373 return & inlinePassthroughParser {
7474 PassthroughDelimiters : ds ,
7575 }
7676}
7777
7878// Determine if the input slice starts with a full valid opening delimiter.
7979// If so, returns the delimiter struct, otherwise returns nil.
80- func GetFullOpeningDelimiter (delims []Delimiters , line []byte ) * Delimiters {
80+ func getFullOpeningDelimiter (delims []Delimiters , line []byte ) * Delimiters {
8181 for _ , d := range delims {
8282 if startsWith (line , d .Open ) {
8383 return & d
@@ -92,7 +92,7 @@ func GetFullOpeningDelimiter(delims []Delimiters, line []byte) *Delimiters {
9292// `Parse` will be executed once for each character that is in this list of
9393// allowed trigger characters. Our parse function needs to do some additional
9494// checks because Trigger only works for single-byte delimiters.
95- func OpenersFirstByte (delims []Delimiters ) []byte {
95+ func openersFirstByte (delims []Delimiters ) []byte {
9696 var firstBytes []byte
9797 containsBackslash := false
9898 for _ , d := range delims {
@@ -111,7 +111,7 @@ func OpenersFirstByte(delims []Delimiters) []byte {
111111}
112112
113113// Determine if the input list of delimiters contains the given delimiter pair
114- func ContainsDelimiters (delims []Delimiters , toFind * Delimiters ) bool {
114+ func containsDelimiters (delims []Delimiters , toFind * Delimiters ) bool {
115115 for _ , d := range delims {
116116 if d .Open == toFind .Open && d .Close == toFind .Close {
117117 return true
@@ -122,7 +122,7 @@ func ContainsDelimiters(delims []Delimiters, toFind *Delimiters) bool {
122122}
123123
124124func (s * inlinePassthroughParser ) Trigger () []byte {
125- return OpenersFirstByte (s .PassthroughDelimiters )
125+ return openersFirstByte (s .PassthroughDelimiters )
126126}
127127
128128func (s * inlinePassthroughParser ) Parse (parent ast.Node , block text.Reader , pc parser.Context ) ast.Node {
@@ -132,7 +132,7 @@ func (s *inlinePassthroughParser) Parse(parent ast.Node, block text.Reader, pc p
132132 // of multiple triggers with parser.Context state saved between calls.
133133 line , startSegment := block .PeekLine ()
134134
135- fencePair := GetFullOpeningDelimiter (s .PassthroughDelimiters , line )
135+ fencePair := getFullOpeningDelimiter (s .PassthroughDelimiters , line )
136136 // fencePair == nil can happen if only the first byte of an opening delimiter
137137 // matches, but it is not the complete opening delimiter. The trigger causes
138138 // this Parse function to execute, but the trigger interface is limited to
@@ -141,7 +141,7 @@ func (s *inlinePassthroughParser) Parse(parent ast.Node, block text.Reader, pc p
141141 // double-backslash. In this case, we advance and return nil.
142142 if fencePair == nil {
143143 if len (line ) > 2 && line [0 ] == '\\' && line [1 ] == '\\' {
144- fencePair = GetFullOpeningDelimiter (s .PassthroughDelimiters , line [2 :])
144+ fencePair = getFullOpeningDelimiter (s .PassthroughDelimiters , line [2 :])
145145 if fencePair != nil {
146146 // Opening delimiter is escaped, return the escaped opener as plain text
147147 // So that the characters are not processed again.
@@ -178,7 +178,7 @@ func (s *inlinePassthroughParser) Parse(parent ast.Node, block text.Reader, pc p
178178 }
179179
180180 block .Advance (closingDelimiterPos + len (fencePair .Close ))
181- return NewPassthroughInline (seg , fencePair )
181+ return newPassthroughInline (seg , fencePair )
182182 }
183183}
184184
@@ -213,8 +213,8 @@ func (n *PassthroughBlock) Kind() ast.NodeKind {
213213 return KindPassthroughBlock
214214}
215215
216- // NewPassthroughBlock return a new PassthroughBlock node.
217- func NewPassthroughBlock () * PassthroughBlock {
216+ // newPassthroughBlock return a new PassthroughBlock node.
217+ func newPassthroughBlock () * PassthroughBlock {
218218 return & PassthroughBlock {
219219 BaseBlock : ast.BaseBlock {},
220220 }
@@ -247,18 +247,32 @@ type passthroughInlineTransformer struct {
247247
248248var PassthroughInlineTransformer = & passthroughInlineTransformer {}
249249
250+ const passthroughMarkedForDeletion = "passthrough_marked_for_deletion"
251+ const passthroughProcessed = "passthrough_processed"
252+
250253// Note, this transformer destroys the RawText attributes of the paragraph
251254// nodes that it transforms. However, this does not seem to have an impact on
252255// rendering.
253256func (p * passthroughInlineTransformer ) Transform (
254257 doc * ast.Document , reader text.Reader , pc parser.Context ) {
255-
258+ // Goldmark's walking algorithm is simplistic, and doesn't handle the
259+ // possibility of replacing the current node being walked with a new node. So
260+ // as a workaround, we split the walk in two. The first walk inserts new
261+ // nodes, and marks the original nodes for deletion. The second walk deletes
262+ // the marked nodes. To avoid an infinite loop, we also need to mark the
263+ // newly inserted nodes as "processed" so that they are not re-processed as
264+ // the walk continues.
256265 ast .Walk (doc , func (n ast.Node , entering bool ) (ast.WalkStatus , error ) {
257266 // Anchor on paragraphs
258267 if n .Kind () != ast .KindParagraph || ! entering {
259268 return ast .WalkContinue , nil
260269 }
261270
271+ val , found := n .AttributeString (passthroughProcessed )
272+ if found && val == "true" {
273+ return ast .WalkContinue , nil
274+ }
275+
262276 // If no direct children are passthroughs, skip it.
263277 foundInlinePassthrough := false
264278 for c := n .FirstChild (); c != nil ; c = c .NextSibling () {
@@ -287,16 +301,19 @@ func (p *passthroughInlineTransformer) Transform(
287301 inline := currentNode .(* PassthroughInline )
288302
289303 // Only split into a new block if the delimiters are block delimiters
290- if ! ContainsDelimiters (p .BlockDelimiters , inline .Delimiters ) {
304+ if ! containsDelimiters (p .BlockDelimiters , inline .Delimiters ) {
291305 currentParagraph .AppendChild (currentParagraph , currentNode )
292306 currentNode = nextNode
293307 continue
294308 }
295309
296- newBlock := NewPassthroughBlock ()
310+ newBlock := newPassthroughBlock ()
297311 newBlock .Lines ().Append (inline .Segment )
298312 if len (currentParagraph .Text (reader .Source ())) > 0 {
299313 parent .InsertAfter (parent , insertionPoint , currentParagraph )
314+ // Since we're not removing the original paragraph, we need to ensure
315+ // that this paragraph is not re-processed as the walk continues
316+ currentParagraph .SetAttributeString (passthroughProcessed , "true" )
300317 insertionPoint = currentParagraph
301318 }
302319 parent .InsertAfter (parent , insertionPoint , newBlock )
@@ -308,14 +325,41 @@ func (p *passthroughInlineTransformer) Transform(
308325
309326 if currentParagraph .ChildCount () > 0 {
310327 parent .InsertAfter (parent , insertionPoint , currentParagraph )
328+ // Since we're not removing the original paragraph, we need to ensure
329+ // that this paragraph is not re-processed as the walk continues
330+ currentParagraph .SetAttributeString (passthroughProcessed , "true" )
331+ }
332+
333+ // At this point, we don't remove the original paragraph, but mark it
334+ // for removal in the second walk.
335+ n .SetAttributeString (passthroughMarkedForDeletion , "true" )
336+ return ast .WalkContinue , nil
337+ })
338+
339+ // Now delete any marked nodes
340+ ast .Walk (doc , func (n ast.Node , entering bool ) (ast.WalkStatus , error ) {
341+ if ! entering {
342+ return ast .WalkContinue , nil
343+ }
344+
345+ for c := n .FirstChild (); c != nil ; {
346+ // Have to eagerly fetch this because `c` may be removed from the tree,
347+ // destroying its link to the next sibling.
348+ next := c .NextSibling ()
349+ if c .Kind () == ast .KindParagraph {
350+ val , found := c .AttributeString (passthroughMarkedForDeletion )
351+ if found && val == "true" {
352+ n .RemoveChild (n , c )
353+ }
354+ }
355+ c = next
311356 }
312357
313- parent .RemoveChild (parent , n )
314358 return ast .WalkContinue , nil
315359 })
316360}
317361
318- func NewPassthroughInlineTransformer (ds []Delimiters ) parser.ASTTransformer {
362+ func newPassthroughInlineTransformer (ds []Delimiters ) parser.ASTTransformer {
319363 return & passthroughInlineTransformer {
320364 BlockDelimiters : ds ,
321365 }
@@ -331,7 +375,7 @@ func (r *passthroughBlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRe
331375 reg .Register (KindPassthroughBlock , r .renderRawBlock )
332376}
333377
334- func NewPassthroughInlineRenderer () renderer.NodeRenderer {
378+ func newPassthroughInlineRenderer () renderer.NodeRenderer {
335379 return & passthroughInlineRenderer {}
336380}
337381
@@ -368,15 +412,15 @@ func NewPassthroughWithDelimiters(
368412func (e * passthrough ) Extend (m goldmark.Markdown ) {
369413 m .Parser ().AddOptions (
370414 parser .WithInlineParsers (
371- util .Prioritized (NewInlinePassthroughParser (e .InlineDelimiters ), 201 ),
415+ util .Prioritized (newInlinePassthroughParser (e .InlineDelimiters ), 201 ),
372416 ),
373417 parser .WithASTTransformers (
374- util .Prioritized (NewPassthroughInlineTransformer (e .BlockDelimiters ), 0 ),
418+ util .Prioritized (newPassthroughInlineTransformer (e .BlockDelimiters ), 0 ),
375419 ),
376420 )
377421
378422 m .Renderer ().AddOptions (renderer .WithNodeRenderers (
379- util .Prioritized (NewPassthroughInlineRenderer (), 101 ),
423+ util .Prioritized (newPassthroughInlineRenderer (), 101 ),
380424 util .Prioritized (NewPassthroughBlockRenderer (), 99 ),
381425 ))
382426}
0 commit comments