private void ProcessEmphasis(List <EmphasisDelimiterInline> delimiters) { // The following method is inspired by the "An algorithm for parsing nested emphasis and links" // at the end of the CommonMark specs. // Move current_position forward in the delimiter stack (if needed) until // we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input – the first one in parse order.) for (int i = 0; i < delimiters.Count; i++) { var closeDelimiter = delimiters[i]; // Skip delimiters not supported by this instance if (emphasisMap[closeDelimiter.DelimiterChar] == null) { continue; } if ((closeDelimiter.Type & DelimiterType.Close) != 0 && closeDelimiter.DelimiterCount > 0) { while (true) { // Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type) // for the first matching potential opener (“matching” means same delimiter). EmphasisDelimiterInline openDelimiter = null; int openDelimiterIndex = -1; for (int j = i - 1; j >= 0; j--) { var previousOpenDelimiter = delimiters[j]; var isOddMatch = ((closeDelimiter.Type & DelimiterType.Open) != 0 || (previousOpenDelimiter.Type & DelimiterType.Close) != 0) && previousOpenDelimiter.DelimiterCount != closeDelimiter.DelimiterCount && (previousOpenDelimiter.DelimiterCount + closeDelimiter.DelimiterCount) % 3 == 0; if (previousOpenDelimiter.DelimiterChar == closeDelimiter.DelimiterChar && (previousOpenDelimiter.Type & DelimiterType.Open) != 0 && previousOpenDelimiter.DelimiterCount > 0 && !isOddMatch) { openDelimiter = previousOpenDelimiter; openDelimiterIndex = j; break; } } if (openDelimiter != null) { process_delims: bool isStrong = openDelimiter.DelimiterCount >= 2 && closeDelimiter.DelimiterCount >= 2; // Insert an emph or strong emph node accordingly, after the text node corresponding to the opener. var emphasis = CreateEmphasisInline?.Invoke(closeDelimiter.DelimiterChar, isStrong) ?? new EmphasisInline() { DelimiterChar = closeDelimiter.DelimiterChar, IsDouble = isStrong }; // Update position for emphasis var closeDelimitercount = closeDelimiter.DelimiterCount; var delimiterDelta = isStrong ? 2 : 1; emphasis.Span.Start = openDelimiter.Span.Start; emphasis.Line = openDelimiter.Line; emphasis.Column = openDelimiter.Column; emphasis.Span.End = closeDelimiter.Span.End - closeDelimitercount + delimiterDelta; openDelimiter.Span.Start += delimiterDelta; openDelimiter.Column += delimiterDelta; closeDelimiter.Span.Start += delimiterDelta; closeDelimiter.Column += delimiterDelta; openDelimiter.DelimiterCount -= delimiterDelta; closeDelimiter.DelimiterCount -= delimiterDelta; var embracer = (ContainerInline)openDelimiter; // Copy attributes attached to delimiter to the emphasis var attributes = closeDelimiter.TryGetAttributes(); if (attributes != null) { emphasis.SetAttributes(attributes); } // Embrace all delimiters embracer.EmbraceChildrenBy(emphasis); // Remove any intermediate emphasis for (int k = i - 1; k >= openDelimiterIndex + 1; k--) { var literalDelimiter = delimiters[k]; var literal = new LiteralInline() { Content = new StringSlice(literalDelimiter.ToLiteral()), IsClosed = true, Span = literalDelimiter.Span, Line = literalDelimiter.Line, Column = literalDelimiter.Column }; literalDelimiter.ReplaceBy(literal); delimiters.RemoveAt(k); i--; } if (closeDelimiter.DelimiterCount == 0) { var newParent = openDelimiter.DelimiterCount > 0 ? emphasis : emphasis.Parent; closeDelimiter.MoveChildrenAfter(newParent); closeDelimiter.Remove(); delimiters.RemoveAt(i); i--; // Remove the open delimiter if it is also empty if (openDelimiter.DelimiterCount == 0) { openDelimiter.MoveChildrenAfter(openDelimiter); openDelimiter.Remove(); delimiters.RemoveAt(openDelimiterIndex); i--; } break; } // The current delimiters are matching if (openDelimiter.DelimiterCount > 0) { goto process_delims; } else { // Remove the open delimiter if it is also empty var firstChild = openDelimiter.FirstChild; firstChild.Remove(); openDelimiter.ReplaceBy(firstChild); firstChild.IsClosed = true; closeDelimiter.Remove(); firstChild.InsertAfter(closeDelimiter); delimiters.RemoveAt(openDelimiterIndex); i--; } } else if ((closeDelimiter.Type & DelimiterType.Open) == 0) { var literal = new LiteralInline() { Content = new StringSlice(closeDelimiter.ToLiteral()), IsClosed = true, Span = closeDelimiter.Span, Line = closeDelimiter.Line, Column = closeDelimiter.Column }; closeDelimiter.ReplaceBy(literal); // Notifies processor as we are creating an inline locally delimiters.RemoveAt(i); i--; break; } else { break; } } } } // Any delimiters left must be literal for (int i = 0; i < delimiters.Count; i++) { var delimiter = delimiters[i]; var literal = new LiteralInline() { Content = new StringSlice(delimiter.ToLiteral()), IsClosed = true, Span = delimiter.Span, Line = delimiter.Line, Column = delimiter.Column }; delimiter.ReplaceBy(literal); } delimiters.Clear(); }
private void ProcessEmphasis(InlineProcessor processor, List <EmphasisDelimiterInline> delimiters) { // The following method is inspired by the "An algorithm for parsing nested emphasis and links" // at the end of the CommonMark specs. // TODO: Benchmark difference between using List and LinkedList here since there could be a few Remove calls // Move current_position forward in the delimiter stack (if needed) until // we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input – the first one in parse order.) for (int i = 0; i < delimiters.Count; i++) { var closeDelimiter = delimiters[i]; // Skip delimiters not supported by this instance EmphasisDescriptor emphasisDesc = emphasisMap[closeDelimiter.DelimiterChar]; if (emphasisDesc == null) { continue; } if ((closeDelimiter.Type & DelimiterType.Close) != 0 && closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount) { while (true) { // Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type) // for the first matching potential opener (“matching” means same delimiter). EmphasisDelimiterInline openDelimiter = null; int openDelimiterIndex = -1; for (int j = i - 1; j >= 0; j--) { var previousOpenDelimiter = delimiters[j]; var isOddMatch = ((closeDelimiter.Type & DelimiterType.Open) != 0 || (previousOpenDelimiter.Type & DelimiterType.Close) != 0) && previousOpenDelimiter.DelimiterCount != closeDelimiter.DelimiterCount && (previousOpenDelimiter.DelimiterCount + closeDelimiter.DelimiterCount) % 3 == 0 && (previousOpenDelimiter.DelimiterCount % 3 != 0 || closeDelimiter.DelimiterCount % 3 != 0); if (previousOpenDelimiter.DelimiterChar == closeDelimiter.DelimiterChar && (previousOpenDelimiter.Type & DelimiterType.Open) != 0 && previousOpenDelimiter.DelimiterCount >= emphasisDesc.MinimumCount && !isOddMatch) { openDelimiter = previousOpenDelimiter; openDelimiterIndex = j; break; } } if (openDelimiter != null) { process_delims: Debug.Assert(openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount, "Extra emphasis should have been discarded by now"); Debug.Assert(closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount, "Extra emphasis should have been discarded by now"); int delimiterDelta = Math.Min(Math.Min(openDelimiter.DelimiterCount, closeDelimiter.DelimiterCount), emphasisDesc.MaximumCount); // Insert an emph or strong emph node accordingly, after the text node corresponding to the opener. EmphasisInline emphasis = null; { if (delimiterDelta <= 2) // We can try using the legacy delegate { #pragma warning disable CS0618 // Support fields marked as obsolete emphasis = CreateEmphasisInline?.Invoke(closeDelimiter.DelimiterChar, isStrong: delimiterDelta == 2); #pragma warning restore CS0618 // Support fields marked as obsolete } if (emphasis == null) { // Go in backwards order to give priority to newer delegates for (int delegateIndex = TryCreateEmphasisInlineList.Count - 1; delegateIndex >= 0; delegateIndex--) { emphasis = TryCreateEmphasisInlineList[delegateIndex].Invoke(closeDelimiter.DelimiterChar, delimiterDelta); if (emphasis != null) { break; } } if (emphasis == null) { emphasis = new EmphasisInline() { DelimiterChar = closeDelimiter.DelimiterChar, DelimiterCount = delimiterDelta }; } } } Debug.Assert(emphasis != null); // Update position for emphasis var openDelimitercount = openDelimiter.DelimiterCount; var closeDelimitercount = closeDelimiter.DelimiterCount; emphasis.Span.Start = openDelimiter.Span.Start; emphasis.Line = openDelimiter.Line; emphasis.Column = openDelimiter.Column; emphasis.Span.End = closeDelimiter.Span.End - closeDelimitercount + delimiterDelta; openDelimiter.Span.Start += delimiterDelta; openDelimiter.Column += delimiterDelta; closeDelimiter.Span.Start += delimiterDelta; closeDelimiter.Column += delimiterDelta; openDelimiter.DelimiterCount -= delimiterDelta; closeDelimiter.DelimiterCount -= delimiterDelta; var embracer = (ContainerInline)openDelimiter; // Copy attributes attached to delimiter to the emphasis var attributes = closeDelimiter.TryGetAttributes(); if (attributes != null) { emphasis.SetAttributes(attributes); } // Embrace all delimiters embracer.EmbraceChildrenBy(emphasis); // Remove any intermediate emphasis for (int k = i - 1; k >= openDelimiterIndex + 1; k--) { var literalDelimiter = delimiters[k]; literalDelimiter.ReplaceBy(literalDelimiter.AsLiteralInline()); delimiters.RemoveAt(k); i--; } if (closeDelimiter.DelimiterCount == 0) { var newParent = openDelimiter.DelimiterCount > 0 ? emphasis : emphasis.Parent; closeDelimiter.MoveChildrenAfter(newParent); closeDelimiter.Remove(); delimiters.RemoveAt(i); i--; // Remove the open delimiter if it is also empty if (openDelimiter.DelimiterCount == 0) { openDelimiter.MoveChildrenAfter(openDelimiter); openDelimiter.Remove(); delimiters.RemoveAt(openDelimiterIndex); i--; } break; } // The current delimiters are matching if (openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount) { goto process_delims; } else if (openDelimiter.DelimiterCount > 0) { // There are still delimiter characters left, there's just not enough of them openDelimiter.ReplaceBy(openDelimiter.AsLiteralInline()); delimiters.RemoveAt(openDelimiterIndex); i--; } else { // Remove the open delimiter if it is also empty var firstChild = openDelimiter.FirstChild; firstChild.Remove(); openDelimiter.ReplaceBy(firstChild); firstChild.IsClosed = true; closeDelimiter.Remove(); firstChild.InsertAfter(closeDelimiter); delimiters.RemoveAt(openDelimiterIndex); i--; } } else if ((closeDelimiter.Type & DelimiterType.Open) == 0) { closeDelimiter.ReplaceBy(closeDelimiter.AsLiteralInline()); delimiters.RemoveAt(i); i--; break; } else { break; } } } } // Any delimiters left must be literal for (int i = 0; i < delimiters.Count; i++) { var delimiter = delimiters[i]; delimiter.ReplaceBy(delimiter.AsLiteralInline()); } delimiters.Clear(); }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { // First, some definitions. A delimiter run is either a sequence of one or more * characters that // is not preceded or followed by a * character, or a sequence of one or more _ characters that // is not preceded or followed by a _ character. var delimiterChar = slice.CurrentChar; var emphasisDesc = emphasisMap[delimiterChar]; var pc = slice.PeekCharExtra(-1); if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\') { return(false); } var startPosition = slice.Start; int delimiterCount = 0; char c; do { delimiterCount++; c = slice.NextChar(); } while (c == delimiterChar); // If the emphasis doesn't have the minimum required character if (delimiterCount < emphasisDesc.MinimumCount) { return(false); } // Calculate Open-Close for current character bool canOpen; bool canClose; CharHelper.CheckOpenCloseDelimiter(pc, c, emphasisDesc.EnableWithinWord, out canOpen, out canClose); // We have potentially an open or close emphasis if (canOpen || canClose) { var delimiterType = DelimiterType.Undefined; if (canOpen) { delimiterType |= DelimiterType.Open; } if (canClose) { delimiterType |= DelimiterType.Close; } int line; int column; var delimiter = new EmphasisDelimiterInline(this, emphasisDesc) { DelimiterCount = delimiterCount, Type = delimiterType, Span = new SourceSpan(processor.GetSourcePosition(startPosition, out line, out column), processor.GetSourcePosition(slice.Start - 1)), Column = column, Line = line, }; processor.Inline = delimiter; return(true); } // We don't have an emphasis return(false); }